-
Notifications
You must be signed in to change notification settings - Fork 102
Prefer system git by default with fall back to JGit #382
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| package com.github.sbt.git | ||
|
|
||
| /** Selects which Git implementation sbt-git should use. | ||
| * | ||
| * System Git means the `git` executable available to the sbt process, | ||
| * normally through PATH. JGit means the Java implementation on the plugin | ||
| * classpath. | ||
| */ | ||
| sealed trait GitBackend extends Product with Serializable | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I might fall into "who want to force either backend" naïvely, but my gut feel is that:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I see the concern, but I would like to keep the meaning of For read operations, sbt-git currently defaults to JGit, and that generally works fine, especially for The fallback to JGit keeps existing non-worktree environments working when the 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
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:
The first one is the default for developer experience. The Second, we could decide not to expose backend choice at all and just make My preference is still the first option. Even if we removed the explicit |
||
|
|
||
| object GitBackend { | ||
| /** Prefer the system `git` executable and fall back to JGit if it is not | ||
| * available. | ||
| */ | ||
| case object SystemGitFirst extends GitBackend | ||
|
|
||
| /** Always use the system `git` executable. Builds fail if it is not | ||
| * available or cannot run the requested operation. | ||
| */ | ||
| case object SystemGitOnly extends GitBackend | ||
|
|
||
| /** Always use JGit. Builds fail if JGit cannot handle the requested | ||
| * repository layout or operation. | ||
| */ | ||
| case object JGitOnly extends GitBackend | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,7 +8,6 @@ object SbtGit { | |
|
|
||
| object GitKeys { | ||
| // Read-only git settings and values for use in other build settings. | ||
| // Note: These are all grabbed using jgit currently. | ||
| val gitReader = SettingKey[ReadableGit]("git-reader", "This gives us a read-only view of the git repository.") | ||
| val gitBranch = SettingKey[Option[String]]("git-branch", "Target branch of a git operation") | ||
| val gitCurrentBranch = SettingKey[String]("git-current-branch", "The current branch for this project.") | ||
|
|
@@ -20,9 +19,24 @@ object SbtGit { | |
| val gitDescribedVersion = SettingKey[Option[String]]("git-described-version", "Version as returned by `git describe --tags`.") | ||
| val gitUncommittedChanges = SettingKey[Boolean]("git-uncommitted-changes", "Whether there are uncommitted changes.") | ||
|
|
||
| @transient | ||
| val gitReadBackend = SettingKey[GitBackend]( | ||
| "git-read-backend", | ||
| "Selects the Git implementation used to read repository metadata such as HEAD, tags, branch, and dirty status." | ||
| ) | ||
|
|
||
| // A Mechanism to run Git directly. | ||
| @transient | ||
| val gitRunner = TaskKey[GitRunner]("git-runner", "The mechanism used to run git in the current build.") | ||
| @transient | ||
| val gitOperationBackend = SettingKey[GitBackend]( | ||
| "git-operation-backend", | ||
| "Selects the Git implementation used for explicit git operations such as clone, pull, push, and the sbt git command." | ||
| ) | ||
| private[sbt] val systemGitAvailableOverride = SettingKey[Option[Boolean]]( | ||
| "git-system-git-available-override", | ||
| "Internal test hook for overriding whether system git is detected." | ||
| ) | ||
|
|
||
| // Keys associated with setting a version number. | ||
| val useGitDescribe = SettingKey[Boolean]("use-git-describe", "Get version by calling `git describe` on the repository") | ||
|
|
@@ -44,8 +58,10 @@ object SbtGit { | |
| // The remote repository we're using. | ||
| val gitRemoteRepo = SettingKey[String]("git-remote-repo", "The remote git repository associated with this project") | ||
|
|
||
| // Git worktree workaround | ||
| val useConsoleForROGit = SettingKey[Boolean]("console-ro-runner", "Whether to shell out to git for ro ops in the current build.") | ||
| val useConsoleForROGit = SettingKey[Boolean]( | ||
| "console-ro-runner", | ||
| "Deprecated compatibility setting that forces read-only operations to use the system git executable." | ||
| ) | ||
| } | ||
|
|
||
| object GitCommand { | ||
|
|
@@ -116,12 +132,26 @@ object SbtGit { | |
| // Build settings. | ||
| import GitKeys.* | ||
| def buildSettings = Seq( | ||
| gitReadBackend := GitBackend.SystemGitFirst, | ||
| gitOperationBackend := GitBackend.SystemGitFirst, | ||
| systemGitAvailableOverride := None, | ||
| useConsoleForROGit := sys.env.contains("SBT_GIT_USE_CONSOLE_FOR_RO_GIT"), | ||
| gitReader := new DefaultReadableGit( | ||
| baseDirectory.value, | ||
| if (useConsoleForROGit.value) Some(new ConsoleGitReadableOnly(ConsoleGitRunner, file("."), sLog.value)) else None | ||
| ), | ||
| gitRunner := ConsoleGitRunner, | ||
| gitReader := { | ||
| val base = baseDirectory.value | ||
| val log = sLog.value | ||
| val available = detectedSystemGitAvailable(systemGitAvailableOverride.value, base) | ||
| val readable = available && isSystemGitReadable(base) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the readme:
But when discerning |
||
| new DefaultReadableGit( | ||
| base, | ||
| if (useSystemGitForReads(useConsoleForROGit.value, gitReadBackend.value, readable)) | ||
| Some(new ConsoleGitReadableOnly(ConsoleGitRunner, base, log)) | ||
| else None | ||
| ) | ||
| }, | ||
| gitRunner := { | ||
| val available = detectedSystemGitAvailable(systemGitAvailableOverride.value, baseDirectory.value) | ||
| selectGitRunner(gitOperationBackend.value, available) | ||
| }, | ||
| gitHeadCommit := gitReader.value.withGit(_.headCommitSha), | ||
| gitHeadMessage := gitReader.value.withGit(_.headCommitMessage), | ||
| gitHeadCommitDate := gitReader.value.withGit(_.headCommitDate), | ||
|
|
@@ -135,6 +165,29 @@ object SbtGit { | |
| ThisBuild / gitUncommittedChanges := gitReader.value.withGit(_.hasUncommittedChanges), | ||
| scmInfo := parseScmInfo(gitReader.value.withGit(_.remoteOrigin)) | ||
| ) | ||
|
|
||
| private[sbt] def isSystemGitAvailable(dir: File): Boolean = | ||
| ConsoleGitRunner.succeeds("--version")(dir) | ||
|
|
||
| private[sbt] def detectedSystemGitAvailable(overrideValue: Option[Boolean], dir: File): Boolean = | ||
| overrideValue.getOrElse(isSystemGitAvailable(dir)) | ||
|
|
||
| private[sbt] def isSystemGitReadable(dir: File): Boolean = | ||
| ConsoleGitRunner.succeeds("rev-parse", "--git-dir")(dir) | ||
|
|
||
| private[sbt] def useSystemGitForReads(forceSystemGit: Boolean, backend: GitBackend, systemGitAvailable: Boolean): Boolean = | ||
| forceSystemGit || useSystemGit(backend, systemGitAvailable) | ||
|
|
||
| private[sbt] def selectGitRunner(backend: GitBackend, systemGitAvailable: Boolean): GitRunner = | ||
| if (useSystemGit(backend, systemGitAvailable)) ConsoleGitRunner else JGitRunner | ||
|
|
||
| private[sbt] def useSystemGit(backend: GitBackend, systemGitAvailable: Boolean): Boolean = | ||
| backend match { | ||
| case GitBackend.SystemGitFirst => systemGitAvailable | ||
| case GitBackend.SystemGitOnly => true | ||
| case GitBackend.JGitOnly => false | ||
| } | ||
|
|
||
| private[sbt] def parseScmInfo(remoteOrigin: String): Option[ScmInfo] = { | ||
| val user = """(?:[^@\/]+@)?""" | ||
| val domain = """([^\/]+)""" | ||
|
|
@@ -175,8 +228,51 @@ object SbtGit { | |
| } | ||
| ) | ||
|
|
||
| /** A Predefined setting to use JGit runner for git. */ | ||
| def useJGit: Setting[?] = ThisBuild / gitRunner := JGitRunner | ||
| /** Use the system git executable for read-only repository metadata. | ||
| * | ||
| * This is deterministic: if system git is unavailable, the build fails | ||
| * instead of falling back to JGit. | ||
| */ | ||
| def useSystemGitOnlyForReads: Setting[?] = | ||
| ThisBuild / gitReadBackend := GitBackend.SystemGitOnly | ||
|
|
||
| /** Use JGit for read-only repository metadata. | ||
| * | ||
| * This is deterministic: sbt-git will not shell out to system git for | ||
| * reads, even if JGit cannot handle the repository layout. | ||
| */ | ||
| def useJGitOnlyForReads: Setting[?] = | ||
| ThisBuild / gitReadBackend := GitBackend.JGitOnly | ||
|
|
||
| /** Prefer system git for read-only repository metadata, falling back to JGit | ||
| * if system git is unavailable. This is the default. | ||
| */ | ||
| def useSystemGitFirstForReads: Setting[?] = | ||
| ThisBuild / gitReadBackend := GitBackend.SystemGitFirst | ||
|
|
||
| /** Use the system git executable for explicit git operations. | ||
| * | ||
| * This is deterministic: if system git is unavailable, the build fails | ||
| * instead of falling back to JGit. | ||
| */ | ||
| def useSystemGitOnlyForOperations: Setting[?] = | ||
| ThisBuild / gitOperationBackend := GitBackend.SystemGitOnly | ||
|
|
||
| /** Use JGit for explicit git operations such as clone, pull, push, and the | ||
| * sbt git command. | ||
| */ | ||
| def useJGitOnlyForOperations: Setting[?] = | ||
| ThisBuild / gitOperationBackend := GitBackend.JGitOnly | ||
|
|
||
| /** Prefer system git for explicit git operations, falling back to JGit if | ||
| * system git is unavailable. This is the default. | ||
| */ | ||
| def useSystemGitFirstForOperations: Setting[?] = | ||
| ThisBuild / gitOperationBackend := GitBackend.SystemGitFirst | ||
|
|
||
| /** Predefined settings to use JGit for reads and explicit git operations. */ | ||
| def useJGit: Seq[Setting[?]] = | ||
| Seq(useJGitOnlyForReads, useJGitOnlyForOperations) | ||
|
|
||
| /** Setting to use console git for readable ops, to allow working with git worktrees */ | ||
| def useReadableConsoleGit: Setting[?] = ThisBuild / useConsoleForROGit := true | ||
|
|
@@ -267,7 +363,9 @@ object SbtGit { | |
| object git { | ||
| val remoteRepo = GitKeys.gitRemoteRepo | ||
| val branch = GitKeys.gitBranch | ||
| val readBackend = ThisBuild / GitKeys.gitReadBackend | ||
| val runner = ThisBuild / GitKeys.gitRunner | ||
| val operationBackend = ThisBuild / GitKeys.gitOperationBackend | ||
| val gitHeadCommit = ThisBuild / GitKeys.gitHeadCommit | ||
| val gitHeadMessage = ThisBuild / GitKeys.gitHeadMessage | ||
| val gitHeadCommitDate = ThisBuild / GitKeys.gitHeadCommitDate | ||
|
|
@@ -344,9 +442,17 @@ object GitPlugin extends AutoPlugin { | |
| // Note: In an attempt to pretend we are binary compatible, we current add this as an after thought. | ||
| // In 1.0, we should deprecate/move the other means of getting these values. | ||
| object autoImport { | ||
| type GitBackend = com.github.sbt.git.GitBackend | ||
| val GitBackend = com.github.sbt.git.GitBackend | ||
| val git = SbtGit.git | ||
| def versionWithGit = SbtGit.versionWithGit | ||
| def versionProjectWithGit = SbtGit.versionProjectWithGit | ||
| def useSystemGitOnlyForReads = SbtGit.useSystemGitOnlyForReads | ||
| def useJGitOnlyForReads = SbtGit.useJGitOnlyForReads | ||
| def useSystemGitFirstForReads = SbtGit.useSystemGitFirstForReads | ||
| def useSystemGitOnlyForOperations = SbtGit.useSystemGitOnlyForOperations | ||
| def useJGitOnlyForOperations = SbtGit.useJGitOnlyForOperations | ||
| def useSystemGitFirstForOperations = SbtGit.useSystemGitFirstForOperations | ||
| def useJGit = SbtGit.useJGit | ||
| def useReadableConsoleGit = SbtGit.useReadableConsoleGit | ||
| def showCurrentGitBranch = SbtGit.showCurrentGitBranch | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think swapping the default to symbolic-ref first might return literal
HEADwhere previously the SHA of the detached branch's HEAD would have been returned