Repository navigation
Fix #11: run the command behind git -c, -C and other global options - #13
Merged
Merged
Conversation
…patch Leading -c name=value pairs become a per-invocation config overlay below operator-locked values, -C changes the working directory before repo discovery, pager flags are accepted and ignored, and any other leading option exits 129 with git's usage text. Override keys are now matched case-insensitively, as git does.
Contributor
Author
|
@blindmansion could you review this when you have a moment? It makes |
Owner
|
Thanks for raising, taking a look now |
Owner
|
This fix and a few followups are now published in 1.9.1 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #11
Summary
git -c user.name=x -c user.email=x@y.z commit -m msgnow creates the commit, authoredx <x@y.z>, and exits 0. Before this change it printed the top-level help to stdout, exited 0, and created nothing.git -C <path> <cmd>runs the command from<path>. An unknown leading option such as--bogus-optexits 129 withunknown option: --bogus-optand the usage line, as real git does.Root cause
Git.execute()insrc/git.tstookargs[0]as the subcommand.-cis not a registered command and is not inKNOWN_UNIMPLEMENTED_COMMANDS, so the parser fell through to the help text with exit 0. Nothing parsed git's global options before dispatch.A second, related gap surfaced while wiring the overlay. The
configOverridestier ingetConfigValuematched keys by exact string, and callers spell keys inconsistently (merge.conflictstylelowercase,init.defaultBranchcamelCase). Any-cvalue whose casing differed from the caller's would have been silently ignored, the same failure class the issue reports. Real git compares section and variable names case-insensitively.The fix
parseGlobalOptionsinsrc/git.tssplits leading options from the subcommand and returns either aGlobalOptionsrecord (chdirs,config,args) or anExecResulterror. It handles:-c <name>=<value>, repeatable. A name without=reads astrue. That mirrors how just-git'sparseConfigalready reads a valueless key in a config file, and differs from real git (see Not covered).-c =valuefails witherror: empty config keyand-c foo=1witherror: key does not contain a section: foo, both exit 128 underfatal: unable to parse command-line config.-C <path>, repeatable and cumulative. A missing directory fails withfatal: cannot change to '<path>': No such file or directory, exit 128.-p,--paginate,-P,--no-pager, accepted and ignored since just-git never pages.--helpand--versionpass through unchanged.-exits 129 withunknown option: <opt>plus the usage text. A missing argument to-cor-Cuses git's wording (-c expects a configuration string,no directory given for -C), exit 129.The
-coverlay rides the existinglockedtier ofConfigOverrides, so it reaches everygetConfigValueandgetConfigValueAllcaller and the direct read ininit.tswithout a new code path. It sits below operator-locked values and above everything else, socreateGit({ config: { locked: { "user.name": "Locked" } } })still wins overgit -c user.name=agent. The command tree closes over the extensions at construction, so an invocation with-cbuilds a fresh tree with the merged overrides for that call only. Measured cost is 0.3 ms per call.canonicalConfigKeyinsrc/lib/config.tslowercases the section and variable name and keeps the subsection as typed. Override maps are canonicalised whencreateGitreceives them,-ckeys are canonicalised at parse time, and the two lookup functions canonicalise the requested key.init.tsreadsinit.defaultbranchaccordingly.git config --listalready printed file keys in lowercase, so operator keys now print the same way.Blocked commands and
beforeCommand/afterCommandhooks see the real subcommand, its own arguments, and the-Cadjustedcwd. Hooks do not see the-cpairs themselves.Tests
test/global-options.test.ts, 19 tests, all run throughGit.executevia just-bash orGit.exec. They cover the commit author, the one-invocation overlay (git config user.nameafterwards still shows the old value),init.defaultBranchwithout a repository, arbitrary keys with mixed case, the valueless form, locked-beats--c, each-cand-Cerror,-Cfrom a parent directory with--show-prefix, pager flags,--help/--version, a blockedpushbehind-c, and the hook event shape.Every test except the hook test asserts the exit code of the command under test. Tests that run a command assert its exact stdout, and the success paths assert an empty stderr. Error paths assert exact stderr and an empty stdout. Two tests check stdout loosely:
--versionby prefix and--helpfor theCommands:heading. The hook test asserts only the event the hook received.One test pins the lookup canonicalisation on its own.
git -c clean.requireForce=false cleanmust printRemoving u, exit 0 and delete the untracked file, which matches real git. The-ckey is lowercased at parse time whileclean.tsasks forclean.requireForce, so the test fails ifgetConfigValuestops canonicalising the requested key. With the twocanonicalConfigKeycalls ingetConfigValueandgetConfigValueAllreverted it fails with expected"Removing u\n", received"", and the other 18 tests stay green.The test commit lands before the fix commit so the red run is in history.
bun testis 3507 pass, 29 skip, 0 fail (baseline 3488 plus these 19).bun oracle validatefails the same five steps on this branch and onfaa98c5, all inconfig unset,rebase --abortandworktree addwording that the local git 2.34.1 emits differently from the 2.53 target.Not covered
git -c foo.bar config foo.barprintstrue, the way just-git reads a valueless key in a config file. Real git prints an empty line for that read, andgit -c user.name commitfails in real git witherror: missing value for 'user.name', exit 128, where just-git commits with the nametrue.git config user.name Repofollowed bygit -c user.name=Cli config --listprints onlyuser.name=Repo. Real git printsuser.name=Repoanduser.name=Clias two lines.handleListinsrc/commands/config.tsappends alockedkey only when the file does not already have it, so a-ckey the file lacks is appended after the file entries and a-ckey the file has is left out of the listing. Operator-locked keys fromcreateGit({ config: { locked } })go through the same code, so changing it is out of scope here.-Cpointing at a regular file reportsNo such file or directorywhere git saysNot a directory.--git-dir,--work-tree,--bare,--namespace,--exec-pathand--config-envare reported as unknown options.