Prefer system git by default with fall back to JGit#382
Conversation
Add explicit backend settings for read-only metadata and git operations. Both now default to SystemGitFirst, which uses the system git executable when it is usable and falls back to JGit otherwise. Keep JGit 5 for the sbt 1 artifact and use JGit 7 for the sbt 2 artifact, where Java 17 is already required. This improves the JGit fallback and JGitOnly paths without raising the Java floor for sbt 1 users. Add SystemGitOnly and JGitOnly helpers for users who need deterministic behavior. Deprecate the older useJGit and useReadableConsoleGit aliases in favor of the new read and operation helpers. Keep the system git detection details internal. Cover the missing-system-git fallback and linked-worktree default paths with scripted tests.
|
Now that people use AI agents, which includes workflows that check out worktrees, we should finally make sbt-git handle git worktrees reliable. Using command line git by default will cover 98% of the uses cases reliable, and even for the rest we will have a fallback to jgit.
will fail. But even in that case, things can be fixed in application code by upgrading jgit to v7 manually in |
Keep the console read backend closer to the previous JGit behavior. It now handles unborn repositories without failing during project load, treats empty command output as an empty collection, and ignores untracked files when checking whether tracked sources are dirty. Add unit coverage for the unborn repository and untracked-file cases that caused Java 8 scripted failures after making system git the default read backend.
|
Ahh test fail... Switching the default read backend to the system git command uncovered a few places where the existing console reader did not match the JGit behavior that sbt-git exposed before. The commands themselves were valid, but their semantics differed in important edge cases: The follow-up commit keeps the system git reader compatible with the previous public behavior while still allowing system git to be the default. |
Bring the console read-only backend closer to the behavior exposed by JGit. Use Git formatting options for branch names, pass all describe match patterns, avoid shell-style quotes in commit dates, and keep the same origin fallback when no remote is configured. Add unit coverage for these read operations so future changes to the system git backend keep the public read API stable.
|
I also tightened the existing console read backend in a separate commit. Making system git the preferred read backend means this implementation is now on the main path, so I audited the remaining places where it did not match the behavior users previously got through JGit. The fixes are small but important: branch reads now use Git's ref formatting instead of parsing decorated I added unit tests for these cases so the system git backend has coverage for the public read API behavior we rely on. |
|
Nice. Thanks for the contribution! |
| * normally through PATH. JGit means the Java implementation on the plugin | ||
| * classpath. | ||
| */ | ||
| sealed trait GitBackend extends Product with Serializable |
There was a problem hiding this comment.
I might fall into "who want to force either backend" naïvely, but my gut feel is that:
- We should either use system git (aka console git) or JGit, no halfway
- Current regime uses GitRunner's class inheritance (?) to denote the different backend, so having this sealed trait feels duplicated. Especially we if go with system vs JGit dichotomy all we need is
Boolean?
There was a problem hiding this comment.
- We should either use system git (aka console git) or JGit, no halfway
I see the concern, but I would like to keep the meaning of SystemGitFirst because it preserves the low-friction setup story: for most users sbt-git will use the installed git command, but a build can still load in environments where only Java/sbt are available but no git command.
For read operations, sbt-git currently defaults to JGit, and that generally works fine, especially for sbt-dynver sbt-ci-release, which is probably how many users encounter sbt-git in the first place. The main reason to prefer system git now is that JGit 5 does not support linked worktrees, while JGit 7 does but requires Java 17. Since the sbt 1 artifact still supports Java 8, system git is the only backend that can make worktrees work out of the box there.
The fallback to JGit keeps existing non-worktree environments working when the git executable is not available. The known remaining edge case is: sbt 1, linked worktree, no system git, and no manual JGit 7 override.
That matters for projects like Play Framework where the contributor story is ideally "install Java and sbt and start hacking." Most developers will have system git, but some environments may not: minimal containers, restricted CI images, machines where Git is accessed through an IDE/UI integration, or setups where git is installed outside PATH.
- Current regime uses GitRunner's class inheritance (?) to denote the different backend, so having this sealed trait feels duplicated. Especially we if go with system vs JGit dichotomy all we need is
Boolean?
I agree that we should avoid unnecessary backend complexity. I think there are two possible directions:
First, if we want to give users explicit control, I do not think this is just a Boolean. There are three useful user intents:
SystemGitFirst: prefer system git, fall back to JGitSystemGitOnly: require system git and fail if unavailableJGitOnly: never shell out
The first one is the default for developer experience. The Only modes are for users or CI setups that want deterministic behavior.
Second, we could decide not to expose backend choice at all and just make SystemGitFirst the fixed behavior.
My preference is still the first option. Even if we removed the explicit JGitOnly / SystemGitOnly cases, we would still have the same concepts because the legacy helpers already express them: useJGit means "use JGit, do not shell out", and useReadableConsoleGit means "use system git for reads." Keeping the enum cases makes those existing intents explicit instead of encoding them as special cases elsewhere.
| @deprecated("System git is now preferred for reads by default. Use useSystemGitOnlyForReads to force it.", "2.0.0") | ||
| def useReadableConsoleGit: Setting[?] = ThisBuild / useConsoleForROGit := true |
There was a problem hiding this comment.
As you mentioned the flag situation requires cognitive tax on the user. Given the long history of sbt-git, spiritually useReadableConsoleGit users have already bought into "just use git", so I think it's ok to leave this setting alone as Nil or make it an alias of useSystemGitOnlyForReads, but without the punishment of deprecation.
There was a problem hiding this comment.
OK. I removed the deprecation and kept this as a compatibility helper.
I chose to preserve the existing useConsoleForROGit flag behavior instead of making it a direct alias to useSystemGitOnlyForReads, so existing users keep the same semantics and do not get a warning for a setting that already expresses the intended “use system git for reads” behavior.
| @deprecated("Use useJGitOnlyForOperations instead.", "2.0.0") | ||
| def useJGit: Setting[?] = useJGitOnlyForOperations |
There was a problem hiding this comment.
Similar for useJGit, I think the original intent of ThisBuild / gitRunner := JGitRunner is that JGit will be used for all operations.
There was a problem hiding this comment.
Agreed. I changed useJGit to express the broader legacy intent: it now selects JGit for both read-only metadata and explicit git operations.
That does change the helper from a single Setting[?] to Seq[Setting[?]], but the behavior is much closer to what users reasonably expect from a setting named useJGit.
Keep useReadableConsoleGit as a non-deprecated compatibility helper that preserves the existing read-only console flag. This avoids warning users who had already opted into system git reads. Make useJGit express the broader legacy intent by selecting JGit for both read-only metadata and explicit git operations. Add scripted coverage for both legacy helpers and update the README.
Run system git availability and readability checks through a silent probe instead of the normal ConsoleGitRunner path. These checks are expected to fail when git is missing or the build is not inside a git repository, so they should not log scary fatal messages during project load. Keep normal git command execution unchanged so real command failures still report stderr to the user. Add unit coverage for the non-git-directory readability case.
|
Whoa, I just realized something while testing this against the Play 2.9.x branch (checked out in a linked worktree): The linked-worktree failure is not actually because Play's versioning path uses The reason sbt-git is involved is that if a project uses sbt-ci-release, that one brings sbt-git onto the plugin classpath! I still think this PR is useful, because any build that has sbt-git on the plugin classpath can hit the same problem. But the Play case reveals that most projects just fail because sbt-ci-release unnecessarily pulls in sbt-git! |
Problem
sbt-git currently defaults read-only metadata operations to JGit. That is problematic for linked Git worktrees when the plugin is used with JGit 5.x, because JGit 5 does not support that repository layout. This shows up in downstream plugins such as
sbt-dynversbt-ci-release when an sbt project is checked out as a linked worktree.JGit 7 has worktree support, but it requires Java 17. sbt-git still publishes an sbt 1 artifact that must remain Java 8 compatible, so upgrading JGit unconditionally is not viable.
Related context:
Changes
This changes the default backend selection to prefer the system
gitexecutable for both read-only metadata and explicit git operations.The new default is
SystemGitFirst:New public helpers:
useSystemGitFirstForReadsuseSystemGitOnlyForReadsuseJGitOnlyForReadsuseSystemGitFirstForOperationsuseSystemGitOnlyForOperationsuseJGitOnlyForOperationsThe older helpers are kept as deprecated aliases:
(IMO the old helper names were also confusing:
useJGitonly affected explicit git operations, not all sbt-git reads, whileuseReadableConsoleGitcontrolled only read-only metadata access. The new read/operation-specific helpers make that split explicit.)useJGituseReadableConsoleGitThe JGit dependency is also split by artifact:
That preserves the Java 8 floor for sbt 1 while allowing the sbt 2 artifact to benefit from JGit 7 behavior.
Compatibility
useJGitstill works, but is deprecated and now maps touseJGitOnlyForOperations, which matches its old practical scope.useReadableConsoleGitstill works, but is deprecated and forces system git for reads.git.gitHeadCommit,git.gitCurrentBranch,git.gitUncommittedChanges,git.runner, and the sbtgitcommand remain available.Tests
Added coverage for:
Verified with: