diff options
150 files changed, 4841 insertions, 2003 deletions
diff --git a/.gitignore b/.gitignore index a685ec1fb0..4fd81baf85 100644 --- a/.gitignore +++ b/.gitignore @@ -205,6 +205,7 @@ /test-sha1-array /test-sigchain /test-string-list +/test-submodule-config /test-subprocess /test-svn-fe /test-urlmatch-normalization diff --git a/Documentation/RelNotes/2.4.8.txt b/Documentation/RelNotes/2.4.8.txt new file mode 100644 index 0000000000..ad946b2673 --- /dev/null +++ b/Documentation/RelNotes/2.4.8.txt @@ -0,0 +1,21 @@ +Git v2.4.8 Release Notes +======================== + +Fixes since v2.4.7 +------------------ + + * Abandoning an already applied change in "git rebase -i" with + "--continue" left CHERRY_PICK_HEAD and confused later steps. + + * Various fixes around "git am" that applies a patch to a history + that is not there yet. + + * "git for-each-ref" reported "missing object" for 0{40} when it + encounters a broken ref. The lack of object whose name is 0{40} is + not the problem; the ref being broken is. + + * "git commit --cleanup=scissors" was not careful enough to protect + against getting fooled by a line that looked like scissors. + +Also contains typofixes, documentation updates and trivial code +clean-ups. diff --git a/Documentation/RelNotes/2.5.1.txt b/Documentation/RelNotes/2.5.1.txt new file mode 100644 index 0000000000..b70553308a --- /dev/null +++ b/Documentation/RelNotes/2.5.1.txt @@ -0,0 +1,65 @@ +Git v2.5.1 Release Notes +======================== + +Fixes since v2.5 +---------------- + + * Running an aliased command from a subdirectory when the .git thing + in the working tree is a gitfile pointing elsewhere did not work. + + * Often a fast-import stream builds a new commit on top of the + previous commit it built, and it often unconditionally emits a + "from" command to specify the first parent, which can be omitted in + such a case. This caused fast-import to forget the tree of the + previous commit and then re-read it from scratch, which was + inefficient. Optimize for this common case. + + * The "rev-parse --parseopt" mode parsed the option specification + and the argument hint in a strange way to allow '=' and other + special characters in the option name while forbidding them from + the argument hint. This made it impossible to define an option + like "--pair <key>=<value>" with "pair=key=value" specification, + which instead would have defined a "--pair=key <value>" option. + + * A "rebase" replays changes of the local branch on top of something + else, as such they are placed in stage #3 and referred to as + "theirs", while the changes in the new base, typically a foreign + work, are placed in stage #2 and referred to as "ours". Clarify + the "checkout --ours/--theirs". + + * An experimental "untracked cache" feature used uname(2) in a + slightly unportable way. + + * "sparse checkout" misbehaved for a path that is excluded from the + checkout when switching between branches that differ at the path. + + * The low-level "git send-pack" did not honor 'user.signingkey' + configuration variable when sending a signed-push. + + * An attempt to delete a ref by pushing into a repository whose HEAD + symbolic reference points at an unborn branch that cannot be + created due to ref D/F conflict (e.g. refs/heads/a/b exists, HEAD + points at refs/heads/a) failed. + + * "git subtree" (in contrib/) depended on "git log" output to be + stable, which was a no-no. Apply a workaround to force a + particular date format. + + * "git clone $URL" in recent releases of Git contains a regression in + the code that invents a new repository name incorrectly based on + the $URL. This has been corrected. + (merge db2e220 jk/guess-repo-name-regression-fix later to maint). + + * Running tests with the "-x" option to make them verbose had some + unpleasant interactions with other features of the test suite. + (merge 9b5fe78 jk/test-with-x later to maint). + + * "git pull" in recent releases of Git has a regression in the code + that allows custom path to the --upload-pack=<program>. This has + been corrected. + + * pipe() emulation used in Git for Windows looked at a wrong variable + when checking for an error from an _open_osfhandle() call. + +Also contains typofixes, documentation updates and trivial code +clean-ups. diff --git a/Documentation/RelNotes/2.6.0.txt b/Documentation/RelNotes/2.6.0.txt new file mode 100644 index 0000000000..050371d94c --- /dev/null +++ b/Documentation/RelNotes/2.6.0.txt @@ -0,0 +1,281 @@ +Git 2.6 Release Notes +===================== + +Updates since v2.5 +------------------ + +UI, Workflows & Features + + * An asterisk as a substring (as opposed to the entirety) of a path + component for both side of a refspec, e.g. + "refs/heads/o*:refs/remotes/heads/i*", is now allowed. + + * New userdiff pattern definition for fountain screenwriting markup + format has been added. + + * "git log" and friends learned a new "--date=format:..." option to + format timestamps using system's strftime(3). + + * "git fast-import" learned to respond to the get-mark command via + its cat-blob-fd interface. + + * "git rebase -i" learned "drop commit-object-name subject" command + as another way to skip replaying of a commit. + + * A new configuration variable can enable "--follow" automatically + when "git log" is run with one pathspec argument. + + * "git status" learned to show a more detailed information regarding + the "rebase -i" session in progress. + + * "git cat-file" learned "--batch-all-objects" option to enumerate all + available objects in the repository more quickly than "rev-list + --all --objects" (the output includes unreachable objects, though). + + * "git fsck" learned to ignore errors on a set of known-to-be-bad + objects, and also allows the warning levels of various kinds of + non-critical breakages to be tweaked. + + * "git rebase -i"'s list of todo is made configurable. + + * "git send-email" now performs alias-expansion on names that are + given via --cccmd, etc. + + * An environment variable GIT_REPLACE_REF_BASE tells Git to look into + refs hierarchy other than refs/replace/ for the object replacement + data. + + * Allow untracked cache (experimental) to be used when sparse + checkout (experimental) is also in use. + + * "git pull --rebase" has been taught to pay attention to + rebase.autostash configuration. + + * The command-line completion script (in contrib/) has been updated. + + * A negative !ref entry in multi-value transfer.hideRefs + configuration can be used to say "don't hide this one". + + * After "git am" without "-3" stops, running "git am -" pays attention + to "-3" only for the patch that caused the original invocation + to stop. + + * When linked worktree is used, simultaneous "notes merge" instances + for the same ref in refs/notes/* are prevented from stomping on + each other. + + * "git send-email" learned a new option --smtp-auth to limit the SMTP + AUTH mechanisms to be used to a subset of what the system library + supports. + + * A new configuration variable http.sslVersion can be used to specify + what specific version of SSL/TLS to use to make a connection. + + +Performance, Internal Implementation, Development Support etc. + + * In preparation for allowing different "backends" to store the refs + in a way different from the traditional "one ref per file in + $GIT_DIR or in a $GIT_DIR/packed-refs file" filesystem storage, + direct filesystem access to ref-like things like CHERRY_PICK_HEAD + from scripts and programs has been reduced. + + * Computation of untracked status indicator by bash prompt + script (in contrib/) has been optimized. + + * Memory use reduction when commit-slab facility is used to annotate + sparsely (which is not recommended in the first place). + + * Clean up refs API and make "git clone" less intimate with the + implementation detail. + + * "git pull" was reimplemented in C. + + * The packet tracing machinery allows to capture an incoming pack + data to a file for debugging. + + * Move machinery to parse human-readable scaled numbers like 1k, 4M, + and 2G as an option parameter's value from pack-objects to + parse-options API, to make it available to other codepaths. + + * "git verify-tag" and "git verify-commit" have been taught to share + more code, and then learned to optionally show the verification + message from the underlying GPG implementation. + + * Various enhancements around "git am" reading patches generated by + foreign SCM have been made. + + * Ref listing by "git branch -l" and "git tag -l" commands has + started to be rebuilt, based on the for-each-ref machinery. + + * The code to perform multi-tree merges has been taught to repopulate + the cache-tree upon a successful merge into the index, so that + subsequent "diff-index --cached" (hence "status") and "write-tree" + (hence "commit") will go faster. + + The same logic in "git checkout" may now be removed, but that is a + separate issue. + + * Tests that assume how reflogs are represented on the filesystem too + much have been corrected. + + * "git am" has been rewritten in "C". + + * git_path() and mkpath() are handy helper functions but it is easy + to misuse, as the callers need to be careful to keep the number of + active results below 4. Their uses have been reduced. + + * The "lockfile" API has been rebuilt on top of a new "tempfile" API. + + * To prepare for allowing a different "ref" backend to be plugged in + to the system, update_ref()/delete_ref() have been taught about + ref-like things like MERGE_HEAD that are per-worktree (they will + always be written to the filesystem inside $GIT_DIR). + + +Also contains various documentation updates and code clean-ups. + + +Fixes since v2.5 +---------------- + +Unless otherwise noted, all the fixes since v2.5 in the maintenance +track are contained in this release (see the maintenance releases' +notes for details). + + * "git subtree" (in contrib/) depended on "git log" output to be + stable, which was a no-no. Apply a workaround to force a + particular date format. + (merge e7aac44 da/subtree-date-confusion later to maint). + + * An attempt to delete a ref by pushing into a repository whose HEAD + symbolic reference points at an unborn branch that cannot be + created due to ref D/F conflict (e.g. refs/heads/a/b exists, HEAD + points at refs/heads/a) failed. + (merge b112b14 jx/do-not-crash-receive-pack-wo-head later to maint). + + * The low-level "git send-pack" did not honor 'user.signingkey' + configuration variable when sending a signed-push. + (merge d830d39 db/send-pack-user-signingkey later to maint). + + * "sparse checkout" misbehaved for a path that is excluded from the + checkout when switching between branches that differ at the path. + (merge 7d78241 as/sparse-checkout-removal later to maint). + + * An experimental "untracked cache" feature used uname(2) in a + slightly unportable way. + (merge 100e433 cb/uname-in-untracked later to maint). + + * A "rebase" replays changes of the local branch on top of something + else, as such they are placed in stage #3 and referred to as + "theirs", while the changes in the new base, typically a foreign + work, are placed in stage #2 and referred to as "ours". Clarify + the "checkout --ours/--theirs". + (merge f303016 se/doc-checkout-ours-theirs later to maint). + + * The "rev-parse --parseopt" mode parsed the option specification + and the argument hint in a strange way to allow '=' and other + special characters in the option name while forbidding them from + the argument hint. This made it impossible to define an option + like "--pair <key>=<value>" with "pair=key=value" specification, + which instead would have defined a "--pair=key <value>" option. + (merge 2d893df ib/scripted-parse-opt-better-hint-string later to maint). + + * Often a fast-import stream builds a new commit on top of the + previous commit it built, and it often unconditionally emits a + "from" command to specify the first parent, which can be omitted in + such a case. This caused fast-import to forget the tree of the + previous commit and then re-read it from scratch, which was + inefficient. Optimize for this common case. + (merge 0df3245 mh/fast-import-optimize-current-from later to maint). + + * Running an aliased command from a subdirectory when the .git thing + in the working tree is a gitfile pointing elsewhere did not work. + (merge d95138e nd/export-worktree later to maint). + + * "Is this subdirectory a separate repository that should not be + touched?" check "git clean" was inefficient. This was replaced + with a more optimized check. + (merge 38ae878 ee/clean-remove-dirs later to maint). + + * The "new-worktree-mode" hack in "checkout" that was added in + nd/multiple-work-trees topic has been removed by updating the + implementation of new "worktree add". + (merge 65f9b75 es/worktree-add-cleanup later to maint). + + * Remove remaining cruft from "git checkout --to", which + transitioned to "git worktree add". + (merge 114ff88 es/worktree-add later to maint). + + * An off-by-one error made "git remote" to mishandle a remote with a + single letter nickname. + (merge bc598c3 mh/get-remote-group-fix later to maint). + + * "git clone $URL", when cloning from a site whose sole purpose is to + host a single repository (hence, no path after <scheme>://<site>/), + tried to use the site name as the new repository name, but did not + remove username or password when <site> part was of the form + <user>@<pass>:<host>. The code is taught to redact these. + (merge adef956 ps/guess-repo-name-at-root later to maint). + + * Running tests with the "-x" option to make them verbose had some + unpleasant interactions with other features of the test suite. + (merge 9b5fe78 jk/test-with-x later to maint). + + * t1509 test that requires a dedicated VM environment had some + bitrot, which has been corrected. + (merge faacc5a ps/t1509-chroot-test-fixup later to maint). + + * "git pull" in recent releases of Git has a regression in the code + that allows custom path to the --upload-pack=<program>. This has + been corrected. + + Note that this is irrelevant for 'master' with "git pull" rewritten + in C. + (merge 22d6857 mm/pull-upload-pack later to maint). + + * When trying to see that an object does not exist, a state errno + leaked from our "first try to open a packfile with O_NOATIME and + then if it fails retry without it" logic on a system that refuses + O_NOATIME. This confused us and caused us to die, saying that the + packfile is unreadable, when we should have just reported that the + object does not exist in that packfile to the caller. + (merge dff6f28 cb/open-noatime-clear-errno later to maint). + + * The codepath to produce error messages had a hard-coded limit to + the size of the message, primarily to avoid memory allocation while + calling die(). + (merge f4c3edc jk/long-error-messages later to maint). + + * strbuf_read() used to have one extra iteration (and an unnecessary + strbuf_grow() of 8kB), which was eliminated. + (merge 3ebbd00 jh/strbuf-read-use-read-in-full later to maint). + + * We rewrote one of the build scripts in Perl but this reimplements + in Bourne shell. + (merge 82aec45 sg/help-group later to maint). + + * The experimental untracked-cache feature were buggy when paths with + a few levels of subdirectories are involved. + (merge 73f9145 dt/untracked-subdir later to maint). + + * "interpret-trailers" helper mistook a single-liner log message that + has a colon as the end of existing trailer. + (merge 6262fe9 cc/trailers-corner-case-fix later to maint). + + * Code cleanups and documentation updates. + (merge 1c601af es/doc-clean-outdated-tools later to maint). + (merge 3581304 kn/tag-doc-fix later to maint). + (merge 3a59e59 kb/i18n-doc later to maint). + (merge 45abdee sb/remove-unused-var-from-builtin-add later to maint). + (merge 14691e3 sb/parse-options-codeformat later to maint). + (merge 4a6ada3 ad/bisect-cleanup later to maint). + (merge da4c5ad ta/docfix-index-format-tech later to maint). + (merge ae25fd3 sb/check-return-from-read-ref later to maint). + (merge b3325df nd/dwim-wildcards-as-pathspecs later to maint). + (merge 7aa9b9b sg/wt-status-header-inclusion later to maint). + (merge f04c690 as/docfix-reflog-expire-unreachable later to maint). + (merge 1269847 sg/t3020-typofix later to maint). + (merge 8b54c23 jc/calloc-pathspec later to maint). + (merge a6926b8 po/po-readme later to maint). + (merge 54d160e ss/fix-config-fd-leak later to maint). diff --git a/Documentation/config.txt b/Documentation/config.txt index fb3fc57911..f5d15fff3e 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1307,28 +1307,34 @@ gc.packRefs:: gc.pruneExpire:: When 'git gc' is run, it will call 'prune --expire 2.weeks.ago'. Override the grace period with this config variable. The value - "now" may be used to disable this grace period and always prune - unreachable objects immediately. - -gc.pruneWorktreesExpire:: - When 'git gc' is run, it will call - 'prune --worktrees --expire 3.months.ago'. - Override the grace period with this config variable. The value - "now" may be used to disable the grace period and prune - $GIT_DIR/worktrees immediately. + "now" may be used to disable this grace period and always prune + unreachable objects immediately, or "never" may be used to + suppress pruning. + +gc.worktreePruneExpire:: + When 'git gc' is run, it calls + 'git worktree prune --expire 3.months.ago'. + This config variable can be used to set a different grace + period. The value "now" may be used to disable the grace + period and prune $GIT_DIR/worktrees immediately, or "never" + may be used to suppress pruning. gc.reflogExpire:: gc.<pattern>.reflogExpire:: 'git reflog expire' removes reflog entries older than - this time; defaults to 90 days. With "<pattern>" (e.g. + this time; defaults to 90 days. The value "now" expires all + entries immediately, and "never" suppresses expiration + altogether. With "<pattern>" (e.g. "refs/stash") in the middle the setting applies only to the refs that match the <pattern>. gc.reflogExpireUnreachable:: -gc.<ref>.reflogExpireUnreachable:: +gc.<pattern>.reflogExpireUnreachable:: 'git reflog expire' removes reflog entries older than this time and are not reachable from the current tip; - defaults to 30 days. With "<pattern>" (e.g. "refs/stash") + defaults to 30 days. The value "now" expires all entries + immediately, and "never" suppresses expiration altogether. + With "<pattern>" (e.g. "refs/stash") in the middle, the setting applies only to the refs that match the <pattern>. @@ -1603,6 +1609,29 @@ http.saveCookies:: If set, store cookies received during requests to the file specified by http.cookieFile. Has no effect if http.cookieFile is unset. +http.sslVersion:: + The SSL version to use when negotiating an SSL connection, if you + want to force the default. The available and default version + depend on whether libcurl was built against NSS or OpenSSL and the + particular configuration of the crypto library in use. Internally + this sets the 'CURLOPT_SSL_VERSION' option; see the libcurl + documentation for more details on the format of this option and + for the ssl version supported. Actually the possible values of + this option are: + + - sslv2 + - sslv3 + - tlsv1 + - tlsv1.0 + - tlsv1.1 + - tlsv1.2 + ++ +Can be overridden by the 'GIT_SSL_VERSION' environment variable. +To force git to use libcurl's default ssl version and ignore any +explicit http.sslversion option, set 'GIT_SSL_VERSION' to the +empty string. + http.sslCipherList:: A list of SSL ciphers to use when negotiating an SSL connection. The available ciphers depend on whether libcurl was built against @@ -2307,13 +2336,10 @@ receive.denyNonFastForwards:: set when initializing a shared repository. receive.hideRefs:: - String(s) `receive-pack` uses to decide which refs to omit - from its initial advertisement. Use more than one - definitions to specify multiple prefix strings. A ref that - are under the hierarchies listed on the value of this - variable is excluded, and is hidden when responding to `git - push`, and an attempt to update or delete a hidden ref by - `git push` is rejected. + This variable is the same as `transfer.hideRefs`, but applies + only to `receive-pack` (and so affects pushes, but not fetches). + An attempt to update or delete a hidden ref by `git push` is + rejected. receive.updateServerInfo:: If set to true, git-receive-pack will run git-update-server-info @@ -2601,9 +2627,18 @@ transfer.fsckObjects:: Defaults to false. transfer.hideRefs:: - This variable can be used to set both `receive.hideRefs` - and `uploadpack.hideRefs` at the same time to the same - values. See entries for these other variables. + String(s) `receive-pack` and `upload-pack` use to decide which + refs to omit from their initial advertisements. Use more than + one definition to specify multiple prefix strings. A ref that is + under the hierarchies listed in the value of this variable is + excluded, and is hidden when responding to `git push` or `git + fetch`. See `receive.hideRefs` and `uploadpack.hideRefs` for + program-specific versions of this config. ++ +You may also include a `!` in front of the ref name to negate the entry, +explicitly exposing it, even if an earlier entry marked it as hidden. +If you have multiple hideRefs values, later entries override earlier ones +(and entries in more-specific config files override less-specific ones). transfer.unpackLimit:: When `fetch.unpackLimit` or `receive.unpackLimit` are @@ -2618,13 +2653,10 @@ uploadarchive.allowUnreachable:: `false`. uploadpack.hideRefs:: - String(s) `upload-pack` uses to decide which refs to omit - from its initial advertisement. Use more than one - definitions to specify multiple prefix strings. A ref that - are under the hierarchies listed on the value of this - variable is excluded, and is hidden from `git ls-remote`, - `git fetch`, etc. An attempt to fetch a hidden ref by `git - fetch` will fail. See also `uploadpack.allowTipSHA1InWant`. + This variable is the same as `transfer.hideRefs`, but applies + only to `upload-pack` (and so affects only fetches, not pushes). + An attempt to fetch a hidden ref by `git fetch` will fail. See + also `uploadpack.allowTipSHA1InWant`. uploadpack.allowTipSHA1InWant:: When `uploadpack.hideRefs` is in effect, allow `upload-pack` diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt index 4cb52a7302..e97f2de21b 100644 --- a/Documentation/git-bisect.txt +++ b/Documentation/git-bisect.txt @@ -3,7 +3,7 @@ git-bisect(1) NAME ---- -git-bisect - Find by binary search the change that introduced a bug +git-bisect - Use binary search to find the commit that introduced a bug SYNOPSIS @@ -16,7 +16,6 @@ DESCRIPTION The command takes various subcommands, and different options depending on the subcommand: - git bisect help git bisect start [--no-checkout] [<bad> [<good>...]] [--] [<paths>...] git bisect bad [<rev>] git bisect good [<rev>...] @@ -26,64 +25,71 @@ on the subcommand: git bisect replay <logfile> git bisect log git bisect run <cmd>... + git bisect help -This command uses 'git rev-list --bisect' to help drive the -binary search process to find which change introduced a bug, given an -old "good" commit object name and a later "bad" commit object name. - -Getting help -~~~~~~~~~~~~ - -Use "git bisect" to get a short usage description, and "git bisect -help" or "git bisect -h" to get a long usage description. +This command uses a binary search algorithm to find which commit in +your project's history introduced a bug. You use it by first telling +it a "bad" commit that is known to contain the bug, and a "good" +commit that is known to be before the bug was introduced. Then `git +bisect` picks a commit between those two endpoints and asks you +whether the selected commit is "good" or "bad". It continues narrowing +down the range until it finds the exact commit that introduced the +change. Basic bisect commands: start, bad, good ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Using the Linux kernel tree as an example, basic use of the bisect -command is as follows: +As an example, suppose you are trying to find the commit that broke a +feature that was known to work in version `v2.6.13-rc2` of your +project. You start a bisect session as follows: ------------------------------------------------ $ git bisect start $ git bisect bad # Current version is bad -$ git bisect good v2.6.13-rc2 # v2.6.13-rc2 was the last version - # tested that was good +$ git bisect good v2.6.13-rc2 # v2.6.13-rc2 is known to be good ------------------------------------------------ -When you have specified at least one bad and one good version, the -command bisects the revision tree and outputs something similar to -the following: +Once you have specified at least one bad and one good commit, `git +bisect` selects a commit in the middle of that range of history, +checks it out, and outputs something similar to the following: ------------------------------------------------ -Bisecting: 675 revisions left to test after this +Bisecting: 675 revisions left to test after this (roughly 10 steps) ------------------------------------------------ -The state in the middle of the set of revisions is then checked out. -You would now compile that kernel and boot it. If the booted kernel -works correctly, you would then issue the following command: +You should now compile the checked-out version and test it. If that +version works correctly, type ------------------------------------------------ -$ git bisect good # this one is good +$ git bisect good ------------------------------------------------ -The output of this command would be something similar to the following: +If that version is broken, type ------------------------------------------------ -Bisecting: 337 revisions left to test after this +$ git bisect bad ------------------------------------------------ -You keep repeating this process, compiling the tree, testing it, and -depending on whether it is good or bad issuing the command "git bisect good" -or "git bisect bad" to ask for the next bisection. +Then `git bisect` will respond with something like + +------------------------------------------------ +Bisecting: 337 revisions left to test after this (roughly 9 steps) +------------------------------------------------ + +Keep repeating the process: compile the tree, test it, and depending +on whether it is good or bad run `git bisect good` or `git bisect bad` +to ask for the next commit that needs testing. + +Eventually there will be no more revisions left to inspect, and the +command will print out a description of the first bad commit. The +reference `refs/bisect/bad` will be left pointing at that commit. -Eventually there will be no more revisions left to bisect, and you -will have been left with the first bad kernel revision in "refs/bisect/bad". Bisect reset ~~~~~~~~~~~~ After a bisect session, to clean up the bisection state and return to -the original HEAD (i.e., to quit bisecting), issue the following command: +the original HEAD, issue the following command: ------------------------------------------------ $ git bisect reset @@ -100,9 +106,10 @@ instead: $ git bisect reset <commit> ------------------------------------------------ -For example, `git bisect reset HEAD` will leave you on the current -bisection commit and avoid switching commits at all, while `git bisect -reset bisect/bad` will check out the first bad revision. +For example, `git bisect reset bisect/bad` will check out the first +bad revision, while `git bisect reset HEAD` will leave you on the +current bisection commit and avoid switching commits at all. + Bisect visualize ~~~~~~~~~~~~~~~~ @@ -147,17 +154,17 @@ $ git bisect replay that-file Avoiding testing a commit ~~~~~~~~~~~~~~~~~~~~~~~~~ -If, in the middle of a bisect session, you know that the next suggested -revision is not a good one to test (e.g. the change the commit -introduces is known not to work in your environment and you know it -does not have anything to do with the bug you are chasing), you may -want to find a nearby commit and try that instead. +If, in the middle of a bisect session, you know that the suggested +revision is not a good one to test (e.g. it fails to build and you +know that the failure does not have anything to do with the bug you +are chasing), you can manually select a nearby commit and test that +one instead. For example: ------------ $ git bisect good/bad # previous round was good or bad. -Bisecting: 337 revisions left to test after this +Bisecting: 337 revisions left to test after this (roughly 9 steps) $ git bisect visualize # oops, that is uninteresting. $ git reset --hard HEAD~3 # try 3 revisions before what # was suggested @@ -169,18 +176,19 @@ the revision as good or bad in the usual manner. Bisect skip ~~~~~~~~~~~~ -Instead of choosing by yourself a nearby commit, you can ask Git -to do it for you by issuing the command: +Instead of choosing a nearby commit by yourself, you can ask Git to do +it for you by issuing the command: ------------ $ git bisect skip # Current version cannot be tested ------------ -But Git may eventually be unable to tell the first bad commit among -a bad commit and one or more skipped commits. +However, if you skip a commit adjacent to the one you are looking for, +Git will be unable to tell exactly which of those commits was the +first bad one. -You can even skip a range of commits, instead of just one commit, -using the "'<commit1>'..'<commit2>'" notation. For example: +You can also skip a range of commits, instead of just one commit, +using range notation. For example: ------------ $ git bisect skip v2.5..v2.6 @@ -196,8 +204,8 @@ would issue the command: $ git bisect skip v2.5 v2.5..v2.6 ------------ -This tells the bisect process that the commits between `v2.5` included -and `v2.6` included should be skipped. +This tells the bisect process that the commits between `v2.5` and +`v2.6` (inclusive) should be skipped. Cutting down bisection by giving more parameters to bisect start @@ -231,14 +239,14 @@ or bad, you can bisect by issuing the command: $ git bisect run my_script arguments ------------ -Note that the script (`my_script` in the above example) should -exit with code 0 if the current source code is good, and exit with a -code between 1 and 127 (inclusive), except 125, if the current -source code is bad. +Note that the script (`my_script` in the above example) should exit +with code 0 if the current source code is good/old, and exit with a +code between 1 and 127 (inclusive), except 125, if the current source +code is bad/new. Any other exit code will abort the bisect process. It should be noted -that a program that terminates via "exit(-1)" leaves $? = 255, (see the -exit(3) manual page), as the value is chopped with "& 0377". +that a program that terminates via `exit(-1)` leaves $? = 255, (see the +exit(3) manual page), as the value is chopped with `& 0377`. The special exit code 125 should be used when the current source code cannot be tested. If the script exits with this code, the current @@ -247,7 +255,7 @@ as the highest sensible value to use for this purpose, because 126 and 127 are used by POSIX shells to signal specific error status (127 is for command not found, 126 is for command found but not executable---these details do not matter, as they are normal errors in the script, as far as -"bisect run" is concerned). +`bisect run` is concerned). You may often find that during a bisect session you want to have temporary modifications (e.g. s/#define DEBUG 0/#define DEBUG 1/ in a @@ -260,7 +268,7 @@ next revision to test, the script can apply the patch before compiling, run the real test, and afterwards decide if the revision (possibly with the needed patch) passed the test and then rewind the tree to the pristine state. Finally the script should exit -with the status of the real test to let the "git bisect run" command loop +with the status of the real test to let the `git bisect run` command loop determine the eventual outcome of the bisect session. OPTIONS @@ -307,12 +315,12 @@ $ git bisect run ~/test.sh $ git bisect reset # quit the bisect session ------------ + -Here we use a "test.sh" custom script. In this script, if "make" +Here we use a `test.sh` custom script. In this script, if `make` fails, we skip the current commit. -"check_test_case.sh" should "exit 0" if the test case passes, -and "exit 1" otherwise. +`check_test_case.sh` should `exit 0` if the test case passes, +and `exit 1` otherwise. + -It is safer if both "test.sh" and "check_test_case.sh" are +It is safer if both `test.sh` and `check_test_case.sh` are outside the repository to prevent interactions between the bisect, make and test processes and the scripts. @@ -379,6 +387,11 @@ In this case, when 'git bisect run' finishes, bisect/bad will refer to a commit has at least one parent whose reachable graph is fully traversable in the sense required by 'git pack objects'. +Getting help +~~~~~~~~~~~~ + +Use `git bisect` to get a short usage description, and `git bisect +help` or `git bisect -h` to get a long usage description. SEE ALSO -------- diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt index fc02959ba4..9044dfaada 100644 --- a/Documentation/git-check-ref-format.txt +++ b/Documentation/git-check-ref-format.txt @@ -94,8 +94,8 @@ OPTIONS Interpret <refname> as a reference name pattern for a refspec (as used with remote repositories). If this option is enabled, <refname> is allowed to contain a single `*` - in place of a one full pathname component (e.g., - `foo/*/bar` but not `foo/bar*`). + in the refspec (e.g., `foo/bar*/baz` or `foo/bar*baz/` + but not `foo/bar*/baz*`). --normalize:: Normalize 'refname' by removing any leading slash (`/`) diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index 02ec096faa..2608ca74ac 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -14,13 +14,13 @@ SYNOPSIS 'git config' [<file-option>] [type] --replace-all name value [value_regex] 'git config' [<file-option>] [type] [-z|--null] --get name [value_regex] 'git config' [<file-option>] [type] [-z|--null] --get-all name [value_regex] -'git config' [<file-option>] [type] [-z|--null] --get-regexp name_regex [value_regex] +'git config' [<file-option>] [type] [-z|--null] [--name-only] --get-regexp name_regex [value_regex] 'git config' [<file-option>] [type] [-z|--null] --get-urlmatch name URL 'git config' [<file-option>] --unset name [value_regex] 'git config' [<file-option>] --unset-all name [value_regex] 'git config' [<file-option>] --rename-section old_name new_name 'git config' [<file-option>] --remove-section name -'git config' [<file-option>] [-z|--null] -l | --list +'git config' [<file-option>] [-z|--null] [--name-only] -l | --list 'git config' [<file-option>] --get-color name [default] 'git config' [<file-option>] --get-colorbool name [stdout-is-tty] 'git config' [<file-option>] -e | --edit @@ -159,7 +159,7 @@ See also <<FILES>>. -l:: --list:: - List all variables set in config file. + List all variables set in config file, along with their values. --bool:: 'git config' will ensure that the output is "true" or "false" @@ -190,6 +190,10 @@ See also <<FILES>>. output without getting confused e.g. by values that contain line breaks. +--name-only:: + Output only the names of config variables for `--list` or + `--get-regexp`. + --get-colorbool name [stdout-is-tty]:: Find the color setting for `name` (e.g. `color.diff`) and output diff --git a/Documentation/git-reflog.txt b/Documentation/git-reflog.txt index 5e7908e4f7..44c736f1a8 100644 --- a/Documentation/git-reflog.txt +++ b/Documentation/git-reflog.txt @@ -23,6 +23,7 @@ depending on the subcommand: [--dry-run] [--verbose] [--all | <refs>...] 'git reflog delete' [--rewrite] [--updateref] [--dry-run] [--verbose] ref@\{specifier\}... +'git reflog exists' <ref> Reference logs, or "reflogs", record when the tips of branches and other references were updated in the local repository. Reflogs are @@ -52,6 +53,9 @@ argument must be an _exact_ entry (e.g. "`git reflog delete master@{2}`"). This subcommand is also typically not used directly by end users. +The "exists" subcommand checks whether a ref has a reflog. It exits +with zero status if the reflog exists, and non-zero status if it does +not. OPTIONS ------- diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index f14705ee04..b9134d234f 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -171,6 +171,19 @@ Sending to determine your FQDN automatically. Default is the value of 'sendemail.smtpDomain'. +--smtp-auth=<mechanisms>:: + Whitespace-separated list of allowed SMTP-AUTH mechanisms. This setting + forces using only the listed mechanisms. Example: ++ +------ +$ git send-email --smtp-auth="PLAIN LOGIN GSSAPI" ... +------ ++ +If at least one of the specified mechanisms matches the ones advertised by the +SMTP server and if it is supported by the utilized SASL library, the mechanism +is used for authentication. If neither 'sendemail.smtpAuth' nor '--smtp-auth' +is specified, all mechanisms supported by the SASL library can be used. + --smtp-pass[=<password>]:: Password for SMTP-AUTH. The argument is optional: If no argument is specified, then the empty string is used as diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 4b04c2b7d5..84f6496bf2 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -13,7 +13,7 @@ SYNOPSIS <tagname> [<commit> | <object>] 'git tag' -d <tagname>... 'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>] - [--column[=<options>] | --no-column] [<pattern>...] + [--column[=<options>] | --no-column] [--create-reflog] [<pattern>...] 'git tag' -v <tagname>... DESCRIPTION @@ -142,6 +142,9 @@ This option is only applicable when listing tags without annotation lines. all, 'whitespace' removes just leading/trailing whitespace lines and 'strip' removes both whitespace and commentary. +--create-reflog:: + Create a reflog for the tag. + <tagname>:: The name of the tag to create, delete, or describe. The new tag name must pass all checks defined by diff --git a/Documentation/git-tools.txt b/Documentation/git-tools.txt index 78a0d955ec..2f4ff50156 100644 --- a/Documentation/git-tools.txt +++ b/Documentation/git-tools.txt @@ -1,118 +1,10 @@ -A short Git tools survey -======================== +Git Tools +========= +When Git was young, people looking for third-party Git-related tools came +to the Git project itself to find them, thus a list of such tools was +maintained here. These days, however, search engines fill that role much +more efficiently, so this manually-maintained list has been retired. -Introduction ------------- - -Apart from Git contrib/ area there are some others third-party tools -you may want to look. - -This document presents a brief summary of each tool and the corresponding -link. - - -Alternative/Augmentative Porcelains ------------------------------------ - - - *Cogito* (http://www.kernel.org/pub/software/scm/cogito/) - - Cogito is a version control system layered on top of the Git tree history - storage system. It aims at seamless user interface and ease of use, - providing generally smoother user experience than the "raw" Core Git - itself and indeed many other version control systems. - - Cogito is no longer maintained as most of its functionality - is now in core Git. - - - - *pg* (http://www.spearce.org/category/projects/scm/pg/) - - pg is a shell script wrapper around Git to help the user manage a set of - patches to files. pg is somewhat like quilt or StGit, but it does have a - slightly different feature set. - - - - *StGit* (http://www.procode.org/stgit/) - - Stacked Git provides a quilt-like patch management functionality in the - Git environment. You can easily manage your patches in the scope of Git - until they get merged upstream. - - -History Viewers ---------------- - - - *gitk* (shipped with git-core) - - gitk is a simple Tk GUI for browsing history of Git repositories easily. - - - - *gitview* (contrib/) - - gitview is a GTK based repository browser for Git - - - - *gitweb* (shipped with git-core) - - Gitweb provides full-fledged web interface for Git repositories. - - - - *qgit* (http://digilander.libero.it/mcostalba/) - - QGit is a git/StGit GUI viewer built on Qt/C++. QGit could be used - to browse history and directory tree, view annotated files, commit - changes cherry picking single files or applying patches. - Currently it is the fastest and most feature rich among the Git - viewers and commit tools. - - - *tig* (http://jonas.nitro.dk/tig/) - - tig by Jonas Fonseca is a simple Git repository browser - written using ncurses. Basically, it just acts as a front-end - for git-log and git-show/git-diff. Additionally, you can also - use it as a pager for Git commands. - - -Foreign SCM interface ---------------------- - - - *git-svn* (shipped with git-core) - - git-svn is a simple conduit for changesets between a single Subversion - branch and Git. - - - - *quilt2git / git2quilt* (http://home-tj.org/wiki/index.php/Misc) - - These utilities convert patch series in a quilt repository and commit - series in Git back and forth. - - - - *hg-to-git* (contrib/) - - hg-to-git converts a Mercurial repository into a Git one, and - preserves the full branch history in the process. hg-to-git can - also be used in an incremental way to keep the Git repository - in sync with the master Mercurial repository. - - -Others ------- - - - *(h)gct* (http://www.cyd.liu.se/users/~freku045/gct/) - - Commit Tool or (h)gct is a GUI enabled commit tool for Git and - Mercurial (hg). It allows the user to view diffs, select which files - to committed (or ignored / reverted) write commit messages and - perform the commit itself. - - - *git.el* (contrib/) - - This is an Emacs interface for Git. The user interface is modelled on - pcl-cvs. It has been developed on Emacs 21 and will probably need some - tweaking to work on XEmacs. - - -http://git.or.cz/gitwiki/InterfacesFrontendsAndTools has more -comprehensive list. +See also the `contrib/` area, and the Git wiki: +http://git.or.cz/gitwiki/InterfacesFrontendsAndTools diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt index c8f5ae5cb3..969bfab2ab 100644 --- a/Documentation/git-update-ref.txt +++ b/Documentation/git-update-ref.txt @@ -8,7 +8,7 @@ git-update-ref - Update the object name stored in a ref safely SYNOPSIS -------- [verse] -'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>] | --stdin [-z]) +'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] [--create-reflog] <ref> <newvalue> [<oldvalue>] | --stdin [-z]) DESCRIPTION ----------- @@ -67,6 +67,9 @@ performs all modifications together. Specify commands of the form: verify SP <ref> [SP <oldvalue>] LF option SP <opt> LF +With `--create-reflog`, update-ref will create a reflog for each ref +even if one would not ordinarily be created. + Quote fields containing whitespace as if they were strings in C source code; i.e., surrounded by double-quotes and with backslash escapes. Use 40 "0" characters or the empty string to specify a zero value. To diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt index 3387e2f037..fb68156cf8 100644 --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@ -3,7 +3,7 @@ git-worktree(1) NAME ---- -git-worktree - Manage multiple worktrees +git-worktree - Manage multiple working trees SYNOPSIS @@ -15,7 +15,7 @@ SYNOPSIS DESCRIPTION ----------- -Manage multiple worktrees attached to the same repository. +Manage multiple working trees attached to the same repository. A git repository can support multiple working trees, allowing you to check out more than one branch at a time. With `git worktree add` a new working @@ -27,19 +27,19 @@ bare repository) and zero or more linked working trees. When you are done with a linked working tree you can simply delete it. The working tree's administrative files in the repository (see "DETAILS" below) will eventually be removed automatically (see -`gc.pruneworktreesexpire` in linkgit::git-config[1]), or you can run +`gc.worktreePruneExpire` in linkgit:git-config[1]), or you can run `git worktree prune` in the main or any linked working tree to clean up any stale administrative files. -If you move a linked working directory to another file system, or +If you move a linked working tree to another file system, or within a file system that does not support hard links, you need to run -at least one git command inside the linked working directory +at least one git command inside the linked working tree (e.g. `git status`) in order to update its administrative files in the repository so that they do not get automatically pruned. If a linked working tree is stored on a portable device or network share which is not always mounted, you can prevent its administrative files from -being pruned by creating a file named 'lock' alongside the other +being pruned by creating a file named 'locked' alongside the other administrative files, optionally containing a plain text reason that pruning should be suppressed. See section "DETAILS" for more information. @@ -51,9 +51,9 @@ Create `<path>` and checkout `<branch>` into it. The new working directory is linked to the current repository, sharing everything except working directory specific files such as HEAD, index, etc. + -If `<branch>` is omitted and neither `-b` nor `-B` is used, then, as a -convenience, a new branch based at HEAD is created automatically, as if -`-b $(basename <path>)` was specified. +If `<branch>` is omitted and neither `-b` nor `-B` nor `--detached` used, +then, as a convenience, a new branch based at HEAD is created automatically, +as if `-b $(basename <path>)` was specified. prune:: @@ -64,22 +64,22 @@ OPTIONS -f:: --force:: - By default, `add` refuses to create a new worktree when `<branch>` - is already checked out by another worktree. This option overrides + By default, `add` refuses to create a new working tree when `<branch>` + is already checked out by another working tree. This option overrides that safeguard. -b <new-branch>:: -B <new-branch>:: With `add`, create a new branch named `<new-branch>` starting at - `<branch>`, and check out `<new-branch>` into the new worktree. + `<branch>`, and check out `<new-branch>` into the new working tree. If `<branch>` is omitted, it defaults to HEAD. By default, `-b` refuses to create a new branch if it already exists. `-B` overrides this safeguard, resetting `<new-branch>` to `<branch>`. --detach:: - With `add`, detach HEAD in the new worktree. See "DETACHED HEAD" in - linkgit:git-checkout[1]. + With `add`, detach HEAD in the new working tree. See "DETACHED HEAD" + in linkgit:git-checkout[1]. -n:: --dry-run:: @@ -91,7 +91,7 @@ OPTIONS With `prune`, report all removals. --expire <time>:: - With `prune`, only expire unused worktrees older than <time>. + With `prune`, only expire unused working trees older than <time>. DETAILS ------- @@ -124,7 +124,7 @@ thumb is do not make any assumption about whether a path belongs to $GIT_DIR or $GIT_COMMON_DIR when you need to directly access something inside $GIT_DIR. Use `git rev-parse --git-path` to get the final path. -To prevent a $GIT_DIR/worktrees entry from from being pruned (which +To prevent a $GIT_DIR/worktrees entry from being pruned (which can be useful in some situations, such as when the entry's working tree is stored on a portable device), add a file named 'locked' to the entry's directory. The file contains the reason in @@ -139,9 +139,9 @@ EXAMPLES You are in the middle of a refactoring session and your boss comes in and demands that you fix something immediately. You might typically use linkgit:git-stash[1] to store your changes away temporarily, however, your -worktree is in such a state of disarray (with new, moved, and removed files, -and other bits and pieces strewn around) that you don't want to risk -disturbing any of it. Instead, you create a temporary linked worktree to +working tree is in such a state of disarray (with new, moved, and removed +files, and other bits and pieces strewn around) that you don't want to risk +disturbing any of it. Instead, you create a temporary linked working tree to make the emergency fix, remove it when done, and then resume your earlier refactoring session. @@ -164,12 +164,12 @@ checkouts of a superproject. git-worktree could provide more automation for tasks currently performed manually, such as: -- `remove` to remove a linked worktree and its administrative files (and - warn if the worktree is dirty) -- `mv` to move or rename a worktree and update its administrative files -- `list` to list linked worktrees +- `remove` to remove a linked working tree and its administrative files (and + warn if the working tree is dirty) +- `mv` to move or rename a working tree and update its administrative files +- `list` to list linked working trees - `lock` to prevent automatic pruning of administrative files (for instance, - for a worktree on a portable device) + for a working tree on a portable device) GIT --- diff --git a/Documentation/git.txt b/Documentation/git.txt index f87d3325f8..4e5d55be6a 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,14 +43,16 @@ unreleased) version of Git, that is available from the 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v2.5.0/git.html[documentation for release 2.5] +* link:v2.5.1/git.html[documentation for release 2.5.1] * release notes for - link:RelNotes/2.5.0.txt[2.5], + link:RelNotes/2.5.1.txt[2.5.1], + link:RelNotes/2.5.0.txt[2.5]. -* link:v2.4.7/git.html[documentation for release 2.4.7] +* link:v2.4.8/git.html[documentation for release 2.4.8] * release notes for + link:RelNotes/2.4.8.txt[2.4.8], link:RelNotes/2.4.7.txt[2.4.7], link:RelNotes/2.4.6.txt[2.4.6], link:RelNotes/2.4.5.txt[2.4.5], @@ -780,7 +782,7 @@ The Git Repository ~~~~~~~~~~~~~~~~~~ These environment variables apply to 'all' core Git commands. Nb: it is worth noting that they may be used/overridden by SCMS sitting above -Git so take care if using Cogito etc. +Git so take care if using a foreign front-end. 'GIT_INDEX_FILE':: This environment allows the specification of an alternate diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 81fe586948..e3b1de8033 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -527,6 +527,8 @@ patterns are available: - `fortran` suitable for source code in the Fortran language. +- `fountain` suitable for Fountain documents. + - `html` suitable for HTML/XHTML documents. - `java` suitable for source code in the Java language. diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt index 7173b38830..577ee844e0 100644 --- a/Documentation/gitrepository-layout.txt +++ b/Documentation/gitrepository-layout.txt @@ -251,25 +251,25 @@ modules:: Contains the git-repositories of the submodules. worktrees:: - Contains worktree specific information of linked - checkouts. Each subdirectory contains the worktree-related - part of a linked checkout. This directory is ignored if - $GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/worktrees" will be - used instead. + Contains administrative data for linked + working trees. Each subdirectory contains the working tree-related + part of a linked working tree. This directory is ignored if + $GIT_COMMON_DIR is set, in which case + "$GIT_COMMON_DIR/worktrees" will be used instead. worktrees/<id>/gitdir:: A text file containing the absolute path back to the .git file that points to here. This is used to check if the linked repository has been manually removed and there is no need to - keep this directory any more. mtime of this file should be + keep this directory any more. The mtime of this file should be updated every time the linked repository is accessed. worktrees/<id>/locked:: - If this file exists, the linked repository may be on a - portable device and not available. It does not mean that the - linked repository is gone and `worktrees/<id>` could be - removed. The file's content contains a reason string on why - the repository is locked. + If this file exists, the linked working tree may be on a + portable device and not available. The presence of this file + prevents `worktrees/<id>` from being pruned either automatically + or manually by `git worktree prune`. The file may contain a string + explaining why the repository is locked. worktrees/<id>/link:: If this file exists, it is a hard link to the linked .git diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index ab18f4baca..8c6478b2f2 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -411,6 +411,27 @@ exclude;; core Git. Porcelains expose more of a <<def_SCM,SCM>> interface than the <<def_plumbing,plumbing>>. +[[def_per_worktree_ref]]per-worktree ref:: + Refs that are per-<<def_working_tree,worktree>>, rather than + global. This is presently only <<def_HEAD,HEAD>>, but might + later include other unusual refs. + +[[def_pseudoref]]pseudoref:: + Pseudorefs are a class of files under `$GIT_DIR` which behave + like refs for the purposes of rev-parse, but which are treated + specially by git. Pseudorefs both have names that are all-caps, + and always start with a line consisting of a + <<def_SHA1,SHA-1>> followed by whitespace. So, HEAD is not a + pseudoref, because it is sometimes a symbolic ref. They might + optionally contain some additional data. `MERGE_HEAD` and + `CHERRY_PICK_HEAD` are examples. Unlike + <<def_per_worktree_ref,per-worktree refs>>, these files cannot + be symbolic refs, and never have reflogs. They also cannot be + updated through the normal ref update machinery. Instead, + they are updated by directly writing to the files. However, + they can be read as if they were refs, so `git rev-parse + MERGE_HEAD` will work. + [[def_pull]]pull:: Pulling a <<def_branch,branch>> means to <<def_fetch,fetch>> it and <<def_merge,merge>> it. See also linkgit:git-pull[1]. diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index dc865cbb27..671cebd95c 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -139,7 +139,9 @@ The placeholders are: - '%f': sanitized subject line, suitable for a filename - '%b': body - '%B': raw body (unwrapped subject and body) +ifndef::git-rev-list[] - '%N': commit notes +endif::git-rev-list[] - '%GG': raw verification message from GPG for a signed commit - '%G?': show "G" for a Good signature, "B" for a Bad signature, "U" for a good, untrusted signature and "N" for no signature diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt index 642af6e426..8d6c5cec4c 100644 --- a/Documentation/pretty-options.txt +++ b/Documentation/pretty-options.txt @@ -42,6 +42,7 @@ people using 80-column terminals. verbatim; this means that invalid sequences in the original commit may be copied to the output. +ifndef::git-rev-list[] --notes[=<ref>]:: Show the notes (see linkgit:git-notes[1]) that annotate the commit, when showing the commit log message. This is the default @@ -73,6 +74,7 @@ being displayed. Examples: "--notes=foo" will show only notes from --[no-]standard-notes:: These options are deprecated. Use the above --notes/--no-notes options instead. +endif::git-rev-list[] --show-signature:: Check the validity of a signed commit object by passing the signature diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index a9b808fab3..f1c52208f0 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -58,9 +58,11 @@ endif::git-rev-list[] more than one `--grep=<pattern>`, commits whose message matches any of the given patterns are chosen (but see `--all-match`). +ifndef::git-rev-list[] + When `--show-notes` is in effect, the message from the notes is matched as if it were part of the log message. +endif::git-rev-list[] --all-match:: Limit the commits output to ones that match all given `--grep`, diff --git a/Documentation/technical/api-lockfile.txt b/Documentation/technical/api-lockfile.txt deleted file mode 100644 index 93b5f23e4c..0000000000 --- a/Documentation/technical/api-lockfile.txt +++ /dev/null @@ -1,220 +0,0 @@ -lockfile API -============ - -The lockfile API serves two purposes: - -* Mutual exclusion and atomic file updates. When we want to change a - file, we create a lockfile `<filename>.lock`, write the new file - contents into it, and then rename the lockfile to its final - destination `<filename>`. We create the `<filename>.lock` file with - `O_CREAT|O_EXCL` so that we can notice and fail if somebody else has - already locked the file, then atomically rename the lockfile to its - final destination to commit the changes and unlock the file. - -* Automatic cruft removal. If the program exits after we lock a file - but before the changes have been committed, we want to make sure - that we remove the lockfile. This is done by remembering the - lockfiles we have created in a linked list and setting up an - `atexit(3)` handler and a signal handler that clean up the - lockfiles. This mechanism ensures that outstanding lockfiles are - cleaned up if the program exits (including when `die()` is called) - or if the program dies on a signal. - -Please note that lockfiles only block other writers. Readers do not -block, but they are guaranteed to see either the old contents of the -file or the new contents of the file (assuming that the filesystem -implements `rename(2)` atomically). - - -Calling sequence ----------------- - -The caller: - -* Allocates a `struct lock_file` either as a static variable or on the - heap, initialized to zeros. Once you use the structure to call the - `hold_lock_file_*` family of functions, it belongs to the lockfile - subsystem and its storage must remain valid throughout the life of - the program (i.e. you cannot use an on-stack variable to hold this - structure). - -* Attempts to create a lockfile by passing that variable and the path - of the final destination (e.g. `$GIT_DIR/index`) to - `hold_lock_file_for_update` or `hold_lock_file_for_append`. - -* Writes new content for the destination file by either: - - * writing to the file descriptor returned by the `hold_lock_file_*` - functions (also available via `lock->fd`). - - * calling `fdopen_lock_file` to get a `FILE` pointer for the open - file and writing to the file using stdio. - -When finished writing, the caller can: - -* Close the file descriptor and rename the lockfile to its final - destination by calling `commit_lock_file` or `commit_lock_file_to`. - -* Close the file descriptor and remove the lockfile by calling - `rollback_lock_file`. - -* Close the file descriptor without removing or renaming the lockfile - by calling `close_lock_file`, and later call `commit_lock_file`, - `commit_lock_file_to`, `rollback_lock_file`, or `reopen_lock_file`. - -Even after the lockfile is committed or rolled back, the `lock_file` -object must not be freed or altered by the caller. However, it may be -reused; just pass it to another call of `hold_lock_file_for_update` or -`hold_lock_file_for_append`. - -If the program exits before you have called one of `commit_lock_file`, -`commit_lock_file_to`, `rollback_lock_file`, or `close_lock_file`, an -`atexit(3)` handler will close and remove the lockfile, rolling back -any uncommitted changes. - -If you need to close the file descriptor you obtained from a -`hold_lock_file_*` function yourself, do so by calling -`close_lock_file`. You should never call `close(2)` or `fclose(3)` -yourself! Otherwise the `struct lock_file` structure would still think -that the file descriptor needs to be closed, and a commit or rollback -would result in duplicate calls to `close(2)`. Worse yet, if you close -and then later open another file descriptor for a completely different -purpose, then a commit or rollback might close that unrelated file -descriptor. - - -Error handling --------------- - -The `hold_lock_file_*` functions return a file descriptor on success -or -1 on failure (unless `LOCK_DIE_ON_ERROR` is used; see below). On -errors, `errno` describes the reason for failure. Errors can be -reported by passing `errno` to one of the following helper functions: - -unable_to_lock_message:: - - Append an appropriate error message to a `strbuf`. - -unable_to_lock_error:: - - Emit an appropriate error message using `error()`. - -unable_to_lock_die:: - - Emit an appropriate error message and `die()`. - -Similarly, `commit_lock_file`, `commit_lock_file_to`, and -`close_lock_file` return 0 on success. On failure they set `errno` -appropriately, do their best to roll back the lockfile, and return -1. - - -Flags ------ - -The following flags can be passed to `hold_lock_file_for_update` or -`hold_lock_file_for_append`: - -LOCK_NO_DEREF:: - - Usually symbolic links in the destination path are resolved - and the lockfile is created by adding ".lock" to the resolved - path. If `LOCK_NO_DEREF` is set, then the lockfile is created - by adding ".lock" to the path argument itself. This option is - used, for example, when locking a symbolic reference, which - for backwards-compatibility reasons can be a symbolic link - containing the name of the referred-to-reference. - -LOCK_DIE_ON_ERROR:: - - If a lock is already taken for the file, `die()` with an error - message. If this option is not specified, trying to lock a - file that is already locked returns -1 to the caller. - - -The functions -------------- - -hold_lock_file_for_update:: - - Take a pointer to `struct lock_file`, the path of the file to - be locked (e.g. `$GIT_DIR/index`) and a flags argument (see - above). Attempt to create a lockfile for the destination and - return the file descriptor for writing to the file. - -hold_lock_file_for_append:: - - Like `hold_lock_file_for_update`, but before returning copy - the existing contents of the file (if any) to the lockfile and - position its write pointer at the end of the file. - -fdopen_lock_file:: - - Associate a stdio stream with the lockfile. Return NULL - (*without* rolling back the lockfile) on error. The stream is - closed automatically when `close_lock_file` is called or when - the file is committed or rolled back. - -get_locked_file_path:: - - Return the path of the file that is locked by the specified - lock_file object. The caller must free the memory. - -commit_lock_file:: - - Take a pointer to the `struct lock_file` initialized with an - earlier call to `hold_lock_file_for_update` or - `hold_lock_file_for_append`, close the file descriptor, and - rename the lockfile to its final destination. Return 0 upon - success. On failure, roll back the lock file and return -1, - with `errno` set to the value from the failing call to - `close(2)` or `rename(2)`. It is a bug to call - `commit_lock_file` for a `lock_file` object that is not - currently locked. - -commit_lock_file_to:: - - Like `commit_lock_file()`, except that it takes an explicit - `path` argument to which the lockfile should be renamed. The - `path` must be on the same filesystem as the lock file. - -rollback_lock_file:: - - Take a pointer to the `struct lock_file` initialized with an - earlier call to `hold_lock_file_for_update` or - `hold_lock_file_for_append`, close the file descriptor and - remove the lockfile. It is a NOOP to call - `rollback_lock_file()` for a `lock_file` object that has - already been committed or rolled back. - -close_lock_file:: - - Take a pointer to the `struct lock_file` initialized with an - earlier call to `hold_lock_file_for_update` or - `hold_lock_file_for_append`. Close the file descriptor (and - the file pointer if it has been opened using - `fdopen_lock_file`). Return 0 upon success. On failure to - `close(2)`, return a negative value and roll back the lock - file. Usually `commit_lock_file`, `commit_lock_file_to`, or - `rollback_lock_file` should eventually be called if - `close_lock_file` succeeds. - -reopen_lock_file:: - - Re-open a lockfile that has been closed (using - `close_lock_file`) but not yet committed or rolled back. This - can be used to implement a sequence of operations like the - following: - - * Lock file. - - * Write new contents to lockfile, then `close_lock_file` to - cause the contents to be written to disk. - - * Pass the name of the lockfile to another program to allow it - (and nobody else) to inspect the contents you wrote, while - still holding the lock yourself. - - * `reopen_lock_file` to reopen the lockfile. Make further - updates to the contents. - - * `commit_lock_file` to make the final version permanent. diff --git a/Documentation/technical/api-submodule-config.txt b/Documentation/technical/api-submodule-config.txt new file mode 100644 index 0000000000..941fa178dd --- /dev/null +++ b/Documentation/technical/api-submodule-config.txt @@ -0,0 +1,62 @@ +submodule config cache API +========================== + +The submodule config cache API allows to read submodule +configurations/information from specified revisions. Internally +information is lazily read into a cache that is used to avoid +unnecessary parsing of the same .gitmodule files. Lookups can be done by +submodule path or name. + +Usage +----- + +To initialize the cache with configurations from the worktree the caller +typically first calls `gitmodules_config()` to read values from the +worktree .gitmodules and then to overlay the local git config values +`parse_submodule_config_option()` from the config parsing +infrastructure. + +The caller can look up information about submodules by using the +`submodule_from_path()` or `submodule_from_name()` functions. They return +a `struct submodule` which contains the values. The API automatically +initializes and allocates the needed infrastructure on-demand. If the +caller does only want to lookup values from revisions the initialization +can be skipped. + +If the internal cache might grow too big or when the caller is done with +the API, all internally cached values can be freed with submodule_free(). + +Data Structures +--------------- + +`struct submodule`:: + + This structure is used to return the information about one + submodule for a certain revision. It is returned by the lookup + functions. + +Functions +--------- + +`void submodule_free()`:: + + Use these to free the internally cached values. + +`int parse_submodule_config_option(const char *var, const char *value)`:: + + Can be passed to the config parsing infrastructure to parse + local (worktree) submodule configurations. + +`const struct submodule *submodule_from_path(const unsigned char *commit_sha1, const char *path)`:: + + Lookup values for one submodule by its commit_sha1 and path. + +`const struct submodule *submodule_from_name(const unsigned char *commit_sha1, const char *name)`:: + + The same as above but lookup by name. + +If given the null_sha1 as commit_sha1 the local configuration of a +submodule will be returned (e.g. consolidated values from local git +configuration and the .gitmodules file in the worktree). + +For an example usage see test-submodule-config.c. diff --git a/Documentation/technical/index-format.txt b/Documentation/technical/index-format.txt index b7093af8b2..7392ff636c 100644 --- a/Documentation/technical/index-format.txt +++ b/Documentation/technical/index-format.txt @@ -275,7 +275,7 @@ Git index format - The directory name terminated by NUL. - - A number of untrached file/dir names terminated by NUL. + - A number of untracked file/dir names terminated by NUL. The remaining data of each directory block is grouped by type: diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index cd96820162..fc45283501 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.5.0 +DEF_VER=v2.5.0.GIT LF=' ' @@ -38,5 +38,3 @@ test "$VN" = "$VC" || { echo >&2 "GIT_VERSION = $VN" echo "GIT_VERSION = $VN" >$GVF } - - @@ -593,6 +593,7 @@ TEST_PROGRAMS_NEED_X += test-sha1 TEST_PROGRAMS_NEED_X += test-sha1-array TEST_PROGRAMS_NEED_X += test-sigchain TEST_PROGRAMS_NEED_X += test-string-list +TEST_PROGRAMS_NEED_X += test-submodule-config TEST_PROGRAMS_NEED_X += test-subprocess TEST_PROGRAMS_NEED_X += test-svn-fe TEST_PROGRAMS_NEED_X += test-urlmatch-normalization @@ -784,8 +785,10 @@ LIB_OBJS += strbuf.o LIB_OBJS += streaming.o LIB_OBJS += string-list.o LIB_OBJS += submodule.o +LIB_OBJS += submodule-config.o LIB_OBJS += symlinks.o LIB_OBJS += tag.o +LIB_OBJS += tempfile.o LIB_OBJS += trace.o LIB_OBJS += trailer.o LIB_OBJS += transport.o @@ -1697,10 +1700,10 @@ $(BUILT_INS): git$X ln -s $< $@ 2>/dev/null || \ cp $< $@ -common-cmds.h: generate-cmdlist.perl command-list.txt +common-cmds.h: generate-cmdlist.sh command-list.txt common-cmds.h: $(wildcard Documentation/git-*.txt) - $(QUIET_GEN)$(PERL_PATH) generate-cmdlist.perl command-list.txt > $@+ && mv $@+ $@ + $(QUIET_GEN)./generate-cmdlist.sh command-list.txt >$@+ && mv $@+ $@ SCRIPT_DEFINES = $(SHELL_PATH_SQ):$(DIFF_SQ):$(GIT_VERSION):\ $(localedir_SQ):$(NO_CURL):$(USE_GETTEXT_SCHEME):$(SANE_TOOL_PATH_SQ):\ @@ -1 +1 @@ -Documentation/RelNotes/2.5.0.txt
\ No newline at end of file +Documentation/RelNotes/2.6.0.txt
\ No newline at end of file @@ -5,7 +5,8 @@ char *alias_lookup(const char *alias) char *v = NULL; struct strbuf key = STRBUF_INIT; strbuf_addf(&key, "alias.%s", alias); - git_config_get_string(key.buf, &v); + if (git_config_key_is_valid(key.buf)) + git_config_get_string(key.buf, &v); strbuf_release(&key); return v; } @@ -490,6 +490,8 @@ static int git_attr_system(void) return !git_env_bool("GIT_ATTR_NOSYSTEM", 0); } +static GIT_PATH_FUNC(git_path_info_attributes, INFOATTRIBUTES_FILE) + static void bootstrap_attr_stack(void) { struct attr_stack *elem; @@ -531,7 +533,7 @@ static void bootstrap_attr_stack(void) debug_push(elem); } - elem = read_attr_from_file(git_path(INFOATTRIBUTES_FILE), 1); + elem = read_attr_from_file(git_path_info_attributes(), 1); if (!elem) elem = xcalloc(1, sizeof(*elem)); elem->origin = NULL; @@ -19,7 +19,9 @@ static struct object_id *current_bad_oid; static const char *argv_checkout[] = {"checkout", "-q", NULL, "--", NULL}; static const char *argv_show_branch[] = {"show-branch", NULL, NULL}; -static const char *argv_update_ref[] = {"update-ref", "--no-deref", "BISECT_HEAD", NULL, NULL}; + +static const char *term_bad; +static const char *term_good; /* Remember to update object flag allocation in object.h */ #define COUNTED (1u<<16) @@ -403,15 +405,21 @@ struct commit_list *find_bisection(struct commit_list *list, static int register_ref(const char *refname, const struct object_id *oid, int flags, void *cb_data) { - if (!strcmp(refname, "bad")) { + struct strbuf good_prefix = STRBUF_INIT; + strbuf_addstr(&good_prefix, term_good); + strbuf_addstr(&good_prefix, "-"); + + if (!strcmp(refname, term_bad)) { current_bad_oid = xmalloc(sizeof(*current_bad_oid)); oidcpy(current_bad_oid, oid); - } else if (starts_with(refname, "good-")) { + } else if (starts_with(refname, good_prefix.buf)) { sha1_array_append(&good_revs, oid->hash); } else if (starts_with(refname, "skip-")) { sha1_array_append(&skipped_revs, oid->hash); } + strbuf_release(&good_prefix); + return 0; } @@ -420,10 +428,13 @@ static int read_bisect_refs(void) return for_each_ref_in("refs/bisect/", register_ref, NULL); } +static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES") +static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV") + static void read_bisect_paths(struct argv_array *array) { struct strbuf str = STRBUF_INIT; - const char *filename = git_path("BISECT_NAMES"); + const char *filename = git_path_bisect_names(); FILE *fp = fopen(filename, "r"); if (!fp) @@ -634,7 +645,7 @@ static void exit_if_skipped_commits(struct commit_list *tried, return; printf("There are only 'skip'ped commits left to test.\n" - "The first bad commit could be any of:\n"); + "The first %s commit could be any of:\n", term_bad); print_commit_list(tried, "%s\n", "%s\n"); if (bad) printf("%s\n", oid_to_hex(bad)); @@ -644,7 +655,7 @@ static void exit_if_skipped_commits(struct commit_list *tried, static int is_expected_rev(const struct object_id *oid) { - const char *filename = git_path("BISECT_EXPECTED_REV"); + const char *filename = git_path_bisect_expected_rev(); struct stat st; struct strbuf str = STRBUF_INIT; FILE *fp; @@ -666,34 +677,16 @@ static int is_expected_rev(const struct object_id *oid) return res; } -static void mark_expected_rev(char *bisect_rev_hex) -{ - int len = strlen(bisect_rev_hex); - const char *filename = git_path("BISECT_EXPECTED_REV"); - int fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600); - - if (fd < 0) - die_errno("could not create file '%s'", filename); - - bisect_rev_hex[len] = '\n'; - write_or_die(fd, bisect_rev_hex, len + 1); - bisect_rev_hex[len] = '\0'; - - if (close(fd) < 0) - die("closing file %s: %s", filename, strerror(errno)); -} - -static int bisect_checkout(char *bisect_rev_hex, int no_checkout) +static int bisect_checkout(const unsigned char *bisect_rev, int no_checkout) { + char bisect_rev_hex[GIT_SHA1_HEXSZ + 1]; - mark_expected_rev(bisect_rev_hex); + memcpy(bisect_rev_hex, sha1_to_hex(bisect_rev), GIT_SHA1_HEXSZ + 1); + update_ref(NULL, "BISECT_EXPECTED_REV", bisect_rev, NULL, 0, UPDATE_REFS_DIE_ON_ERR); argv_checkout[2] = bisect_rev_hex; if (no_checkout) { - argv_update_ref[3] = bisect_rev_hex; - if (run_command_v_opt(argv_update_ref, RUN_GIT_CMD)) - die("update-ref --no-deref HEAD failed on %s", - bisect_rev_hex); + update_ref(NULL, "BISECT_HEAD", bisect_rev, NULL, 0, UPDATE_REFS_DIE_ON_ERR); } else { int res; res = run_command_v_opt(argv_checkout, RUN_GIT_CMD); @@ -732,18 +725,24 @@ static void handle_bad_merge_base(void) if (is_expected_rev(current_bad_oid)) { char *bad_hex = oid_to_hex(current_bad_oid); char *good_hex = join_sha1_array_hex(&good_revs, ' '); - - fprintf(stderr, "The merge base %s is bad.\n" - "This means the bug has been fixed " - "between %s and [%s].\n", - bad_hex, bad_hex, good_hex); - + if (!strcmp(term_bad, "bad") && !strcmp(term_good, "good")) { + fprintf(stderr, "The merge base %s is bad.\n" + "This means the bug has been fixed " + "between %s and [%s].\n", + bad_hex, bad_hex, good_hex); + } else { + fprintf(stderr, "The merge base %s is %s.\n" + "This means the first '%s' commit is " + "between %s and [%s].\n", + bad_hex, term_bad, term_good, bad_hex, good_hex); + } exit(3); } - fprintf(stderr, "Some good revs are not ancestor of the bad rev.\n" + fprintf(stderr, "Some %s revs are not ancestor of the %s rev.\n" "git bisect cannot work properly in this case.\n" - "Maybe you mistake good and bad revs?\n"); + "Maybe you mistook %s and %s revs?\n", + term_good, term_bad, term_good, term_bad); exit(1); } @@ -755,10 +754,10 @@ static void handle_skipped_merge_base(const unsigned char *mb) warning("the merge base between %s and [%s] " "must be skipped.\n" - "So we cannot be sure the first bad commit is " + "So we cannot be sure the first %s commit is " "between %s and %s.\n" "We continue anyway.", - bad_hex, good_hex, mb_hex, bad_hex); + bad_hex, good_hex, term_bad, mb_hex, bad_hex); free(good_hex); } @@ -789,7 +788,7 @@ static void check_merge_bases(int no_checkout) handle_skipped_merge_base(mb); } else { printf("Bisecting: a merge base must be tested\n"); - exit(bisect_checkout(sha1_to_hex(mb), no_checkout)); + exit(bisect_checkout(mb, no_checkout)); } } @@ -839,7 +838,7 @@ static void check_good_are_ancestors_of_bad(const char *prefix, int no_checkout) int fd; if (!current_bad_oid) - die("a bad revision is needed"); + die("a %s revision is needed", term_bad); /* Check if file BISECT_ANCESTORS_OK exists. */ if (!stat(filename, &st) && S_ISREG(st.st_mode)) @@ -890,6 +889,36 @@ static void show_diff_tree(const char *prefix, struct commit *commit) } /* + * The terms used for this bisect session are stored in BISECT_TERMS. + * We read them and store them to adapt the messages accordingly. + * Default is bad/good. + */ +void read_bisect_terms(const char **read_bad, const char **read_good) +{ + struct strbuf str = STRBUF_INIT; + const char *filename = git_path("BISECT_TERMS"); + FILE *fp = fopen(filename, "r"); + + if (!fp) { + if (errno == ENOENT) { + *read_bad = "bad"; + *read_good = "good"; + return; + } else { + die("could not read file '%s': %s", filename, + strerror(errno)); + } + } else { + strbuf_getline(&str, fp, '\n'); + *read_bad = strbuf_detach(&str, NULL); + strbuf_getline(&str, fp, '\n'); + *read_good = strbuf_detach(&str, NULL); + } + strbuf_release(&str); + fclose(fp); +} + +/* * We use the convention that exiting with an exit code 10 means that * the bisection process finished successfully. * In this case the calling shell script should exit 0. @@ -903,8 +932,8 @@ int bisect_next_all(const char *prefix, int no_checkout) struct commit_list *tried; int reaches = 0, all = 0, nr, steps; const unsigned char *bisect_rev; - char bisect_rev_hex[GIT_SHA1_HEXSZ + 1]; + read_bisect_terms(&term_bad, &term_good); if (read_bisect_refs()) die("reading bisect refs failed"); @@ -926,8 +955,10 @@ int bisect_next_all(const char *prefix, int no_checkout) */ exit_if_skipped_commits(tried, NULL); - printf("%s was both good and bad\n", - oid_to_hex(current_bad_oid)); + printf("%s was both %s and %s\n", + oid_to_hex(current_bad_oid), + term_good, + term_bad); exit(1); } @@ -938,11 +969,11 @@ int bisect_next_all(const char *prefix, int no_checkout) } bisect_rev = revs.commits->item->object.sha1; - memcpy(bisect_rev_hex, sha1_to_hex(bisect_rev), GIT_SHA1_HEXSZ + 1); if (!hashcmp(bisect_rev, current_bad_oid->hash)) { exit_if_skipped_commits(tried, current_bad_oid); - printf("%s is the first bad commit\n", bisect_rev_hex); + printf("%s is the first %s commit\n", sha1_to_hex(bisect_rev), + term_bad); show_diff_tree(prefix, revs.commits->item); /* This means the bisection process succeeded. */ exit(10); @@ -954,7 +985,7 @@ int bisect_next_all(const char *prefix, int no_checkout) "(roughly %d step%s)\n", nr, (nr == 1 ? "" : "s"), steps, (steps == 1 ? "" : "s")); - return bisect_checkout(bisect_rev_hex, no_checkout); + return bisect_checkout(bisect_rev, no_checkout); } static inline int log2i(int n) @@ -26,4 +26,6 @@ extern int bisect_next_all(const char *prefix, int no_checkout); extern int estimate_bisect_steps(int all); +extern void read_bisect_terms(const char **bad, const char **good); + #endif @@ -302,11 +302,100 @@ void create_branch(const char *head, void remove_branch_state(void) { - unlink(git_path("CHERRY_PICK_HEAD")); - unlink(git_path("REVERT_HEAD")); - unlink(git_path("MERGE_HEAD")); - unlink(git_path("MERGE_RR")); - unlink(git_path("MERGE_MSG")); - unlink(git_path("MERGE_MODE")); - unlink(git_path("SQUASH_MSG")); + unlink(git_path_cherry_pick_head()); + unlink(git_path_revert_head()); + unlink(git_path_merge_head()); + unlink(git_path_merge_rr()); + unlink(git_path_merge_msg()); + unlink(git_path_merge_mode()); + unlink(git_path_squash_msg()); +} + +static char *find_linked_symref(const char *symref, const char *branch, + const char *id) +{ + struct strbuf sb = STRBUF_INIT; + struct strbuf path = STRBUF_INIT; + struct strbuf gitdir = STRBUF_INIT; + char *existing = NULL; + + /* + * $GIT_COMMON_DIR/$symref (e.g. HEAD) is practically outside + * $GIT_DIR so resolve_ref_unsafe() won't work (it uses + * git_path). Parse the ref ourselves. + */ + if (id) + strbuf_addf(&path, "%s/worktrees/%s/%s", get_git_common_dir(), id, symref); + else + strbuf_addf(&path, "%s/%s", get_git_common_dir(), symref); + + if (!strbuf_readlink(&sb, path.buf, 0)) { + if (!starts_with(sb.buf, "refs/") || + check_refname_format(sb.buf, 0)) + goto done; + } else if (strbuf_read_file(&sb, path.buf, 0) >= 0 && + starts_with(sb.buf, "ref:")) { + strbuf_remove(&sb, 0, strlen("ref:")); + strbuf_trim(&sb); + } else + goto done; + if (strcmp(sb.buf, branch)) + goto done; + if (id) { + strbuf_reset(&path); + strbuf_addf(&path, "%s/worktrees/%s/gitdir", get_git_common_dir(), id); + if (strbuf_read_file(&gitdir, path.buf, 0) <= 0) + goto done; + strbuf_rtrim(&gitdir); + } else + strbuf_addstr(&gitdir, get_git_common_dir()); + strbuf_strip_suffix(&gitdir, ".git"); + + existing = strbuf_detach(&gitdir, NULL); +done: + strbuf_release(&path); + strbuf_release(&sb); + strbuf_release(&gitdir); + + return existing; +} + +char *find_shared_symref(const char *symref, const char *target) +{ + struct strbuf path = STRBUF_INIT; + DIR *dir; + struct dirent *d; + char *existing; + + if ((existing = find_linked_symref(symref, target, NULL))) + return existing; + + strbuf_addf(&path, "%s/worktrees", get_git_common_dir()); + dir = opendir(path.buf); + strbuf_release(&path); + if (!dir) + return NULL; + + while ((d = readdir(dir)) != NULL) { + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + existing = find_linked_symref(symref, target, d->d_name); + if (existing) + goto done; + } +done: + closedir(dir); + + return existing; +} + +void die_if_checked_out(const char *branch) +{ + char *existing; + + existing = find_shared_symref("HEAD", branch); + if (existing) { + skip_prefix(branch, "refs/heads/", &branch); + die(_("'%s' is already checked out at '%s'"), branch, existing); + } } @@ -52,4 +52,19 @@ extern void install_branch_config(int flag, const char *local, const char *origi */ extern int read_branch_desc(struct strbuf *, const char *branch_name); +/* + * Check if a branch is checked out in the main worktree or any linked + * worktree and die (with a message describing its checkout location) if + * it is. + */ +extern void die_if_checked_out(const char *branch); + +/* + * Check if a per-worktree symref points to a ref in the main worktree + * or any linked worktree, and return the path to the exising worktree + * if it is. Returns NULL if there is no existing ref. The caller is + * responsible for freeing the returned path. + */ +extern char *find_shared_symref(const char *symref, const char *target); + #endif diff --git a/builtin/add.c b/builtin/add.c index 4bd98b799e..b2a5c57f0a 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -375,7 +375,6 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (add_new_files) { int baselen; - struct pathspec empty_pathspec; /* Set up the default git porcelain excludes */ memset(&dir, 0, sizeof(dir)); @@ -384,7 +383,6 @@ int cmd_add(int argc, const char **argv, const char *prefix) setup_standard_excludes(&dir); } - memset(&empty_pathspec, 0, sizeof(empty_pathspec)); /* This picks up the paths that are not tracked */ baselen = fill_directory(&dir, &pathspec); if (pathspec.nr) diff --git a/builtin/am.c b/builtin/am.c index 486ff594d7..27165a6730 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -10,6 +10,7 @@ #include "dir.h" #include "run-command.h" #include "quote.h" +#include "tempfile.h" #include "lockfile.h" #include "cache-tree.h" #include "refs.h" @@ -1976,15 +1977,48 @@ static int fast_forward_to(struct tree *head, struct tree *remote, int reset) } /** + * Merges a tree into the index. The index's stat info will take precedence + * over the merged tree's. Returns 0 on success, -1 on failure. + */ +static int merge_tree(struct tree *tree) +{ + struct lock_file *lock_file; + struct unpack_trees_options opts; + struct tree_desc t[1]; + + if (parse_tree(tree)) + return -1; + + lock_file = xcalloc(1, sizeof(struct lock_file)); + hold_locked_index(lock_file, 1); + + memset(&opts, 0, sizeof(opts)); + opts.head_idx = 1; + opts.src_index = &the_index; + opts.dst_index = &the_index; + opts.merge = 1; + opts.fn = oneway_merge; + init_tree_desc(&t[0], tree->buffer, tree->size); + + if (unpack_trees(1, t, &opts)) { + rollback_lock_file(lock_file); + return -1; + } + + if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) + die(_("unable to write new index file")); + + return 0; +} + +/** * Clean the index without touching entries that are not modified between * `head` and `remote`. */ static int clean_index(const unsigned char *head, const unsigned char *remote) { - struct lock_file *lock_file; struct tree *head_tree, *remote_tree, *index_tree; unsigned char index[GIT_SHA1_RAWSZ]; - struct pathspec pathspec; head_tree = parse_tree_indirect(head); if (!head_tree) @@ -2009,18 +2043,8 @@ static int clean_index(const unsigned char *head, const unsigned char *remote) if (fast_forward_to(index_tree, remote_tree, 0)) return -1; - memset(&pathspec, 0, sizeof(pathspec)); - - lock_file = xcalloc(1, sizeof(struct lock_file)); - hold_locked_index(lock_file, 1); - - if (read_tree(remote_tree, 0, &pathspec)) { - rollback_lock_file(lock_file); + if (merge_tree(remote_tree)) return -1; - } - - if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) - die(_("unable to write new index file")); remove_branch_state(); @@ -2181,7 +2205,7 @@ int cmd_am(int argc, const char **argv, const char *prefix) OPT_BOOL('i', "interactive", &state.interactive, N_("run interactively")), OPT_HIDDEN_BOOL('b', "binary", &binary, - N_("(historical option -- no-op")), + N_("historical option -- no-op")), OPT_BOOL('3', "3way", &state.threeway, N_("allow fall back on 3way merging if needed")), OPT__QUIET(&state.quiet, N_("be quiet")), diff --git a/builtin/blame.c b/builtin/blame.c index 04e4864f8b..4db01c195c 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -2227,20 +2227,19 @@ static struct commit_list **append_parent(struct commit_list **tail, const unsig static void append_merge_parents(struct commit_list **tail) { int merge_head; - const char *merge_head_file = git_path("MERGE_HEAD"); struct strbuf line = STRBUF_INIT; - merge_head = open(merge_head_file, O_RDONLY); + merge_head = open(git_path_merge_head(), O_RDONLY); if (merge_head < 0) { if (errno == ENOENT) return; - die("cannot open '%s' for reading", merge_head_file); + die("cannot open '%s' for reading", git_path_merge_head()); } while (!strbuf_getwholeline_fd(&line, merge_head, '\n')) { unsigned char sha1[20]; if (line.len < 40 || get_sha1_hex(line.buf, sha1)) - die("unknown line in '%s': %s", merge_head_file, line.buf); + die("unknown line in '%s': %s", git_path_merge_head(), line.buf); tail = append_parent(tail, sha1); } close(merge_head); diff --git a/builtin/checkout.c b/builtin/checkout.c index f71844a23a..bc703c0f5e 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -18,6 +18,7 @@ #include "xdiff-interface.h" #include "ll-merge.h" #include "resolve-undo.h" +#include "submodule-config.h" #include "submodule.h" static const char * const checkout_usage[] = { @@ -48,8 +49,6 @@ struct checkout_opts { const char *prefix; struct pathspec pathspec; struct tree *source_tree; - - int new_worktree_mode; }; static int post_checkout_hook(struct commit *old, struct commit *new, @@ -282,7 +281,7 @@ static int checkout_paths(const struct checkout_opts *opts, if (opts->source_tree) read_tree_some(opts->source_tree, &opts->pathspec); - ps_matched = xcalloc(1, opts->pathspec.nr); + ps_matched = xcalloc(opts->pathspec.nr, 1); /* * Make sure all pathspecs participated in locating the paths @@ -509,7 +508,7 @@ static int merge_working_tree(const struct checkout_opts *opts, topts.dir->flags |= DIR_SHOW_IGNORED; setup_standard_excludes(topts.dir); } - tree = parse_tree_indirect(old->commit && !opts->new_worktree_mode ? + tree = parse_tree_indirect(old->commit ? old->commit->object.sha1 : EMPTY_TREE_SHA1_BIN); init_tree_desc(&trees[0], tree->buffer, tree->size); @@ -612,22 +611,20 @@ static void update_refs_for_switch(const struct checkout_opts *opts, if (opts->new_branch) { if (opts->new_orphan_branch) { if (opts->new_branch_log && !log_all_ref_updates) { - int temp; - struct strbuf log_file = STRBUF_INIT; int ret; - const char *ref_name; - - ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch); - temp = log_all_ref_updates; - log_all_ref_updates = 1; - ret = log_ref_setup(ref_name, &log_file); - log_all_ref_updates = temp; - strbuf_release(&log_file); + char *refname; + struct strbuf err = STRBUF_INIT; + + refname = mkpathdup("refs/heads/%s", opts->new_orphan_branch); + ret = safe_create_reflog(refname, 1, &err); + free(refname); if (ret) { - fprintf(stderr, _("Can not do reflog for '%s'\n"), - opts->new_orphan_branch); + fprintf(stderr, _("Can not do reflog for '%s': %s\n"), + opts->new_orphan_branch, err.buf); + strbuf_release(&err); return; } + strbuf_release(&err); } } else @@ -832,8 +829,7 @@ static int switch_branches(const struct checkout_opts *opts, return ret; } - if (!opts->quiet && !old.path && old.commit && - new->commit != old.commit && !opts->new_worktree_mode) + if (!opts->quiet && !old.path && old.commit && new->commit != old.commit) orphaned_commit_warning(old.commit, new->commit); update_refs_for_switch(opts, &old, new); @@ -898,71 +894,6 @@ static const char *unique_tracking_name(const char *name, unsigned char *sha1) return NULL; } -static void check_linked_checkout(struct branch_info *new, const char *id) -{ - struct strbuf sb = STRBUF_INIT; - struct strbuf path = STRBUF_INIT; - struct strbuf gitdir = STRBUF_INIT; - const char *start, *end; - - if (id) - strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id); - else - strbuf_addf(&path, "%s/HEAD", get_git_common_dir()); - - if (strbuf_read_file(&sb, path.buf, 0) < 0 || - !skip_prefix(sb.buf, "ref:", &start)) - goto done; - while (isspace(*start)) - start++; - end = start; - while (*end && !isspace(*end)) - end++; - if (strncmp(start, new->path, end - start) || new->path[end - start] != '\0') - goto done; - if (id) { - strbuf_reset(&path); - strbuf_addf(&path, "%s/worktrees/%s/gitdir", get_git_common_dir(), id); - if (strbuf_read_file(&gitdir, path.buf, 0) <= 0) - goto done; - strbuf_rtrim(&gitdir); - } else - strbuf_addstr(&gitdir, get_git_common_dir()); - die(_("'%s' is already checked out at '%s'"), new->name, gitdir.buf); -done: - strbuf_release(&path); - strbuf_release(&sb); - strbuf_release(&gitdir); -} - -static void check_linked_checkouts(struct branch_info *new) -{ - struct strbuf path = STRBUF_INIT; - DIR *dir; - struct dirent *d; - - strbuf_addf(&path, "%s/worktrees", get_git_common_dir()); - if ((dir = opendir(path.buf)) == NULL) { - strbuf_release(&path); - return; - } - - /* - * $GIT_COMMON_DIR/HEAD is practically outside - * $GIT_DIR so resolve_ref_unsafe() won't work (it - * uses git_path). Parse the ref ourselves. - */ - check_linked_checkout(new, NULL); - - while ((d = readdir(dir)) != NULL) { - if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) - continue; - check_linked_checkout(new, d->d_name); - } - strbuf_release(&path); - closedir(dir); -} - static int parse_branchname_arg(int argc, const char **argv, int dwim_new_local_branch_ok, struct branch_info *new, @@ -1170,14 +1101,14 @@ static int checkout_branch(struct checkout_opts *opts, die(_("Cannot switch branch to a non-commit '%s'"), new->name); - if (new->path && !opts->force_detach && !opts->new_branch) { + if (new->path && !opts->force_detach && !opts->new_branch && + !opts->ignore_other_worktrees) { unsigned char sha1[20]; int flag; char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag); if (head_ref && - (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)) && - !opts->ignore_other_worktrees) - check_linked_checkouts(new); + (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path))) + die_if_checked_out(new->path); free(head_ref); } @@ -1241,8 +1172,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, checkout_usage, PARSE_OPT_KEEP_DASHDASH); - opts.new_worktree_mode = getenv("GIT_CHECKOUT_NEW_WORKTREE") != NULL; - if (conflict_style) { opts.merge = 1; /* implied */ git_xmerge_config("merge.conflictstyle", conflict_style, NULL); diff --git a/builtin/clone.c b/builtin/clone.c index 303a3a7eb6..578da85254 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -99,82 +99,140 @@ static const char *argv_submodule[] = { "submodule", "update", "--init", "--recursive", NULL }; -static char *get_repo_path(const char *repo, int *is_bundle) +static const char *get_repo_path_1(struct strbuf *path, int *is_bundle) { static char *suffix[] = { "/.git", "", ".git/.git", ".git" }; static char *bundle_suffix[] = { ".bundle", "" }; + size_t baselen = path->len; struct stat st; int i; for (i = 0; i < ARRAY_SIZE(suffix); i++) { - const char *path; - path = mkpath("%s%s", repo, suffix[i]); - if (stat(path, &st)) + strbuf_setlen(path, baselen); + strbuf_addstr(path, suffix[i]); + if (stat(path->buf, &st)) continue; - if (S_ISDIR(st.st_mode) && is_git_directory(path)) { + if (S_ISDIR(st.st_mode) && is_git_directory(path->buf)) { *is_bundle = 0; - return xstrdup(absolute_path(path)); + return path->buf; } else if (S_ISREG(st.st_mode) && st.st_size > 8) { /* Is it a "gitfile"? */ char signature[8]; - int len, fd = open(path, O_RDONLY); + const char *dst; + int len, fd = open(path->buf, O_RDONLY); if (fd < 0) continue; len = read_in_full(fd, signature, 8); close(fd); if (len != 8 || strncmp(signature, "gitdir: ", 8)) continue; - path = read_gitfile(path); - if (path) { + dst = read_gitfile(path->buf); + if (dst) { *is_bundle = 0; - return xstrdup(absolute_path(path)); + return dst; } } } for (i = 0; i < ARRAY_SIZE(bundle_suffix); i++) { - const char *path; - path = mkpath("%s%s", repo, bundle_suffix[i]); - if (!stat(path, &st) && S_ISREG(st.st_mode)) { + strbuf_setlen(path, baselen); + strbuf_addstr(path, bundle_suffix[i]); + if (!stat(path->buf, &st) && S_ISREG(st.st_mode)) { *is_bundle = 1; - return xstrdup(absolute_path(path)); + return path->buf; } } return NULL; } +static char *get_repo_path(const char *repo, int *is_bundle) +{ + struct strbuf path = STRBUF_INIT; + const char *raw; + char *canon; + + strbuf_addstr(&path, repo); + raw = get_repo_path_1(&path, is_bundle); + canon = raw ? xstrdup(absolute_path(raw)) : NULL; + strbuf_release(&path); + return canon; +} + static char *guess_dir_name(const char *repo, int is_bundle, int is_bare) { - const char *end = repo + strlen(repo), *start; + const char *end = repo + strlen(repo), *start, *ptr; size_t len; char *dir; /* + * Skip scheme. + */ + start = strstr(repo, "://"); + if (start == NULL) + start = repo; + else + start += 3; + + /* + * Skip authentication data. The stripping does happen + * greedily, such that we strip up to the last '@' inside + * the host part. + */ + for (ptr = start; ptr < end && !is_dir_sep(*ptr); ptr++) { + if (*ptr == '@') + start = ptr + 1; + } + + /* * Strip trailing spaces, slashes and /.git */ - while (repo < end && (is_dir_sep(end[-1]) || isspace(end[-1]))) + while (start < end && (is_dir_sep(end[-1]) || isspace(end[-1]))) end--; - if (end - repo > 5 && is_dir_sep(end[-5]) && + if (end - start > 5 && is_dir_sep(end[-5]) && !strncmp(end - 4, ".git", 4)) { end -= 5; - while (repo < end && is_dir_sep(end[-1])) + while (start < end && is_dir_sep(end[-1])) end--; } /* - * Find last component, but be prepared that repo could have - * the form "remote.example.com:foo.git", i.e. no slash - * in the directory part. + * Strip trailing port number if we've got only a + * hostname (that is, there is no dir separator but a + * colon). This check is required such that we do not + * strip URI's like '/foo/bar:2222.git', which should + * result in a dir '2222' being guessed due to backwards + * compatibility. */ - start = end; - while (repo < start && !is_dir_sep(start[-1]) && start[-1] != ':') - start--; + if (memchr(start, '/', end - start) == NULL + && memchr(start, ':', end - start) != NULL) { + ptr = end; + while (start < ptr && isdigit(ptr[-1]) && ptr[-1] != ':') + ptr--; + if (start < ptr && ptr[-1] == ':') + end = ptr - 1; + } + + /* + * Find last component. To remain backwards compatible we + * also regard colons as path separators, such that + * cloning a repository 'foo:bar.git' would result in a + * directory 'bar' being guessed. + */ + ptr = end; + while (start < ptr && !is_dir_sep(ptr[-1]) && ptr[-1] != ':') + ptr--; + start = ptr; /* * Strip .{bundle,git}. */ - strip_suffix(start, is_bundle ? ".bundle" : ".git" , &len); + len = end - start; + strip_suffix_mem(start, &len, is_bundle ? ".bundle" : ".git"); + + if (!len || (len == 1 && *start == '/')) + die("No directory name could be guessed.\n" + "Please specify a directory on the command line"); if (is_bare) dir = xstrfmt("%.*s.git", (int)len, start); diff --git a/builtin/commit.c b/builtin/commit.c index 7314f33885..b37cb6c8b7 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -166,9 +166,9 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset) static void determine_whence(struct wt_status *s) { - if (file_exists(git_path("MERGE_HEAD"))) + if (file_exists(git_path_merge_head())) whence = FROM_MERGE; - else if (file_exists(git_path("CHERRY_PICK_HEAD"))) { + else if (file_exists(git_path_cherry_pick_head())) { whence = FROM_CHERRY_PICK; if (file_exists(git_path(SEQ_DIR))) sequencer_in_use = 1; @@ -324,6 +324,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix struct string_list partial; struct pathspec pathspec; int refresh_flags = REFRESH_QUIET; + const char *ret; if (is_status) refresh_flags |= REFRESH_UNMERGED; @@ -344,7 +345,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix die(_("unable to create temporary index")); old_index_env = getenv(INDEX_ENVIRONMENT); - setenv(INDEX_ENVIRONMENT, index_lock.filename.buf, 1); + setenv(INDEX_ENVIRONMENT, get_lock_file_path(&index_lock), 1); if (interactive_add(argc, argv, prefix, patch_interactive) != 0) die(_("interactive add failed")); @@ -355,7 +356,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix unsetenv(INDEX_ENVIRONMENT); discard_cache(); - read_cache_from(index_lock.filename.buf); + read_cache_from(get_lock_file_path(&index_lock)); if (update_main_cache_tree(WRITE_TREE_SILENT) == 0) { if (reopen_lock_file(&index_lock) < 0) die(_("unable to write index file")); @@ -365,7 +366,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix warning(_("Failed to update main cache tree")); commit_style = COMMIT_NORMAL; - return index_lock.filename.buf; + return get_lock_file_path(&index_lock); } /* @@ -388,7 +389,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK)) die(_("unable to write new_index file")); commit_style = COMMIT_NORMAL; - return index_lock.filename.buf; + return get_lock_file_path(&index_lock); } /* @@ -475,9 +476,9 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix die(_("unable to write temporary index file")); discard_cache(); - read_cache_from(false_lock.filename.buf); - - return false_lock.filename.buf; + ret = get_lock_file_path(&false_lock); + read_cache_from(ret); + return ret; } static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn, @@ -725,12 +726,12 @@ static int prepare_to_commit(const char *index_file, const char *prefix, format_commit_message(commit, "fixup! %s\n\n", &sb, &ctx); hook_arg1 = "message"; - } else if (!stat(git_path("MERGE_MSG"), &statbuf)) { - if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0) + } else if (!stat(git_path_merge_msg(), &statbuf)) { + if (strbuf_read_file(&sb, git_path_merge_msg(), 0) < 0) die_errno(_("could not read MERGE_MSG")); hook_arg1 = "merge"; - } else if (!stat(git_path("SQUASH_MSG"), &statbuf)) { - if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0) + } else if (!stat(git_path_squash_msg(), &statbuf)) { + if (strbuf_read_file(&sb, git_path_squash_msg(), 0) < 0) die_errno(_("could not read SQUASH_MSG")); hook_arg1 = "squash"; } else if (template_file) { @@ -1684,10 +1685,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (!reflog_msg) reflog_msg = "commit (merge)"; pptr = &commit_list_insert(current_head, pptr)->next; - fp = fopen(git_path("MERGE_HEAD"), "r"); + fp = fopen(git_path_merge_head(), "r"); if (fp == NULL) die_errno(_("could not open '%s' for reading"), - git_path("MERGE_HEAD")); + git_path_merge_head()); while (strbuf_getline(&m, fp, '\n') != EOF) { struct commit *parent; @@ -1698,8 +1699,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) } fclose(fp); strbuf_release(&m); - if (!stat(git_path("MERGE_MODE"), &statbuf)) { - if (strbuf_read_file(&sb, git_path("MERGE_MODE"), 0) < 0) + if (!stat(git_path_merge_mode(), &statbuf)) { + if (strbuf_read_file(&sb, git_path_merge_mode(), 0) < 0) die_errno(_("could not read MERGE_MODE")); if (!strcmp(sb.buf, "no-ff")) allow_fast_forward = 0; @@ -1775,12 +1776,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix) } ref_transaction_free(transaction); - unlink(git_path("CHERRY_PICK_HEAD")); - unlink(git_path("REVERT_HEAD")); - unlink(git_path("MERGE_HEAD")); - unlink(git_path("MERGE_MSG")); - unlink(git_path("MERGE_MODE")); - unlink(git_path("SQUASH_MSG")); + unlink(git_path_cherry_pick_head()); + unlink(git_path_revert_head()); + unlink(git_path_merge_head()); + unlink(git_path_merge_msg()); + unlink(git_path_merge_mode()); + unlink(git_path_squash_msg()); if (commit_index_files()) die (_("Repository has been updated, but unable to write\n" diff --git a/builtin/config.c b/builtin/config.c index 7188405f7e..71acc44143 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -13,6 +13,7 @@ static char *key; static regex_t *key_regexp; static regex_t *regexp; static int show_keys; +static int omit_values; static int use_key_regexp; static int do_all; static int do_not_match; @@ -78,6 +79,7 @@ static struct option builtin_config_options[] = { OPT_BIT(0, "path", &types, N_("value is a path (file or directory name)"), TYPE_PATH), OPT_GROUP(N_("Other")), OPT_BOOL('z', "null", &end_null, N_("terminate values with NUL byte")), + OPT_BOOL(0, "name-only", &omit_values, N_("show variable names only")), OPT_BOOL(0, "includes", &respect_includes, N_("respect include directives on lookup")), OPT_END(), }; @@ -91,7 +93,7 @@ static void check_argc(int argc, int min, int max) { static int show_all_config(const char *key_, const char *value_, void *cb) { - if (value_) + if (!omit_values && value_) printf("%s%c%s%c", key_, delim, value_, term); else printf("%s%c", key_, term); @@ -106,48 +108,40 @@ struct strbuf_list { static int format_config(struct strbuf *buf, const char *key_, const char *value_) { - int must_free_vptr = 0; - int must_print_delim = 0; - char value[256]; - const char *vptr = value; - - strbuf_init(buf, 0); - - if (show_keys) { + if (show_keys) strbuf_addstr(buf, key_); - must_print_delim = 1; - } - if (types == TYPE_INT) - sprintf(value, "%"PRId64, - git_config_int64(key_, value_ ? value_ : "")); - else if (types == TYPE_BOOL) - vptr = git_config_bool(key_, value_) ? "true" : "false"; - else if (types == TYPE_BOOL_OR_INT) { - int is_bool, v; - v = git_config_bool_or_int(key_, value_, &is_bool); - if (is_bool) - vptr = v ? "true" : "false"; - else - sprintf(value, "%d", v); - } else if (types == TYPE_PATH) { - if (git_config_pathname(&vptr, key_, value_) < 0) - return -1; - must_free_vptr = 1; - } else if (value_) { - vptr = value_; - } else { - /* Just show the key name */ - vptr = ""; - must_print_delim = 0; - } + if (!omit_values) { + if (show_keys) + strbuf_addch(buf, key_delim); - if (must_print_delim) - strbuf_addch(buf, key_delim); - strbuf_addstr(buf, vptr); + if (types == TYPE_INT) + strbuf_addf(buf, "%"PRId64, + git_config_int64(key_, value_ ? value_ : "")); + else if (types == TYPE_BOOL) + strbuf_addstr(buf, git_config_bool(key_, value_) ? + "true" : "false"); + else if (types == TYPE_BOOL_OR_INT) { + int is_bool, v; + v = git_config_bool_or_int(key_, value_, &is_bool); + if (is_bool) + strbuf_addstr(buf, v ? "true" : "false"); + else + strbuf_addf(buf, "%d", v); + } else if (types == TYPE_PATH) { + const char *v; + if (git_config_pathname(&v, key_, value_) < 0) + return -1; + strbuf_addstr(buf, v); + free((char *)v); + } else if (value_) { + strbuf_addstr(buf, value_); + } else { + /* Just show the key name; back out delimiter */ + if (show_keys) + strbuf_setlen(buf, buf->len - 1); + } + } strbuf_addch(buf, term); - - if (must_free_vptr) - free((char *)vptr); return 0; } @@ -164,6 +158,7 @@ static int collect_config(const char *key_, const char *value_, void *cb) return 0; ALLOC_GROW(values->items, values->nr + 1, values->alloc); + strbuf_init(&values->items[values->nr], 0); return format_config(&values->items[values->nr++], key_, value_); } @@ -430,14 +425,11 @@ static int get_urlmatch(const char *var, const char *url) for_each_string_list_item(item, &values) { struct urlmatch_current_candidate_value *matched = item->util; - struct strbuf key = STRBUF_INIT; struct strbuf buf = STRBUF_INIT; - strbuf_addstr(&key, item->string); - format_config(&buf, key.buf, + format_config(&buf, item->string, matched->value_is_null ? NULL : matched->value.buf); fwrite(buf.buf, 1, buf.len, stdout); - strbuf_release(&key); strbuf_release(&buf); strbuf_release(&matched->value); @@ -549,7 +541,11 @@ int cmd_config(int argc, const char **argv, const char *prefix) default: usage_with_options(builtin_config_usage, builtin_config_options); } - + if (omit_values && + !(actions == ACTION_LIST || actions == ACTION_GET_REGEXP)) { + error("--name-only is only applicable to --list or --get-regexp"); + usage_with_options(builtin_config_usage, builtin_config_options); + } if (actions == ACTION_LIST) { check_argc(argc, 0, 0); if (git_config_with_options(show_all_config, NULL, diff --git a/builtin/fetch.c b/builtin/fetch.c index 34b6f5ebca..9a3869f4ff 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -11,6 +11,7 @@ #include "run-command.h" #include "parse-options.h" #include "sigchain.h" +#include "submodule-config.h" #include "submodule.h" #include "connected.h" #include "argv-array.h" @@ -591,7 +592,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, const char *what, *kind; struct ref *rm; char *url; - const char *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD"); + const char *filename = dry_run ? "/dev/null" : git_path_fetch_head(); int want_status; fp = fopen(filename, "a"); @@ -834,7 +835,7 @@ static void check_not_current_branch(struct ref *ref_map) static int truncate_fetch_head(void) { - const char *filename = git_path("FETCH_HEAD"); + const char *filename = git_path_fetch_head(); FILE *fp = fopen(filename, "w"); if (!fp) @@ -988,17 +989,15 @@ static int get_remote_group(const char *key, const char *value, void *priv) { struct remote_group_data *g = priv; - if (starts_with(key, "remotes.") && - !strcmp(key + 8, g->name)) { + if (skip_prefix(key, "remotes.", &key) && !strcmp(key, g->name)) { /* split list by white space */ - int space = strcspn(value, " \t\n"); while (*value) { - if (space > 1) { + size_t wordlen = strcspn(value, " \t\n"); + + if (wordlen >= 1) string_list_append(g->list, - xstrndup(value, space)); - } - value += space + (value[space] != '\0'); - space = strcspn(value, " \t\n"); + xstrndup(value, wordlen)); + value += wordlen + (value[wordlen] != '\0'); } } diff --git a/builtin/fsck.c b/builtin/fsck.c index f4b87e9b33..079470342f 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -243,13 +243,14 @@ static void check_unreachable_object(struct object *obj) printf("dangling %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1)); if (write_lost_and_found) { - const char *filename = git_path("lost-found/%s/%s", + char *filename = git_pathdup("lost-found/%s/%s", obj->type == OBJ_COMMIT ? "commit" : "other", sha1_to_hex(obj->sha1)); FILE *f; if (safe_create_leading_directories_const(filename)) { error("Could not create lost-found"); + free(filename); return; } if (!(f = fopen(filename, "w"))) @@ -262,6 +263,7 @@ static void check_unreachable_object(struct object *obj) if (fclose(f)) die_errno("Could not finish '%s'", filename); + free(filename); } return; } diff --git a/builtin/gc.c b/builtin/gc.c index 4957c39032..0ad8d30b56 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -11,6 +11,7 @@ */ #include "builtin.h" +#include "tempfile.h" #include "lockfile.h" #include "parse-options.h" #include "run-command.h" @@ -42,20 +43,7 @@ static struct argv_array prune = ARGV_ARRAY_INIT; static struct argv_array prune_worktrees = ARGV_ARRAY_INIT; static struct argv_array rerere = ARGV_ARRAY_INIT; -static char *pidfile; - -static void remove_pidfile(void) -{ - if (pidfile) - unlink(pidfile); -} - -static void remove_pidfile_on_signal(int signo) -{ - remove_pidfile(); - sigchain_pop(signo); - raise(signo); -} +static struct tempfile pidfile; static void git_config_date_string(const char *key, const char **output) { @@ -85,7 +73,7 @@ static void gc_config(void) git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit); git_config_get_bool("gc.autodetach", &detach_auto); git_config_date_string("gc.pruneexpire", &prune_expire); - git_config_date_string("gc.pruneworktreesexpire", &prune_worktrees_expire); + git_config_date_string("gc.worktreepruneexpire", &prune_worktrees_expire); git_config(git_default_config, NULL); } @@ -199,20 +187,22 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) uintmax_t pid; FILE *fp; int fd; + char *pidfile_path; - if (pidfile) + if (is_tempfile_active(&pidfile)) /* already locked */ return NULL; if (gethostname(my_host, sizeof(my_host))) strcpy(my_host, "unknown"); - fd = hold_lock_file_for_update(&lock, git_path("gc.pid"), + pidfile_path = git_pathdup("gc.pid"); + fd = hold_lock_file_for_update(&lock, pidfile_path, LOCK_DIE_ON_ERROR); if (!force) { static char locking_host[128]; int should_exit; - fp = fopen(git_path("gc.pid"), "r"); + fp = fopen(pidfile_path, "r"); memset(locking_host, 0, sizeof(locking_host)); should_exit = fp != NULL && @@ -236,6 +226,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) if (fd >= 0) rollback_lock_file(&lock); *ret_pid = pid; + free(pidfile_path); return locking_host; } } @@ -245,11 +236,8 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) write_in_full(fd, sb.buf, sb.len); strbuf_release(&sb); commit_lock_file(&lock); - - pidfile = git_pathdup("gc.pid"); - sigchain_push_common(remove_pidfile_on_signal); - atexit(remove_pidfile); - + register_tempfile(&pidfile, pidfile_path); + free(pidfile_path); return NULL; } diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 3f10840441..3431de2362 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -1421,7 +1421,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name, get_object_directory(), sha1_to_hex(sha1)); final_pack_name = name; } - if (move_temp_to_file(curr_pack_name, final_pack_name)) + if (finalize_object_file(curr_pack_name, final_pack_name)) die(_("cannot store pack file")); } else if (from_stdin) chmod(final_pack_name, 0444); @@ -1432,7 +1432,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name, get_object_directory(), sha1_to_hex(sha1)); final_index_name = name; } - if (move_temp_to_file(curr_index_name, final_index_name)) + if (finalize_object_file(curr_index_name, final_index_name)) die(_("cannot store index file")); } else chmod(final_index_name, 0444); diff --git a/builtin/log.c b/builtin/log.c index b50ef7510c..a491d3dea0 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -342,8 +342,7 @@ static int cmd_log_walk(struct rev_info *rev) * retain that state information if replacing rev->diffopt in this loop */ while ((commit = get_revision(rev)) != NULL) { - if (!log_tree_commit(rev, commit) && - rev->max_count >= 0) + if (!log_tree_commit(rev, commit) && rev->max_count >= 0) /* * We decremented max_count in get_revision, * but we didn't actually show the commit. @@ -504,7 +503,8 @@ static int show_tree_object(const unsigned char *sha1, return 0; } -static void show_rev_tweak_rev(struct rev_info *rev, struct setup_revision_opt *opt) +static void show_setup_revisions_tweak(struct rev_info *rev, + struct setup_revision_opt *opt) { if (rev->ignore_merges) { /* There was no "-m" on the command line */ @@ -539,7 +539,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) memset(&opt, 0, sizeof(opt)); opt.def = "HEAD"; - opt.tweak = show_rev_tweak_rev; + opt.tweak = show_setup_revisions_tweak; cmd_log_init(argc, argv, prefix, &rev, &opt); if (!rev.no_walk) @@ -626,12 +626,20 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) return cmd_log_walk(&rev); } -static void default_follow_tweak(struct rev_info *rev, - struct setup_revision_opt *opt) +static void log_setup_revisions_tweak(struct rev_info *rev, + struct setup_revision_opt *opt) { if (DIFF_OPT_TST(&rev->diffopt, DEFAULT_FOLLOW_RENAMES) && rev->prune_data.nr == 1) DIFF_OPT_SET(&rev->diffopt, FOLLOW_RENAMES); + + /* Turn --cc/-c into -p --cc/-c when -p was not given */ + if (!rev->diffopt.output_format && rev->combine_merges) + rev->diffopt.output_format = DIFF_FORMAT_PATCH; + + /* Turn -m on when --cc/-c was given */ + if (rev->combine_merges) + rev->ignore_merges = 0; } int cmd_log(int argc, const char **argv, const char *prefix) @@ -647,7 +655,7 @@ int cmd_log(int argc, const char **argv, const char *prefix) memset(&opt, 0, sizeof(opt)); opt.def = "HEAD"; opt.revarg_opt = REVARG_COMMITTISH; - opt.tweak = default_follow_tweak; + opt.tweak = log_setup_revisions_tweak; cmd_log_init(argc, argv, prefix, &rev, &opt); return cmd_log_walk(&rev); } @@ -1455,8 +1463,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) continue; } - if (ignore_if_in_upstream && - has_commit_patch_id(commit, &ids)) + if (ignore_if_in_upstream && has_commit_patch_id(commit, &ids)) continue; nr++; diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 6fa2205734..b6a7cb0c7c 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -516,7 +516,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) /* Treat unmatching pathspec elements as errors */ if (pathspec.nr && error_unmatch) - ps_matched = xcalloc(1, pathspec.nr); + ps_matched = xcalloc(pathspec.nr, 1); if ((dir.flags & DIR_SHOW_IGNORED) && !exc_given) die("ls-files --ignored needs some exclude pattern"); diff --git a/builtin/merge.c b/builtin/merge.c index 85c54dcd5a..a0edacab20 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -231,9 +231,9 @@ static struct option builtin_merge_options[] = { /* Cleans up metadata that is uninteresting after a succeeded merge. */ static void drop_save(void) { - unlink(git_path("MERGE_HEAD")); - unlink(git_path("MERGE_MSG")); - unlink(git_path("MERGE_MODE")); + unlink(git_path_merge_head()); + unlink(git_path_merge_msg()); + unlink(git_path_merge_mode()); } static int save_state(unsigned char *stash) @@ -338,7 +338,7 @@ static void squash_message(struct commit *commit, struct commit_list *remotehead struct pretty_print_context ctx = {0}; printf(_("Squash commit -- not updating HEAD\n")); - filename = git_path("SQUASH_MSG"); + filename = git_path_squash_msg(); fd = open(filename, O_WRONLY | O_CREAT, 0666); if (fd < 0) die_errno(_("Could not write to '%s'"), filename); @@ -754,7 +754,7 @@ static void add_strategies(const char *string, unsigned attr) static void write_merge_msg(struct strbuf *msg) { - const char *filename = git_path("MERGE_MSG"); + const char *filename = git_path_merge_msg(); int fd = open(filename, O_WRONLY | O_CREAT, 0666); if (fd < 0) die_errno(_("Could not open '%s' for writing"), @@ -766,7 +766,7 @@ static void write_merge_msg(struct strbuf *msg) static void read_merge_msg(struct strbuf *msg) { - const char *filename = git_path("MERGE_MSG"); + const char *filename = git_path_merge_msg(); strbuf_reset(msg); if (strbuf_read_file(msg, filename, 0) < 0) die_errno(_("Could not read from '%s'"), filename); @@ -799,10 +799,10 @@ static void prepare_to_commit(struct commit_list *remoteheads) strbuf_commented_addf(&msg, _(merge_editor_comment), comment_line_char); write_merge_msg(&msg); if (run_commit_hook(0 < option_edit, get_index_file(), "prepare-commit-msg", - git_path("MERGE_MSG"), "merge", NULL)) + git_path_merge_msg(), "merge", NULL)) abort_commit(remoteheads, NULL); if (0 < option_edit) { - if (launch_editor(git_path("MERGE_MSG"), NULL, NULL)) + if (launch_editor(git_path_merge_msg(), NULL, NULL)) abort_commit(remoteheads, NULL); } read_merge_msg(&msg); @@ -865,7 +865,7 @@ static int suggest_conflicts(void) FILE *fp; struct strbuf msgbuf = STRBUF_INIT; - filename = git_path("MERGE_MSG"); + filename = git_path_merge_msg(); fp = fopen(filename, "a"); if (!fp) die_errno(_("Could not open '%s' for writing"), filename); @@ -967,7 +967,7 @@ static void write_merge_state(struct commit_list *remoteheads) } strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1)); } - filename = git_path("MERGE_HEAD"); + filename = git_path_merge_head(); fd = open(filename, O_WRONLY | O_CREAT, 0666); if (fd < 0) die_errno(_("Could not open '%s' for writing"), filename); @@ -977,7 +977,7 @@ static void write_merge_state(struct commit_list *remoteheads) strbuf_addch(&merge_msg, '\n'); write_merge_msg(&merge_msg); - filename = git_path("MERGE_MODE"); + filename = git_path_merge_mode(); fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd < 0) die_errno(_("Could not open '%s' for writing"), filename); @@ -1070,7 +1070,7 @@ static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge if (!merge_names) merge_names = &fetch_head_file; - filename = git_path("FETCH_HEAD"); + filename = git_path_fetch_head(); fd = open(filename, O_RDONLY); if (fd < 0) die_errno(_("could not open '%s' for reading"), filename); @@ -1204,7 +1204,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) int nargc = 2; const char *nargv[] = {"reset", "--merge", NULL}; - if (!file_exists(git_path("MERGE_HEAD"))) + if (!file_exists(git_path_merge_head())) die(_("There is no merge to abort (MERGE_HEAD missing).")); /* Invoke 'git reset --merge' */ @@ -1215,7 +1215,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (read_cache_unmerged()) die_resolve_conflict("merge"); - if (file_exists(git_path("MERGE_HEAD"))) { + if (file_exists(git_path_merge_head())) { /* * There is no unmerged entry, don't advise 'git * add/rm <file>', just 'git commit'. @@ -1226,7 +1226,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) else die(_("You have not concluded your merge (MERGE_HEAD exists).")); } - if (file_exists(git_path("CHERRY_PICK_HEAD"))) { + if (file_exists(git_path_cherry_pick_head())) { if (advice_resolve_conflict) die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n" "Please, commit your changes before you merge.")); diff --git a/builtin/notes.c b/builtin/notes.c index 63f95fc554..0423480827 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -19,6 +19,7 @@ #include "string-list.h" #include "notes-merge.h" #include "notes-utils.h" +#include "branch.h" static const char * const git_notes_usage[] = { N_("git notes [--ref <notes-ref>] [list [<object>]]"), @@ -825,10 +826,15 @@ static int merge(int argc, const char **argv, const char *prefix) update_ref(msg.buf, default_notes_ref(), result_sha1, NULL, 0, UPDATE_REFS_DIE_ON_ERR); else { /* Merge has unresolved conflicts */ + char *existing; /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */ update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL, 0, UPDATE_REFS_DIE_ON_ERR); /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */ + existing = find_shared_symref("NOTES_MERGE_REF", default_notes_ref()); + if (existing) + die(_("A notes merge into %s is already in-progress at %s"), + default_notes_ref(), existing); if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL)) die("Failed to store link to current notes ref (%s)", default_notes_ref()); diff --git a/builtin/pull.c b/builtin/pull.c index 722a83c51b..7e3c11ea63 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -15,6 +15,7 @@ #include "dir.h" #include "refs.h" #include "revision.h" +#include "tempfile.h" #include "lockfile.h" enum rebase_type { @@ -823,10 +824,14 @@ int cmd_pull(int argc, const char **argv, const char *prefix) hashclr(orig_head); if (opt_rebase) { + int autostash = 0; + if (is_null_sha1(orig_head) && !is_cache_unborn()) die(_("Updating an unborn branch with changes added to the index.")); - die_on_unclean_work_tree(prefix); + git_config_get_bool("rebase.autostash", &autostash); + if (!autostash) + die_on_unclean_work_tree(prefix); if (get_rebase_fork_point(rebase_fork_point, repo, *refspecs)) hashclr(rebase_fork_point); diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 4ced2eba3c..e6b93d0264 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -933,7 +933,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) return "deletion prohibited"; } - if (!strcmp(namespaced_name, head_name)) { + if (head_name && !strcmp(namespaced_name, head_name)) { switch (deny_delete_current) { case DENY_IGNORE: break; diff --git a/builtin/reflog.c b/builtin/reflog.c index c2eb8ff840..7ed0e8501c 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -13,6 +13,8 @@ static const char reflog_expire_usage[] = "git reflog expire [--expire=<time>] [--expire-unreachable=<time>] [--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] [--verbose] [--all] <refs>..."; static const char reflog_delete_usage[] = "git reflog delete [--rewrite] [--updateref] [--dry-run | -n] [--verbose] <refs>..."; +static const char reflog_exists_usage[] = +"git reflog exists <ref>"; static unsigned long default_reflog_expire; static unsigned long default_reflog_expire_unreachable; @@ -699,12 +701,38 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) return status; } +static int cmd_reflog_exists(int argc, const char **argv, const char *prefix) +{ + int i, start = 0; + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (!strcmp(arg, "--")) { + i++; + break; + } + else if (arg[0] == '-') + usage(reflog_exists_usage); + else + break; + } + + start = i; + + if (argc - start != 1) + usage(reflog_exists_usage); + + if (check_refname_format(argv[start], REFNAME_ALLOW_ONELEVEL)) + die("invalid ref format: %s", argv[start]); + return !reflog_exists(argv[start]); +} + /* * main "reflog" */ static const char reflog_usage[] = -"git reflog [ show | expire | delete ]"; +"git reflog [ show | expire | delete | exists ]"; int cmd_reflog(int argc, const char **argv, const char *prefix) { @@ -724,5 +752,8 @@ int cmd_reflog(int argc, const char **argv, const char *prefix) if (!strcmp(argv[1], "delete")) return cmd_reflog_delete(argc - 1, argv + 1, prefix); + if (!strcmp(argv[1], "exists")) + return cmd_reflog_exists(argc - 1, argv + 1, prefix); + return cmd_log_reflog(argc, argv, prefix); } diff --git a/builtin/remote.c b/builtin/remote.c index cc3c7410f3..181668dedd 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -581,7 +581,6 @@ static int migrate_file(struct remote *remote) { struct strbuf buf = STRBUF_INIT; int i; - const char *path = NULL; strbuf_addf(&buf, "remote.%s.url", remote->name); for (i = 0; i < remote->url_nr; i++) @@ -601,11 +600,9 @@ static int migrate_file(struct remote *remote) return error(_("Could not append '%s' to '%s'"), remote->fetch_refspec[i], buf.buf); if (remote->origin == REMOTE_REMOTES) - path = git_path("remotes/%s", remote->name); + unlink_or_warn(git_path("remotes/%s", remote->name)); else if (remote->origin == REMOTE_BRANCHES) - path = git_path("branches/%s", remote->name); - if (path) - unlink_or_warn(path); + unlink_or_warn(git_path("branches/%s", remote->name)); return 0; } diff --git a/builtin/repack.c b/builtin/repack.c index af7340c7ba..70b9b1eaf1 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -285,8 +285,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) failed = 0; for_each_string_list_item(item, &names) { for (ext = 0; ext < ARRAY_SIZE(exts); ext++) { - const char *fname_old; - char *fname; + char *fname, *fname_old; fname = mkpathdup("%s/pack-%s%s", packdir, item->string, exts[ext].name); if (!file_exists(fname)) { @@ -294,7 +293,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) continue; } - fname_old = mkpath("%s/old-%s%s", packdir, + fname_old = mkpathdup("%s/old-%s%s", packdir, item->string, exts[ext].name); if (file_exists(fname_old)) if (unlink(fname_old)) @@ -302,10 +301,12 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (!failed && rename(fname, fname_old)) { free(fname); + free(fname_old); failed = 1; break; } else { string_list_append(&rollback, fname); + free(fname_old); } } if (failed) @@ -314,13 +315,13 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (failed) { struct string_list rollback_failure = STRING_LIST_INIT_DUP; for_each_string_list_item(item, &rollback) { - const char *fname_old; - char *fname; + char *fname, *fname_old; fname = mkpathdup("%s/%s", packdir, item->string); - fname_old = mkpath("%s/old-%s", packdir, item->string); + fname_old = mkpathdup("%s/old-%s", packdir, item->string); if (rename(fname_old, fname)) string_list_append(&rollback_failure, fname); free(fname); + free(fname_old); } if (rollback_failure.nr) { @@ -368,13 +369,14 @@ int cmd_repack(int argc, const char **argv, const char *prefix) /* Remove the "old-" files */ for_each_string_list_item(item, &names) { for (ext = 0; ext < ARRAY_SIZE(exts); ext++) { - const char *fname; - fname = mkpath("%s/old-%s%s", - packdir, - item->string, - exts[ext].name); + char *fname; + fname = mkpathdup("%s/old-%s%s", + packdir, + item->string, + exts[ext].name); if (remove_path(fname)) warning(_("removing '%s' failed"), fname); + free(fname); } } diff --git a/builtin/reset.c b/builtin/reset.c index 4c08ddc1ca..c503e75a59 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -36,7 +36,7 @@ static const char *reset_type_names[] = { static inline int is_merge(void) { - return !access(git_path("MERGE_HEAD"), F_OK); + return !access(git_path_merge_head(), F_OK); } static int reset_index(const unsigned char *sha1, int reset_type, int quiet) diff --git a/builtin/rev-list.c b/builtin/rev-list.c index c0b4b53652..d80d1ed359 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -350,6 +350,9 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) revs.diff) usage(rev_list_usage); + if (revs.show_notes) + die(_("rev-list does not support display of notes")); + save_commit_buffer = (revs.verbose_header || revs.grep_filter.pattern_list || revs.grep_filter.header_list); diff --git a/builtin/send-pack.c b/builtin/send-pack.c index b961e5ae78..23b2962cb0 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -11,6 +11,7 @@ #include "transport.h" #include "version.h" #include "sha1-array.h" +#include "gpg-interface.h" static const char send_pack_usage[] = "git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] [<host>:]<directory> [<ref>...]\n" @@ -113,6 +114,8 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) int from_stdin = 0; struct push_cas_option cas = {0}; + git_config(git_gpg_config, NULL); + argv++; for (i = 1; i < argc; i++, argv++) { const char *arg = *argv; diff --git a/builtin/tag.c b/builtin/tag.c index 5f6cdc5a03..cccca99104 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -579,6 +579,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) struct create_tag_options opt; char *cleanup_arg = NULL; int annotate = 0, force = 0, lines = -1; + int create_reflog = 0; int cmdmode = 0; const char *msgfile = NULL, *keyid = NULL; struct msg_arg msg = { 0, STRBUF_INIT }; @@ -605,6 +606,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT_STRING('u', "local-user", &keyid, N_("key-id"), N_("use another key to sign the tag")), OPT__FORCE(&force, N_("replace the tag if exists")), + OPT_BOOL(0, "create-reflog", &create_reflog, N_("create_reflog")), OPT_GROUP(N_("Tag listing options")), OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")), @@ -733,7 +735,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_update(transaction, ref.buf, object, prev, - 0, NULL, &err) || + create_reflog ? REF_FORCE_CREATE_REFLOG : 0, + NULL, &err) || ref_transaction_commit(transaction, &err)) die("%s", err.buf); ref_transaction_free(transaction); diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 6763cf1837..04dd00f734 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -14,6 +14,7 @@ static const char * const git_update_ref_usage[] = { static char line_termination = '\n'; static int update_flags; +static unsigned create_reflog_flag; static const char *msg; /* @@ -200,7 +201,8 @@ static const char *parse_cmd_update(struct ref_transaction *transaction, if (ref_transaction_update(transaction, refname, new_sha1, have_old ? old_sha1 : NULL, - update_flags, msg, &err)) + update_flags | create_reflog_flag, + msg, &err)) die("%s", err.buf); update_flags = 0; @@ -231,7 +233,8 @@ static const char *parse_cmd_create(struct ref_transaction *transaction, die("create %s: extra input: %s", refname, next); if (ref_transaction_create(transaction, refname, new_sha1, - update_flags, msg, &err)) + update_flags | create_reflog_flag, + msg, &err)) die("%s", err.buf); update_flags = 0; @@ -354,6 +357,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) unsigned char sha1[20], oldsha1[20]; int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0; unsigned int flags = 0; + int create_reflog = 0; struct option options[] = { OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")), OPT_BOOL('d', NULL, &delete, N_("delete the reference")), @@ -361,6 +365,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) N_("update <refname> not the one it points to")), OPT_BOOL('z', NULL, &end_null, N_("stdin has NUL-terminated arguments")), OPT_BOOL( 0 , "stdin", &read_stdin, N_("read updates from stdin")), + OPT_BOOL( 0 , "create-reflog", &create_reflog, N_("create_reflog")), OPT_END(), }; @@ -370,6 +375,8 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) if (msg && !*msg) die("Refusing to perform update with empty message."); + create_reflog_flag = create_reflog ? REF_FORCE_CREATE_REFLOG : 0; + if (read_stdin) { struct strbuf err = STRBUF_INIT; struct ref_transaction *transaction; @@ -431,5 +438,6 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) flags); else return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL, - flags, UPDATE_REFS_DIE_ON_ERR); + flags | create_reflog_flag, + UPDATE_REFS_DIE_ON_ERR); } diff --git a/builtin/worktree.c b/builtin/worktree.c index bbb169a5d0..71bb770f7a 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -3,6 +3,8 @@ #include "dir.h" #include "parse-options.h" #include "argv-array.h" +#include "branch.h" +#include "refs.h" #include "run-command.h" #include "sigchain.h" #include "refs.h" @@ -13,6 +15,13 @@ static const char * const worktree_usage[] = { NULL }; +struct add_opts { + int force; + int detach; + const char *new_branch; + int force_new_branch; +}; + static int show_only; static int verbose; static unsigned long expire; @@ -172,19 +181,35 @@ static const char *worktree_basename(const char *path, int *olen) return name; } -static int add_worktree(const char *path, const char **child_argv) +static int add_worktree(const char *path, const char *refname, + const struct add_opts *opts) { struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT; struct strbuf sb = STRBUF_INIT; const char *name; struct stat st; struct child_process cp; + struct argv_array child_env = ARGV_ARRAY_INIT; int counter = 0, len, ret; - unsigned char rev[20]; + struct strbuf symref = STRBUF_INIT; + struct commit *commit = NULL; if (file_exists(path) && !is_empty_dir(path)) die(_("'%s' already exists"), path); + /* is 'refname' a branch or commit? */ + if (opts->force_new_branch) /* definitely a branch */ + ; + else if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) && + ref_exists(symref.buf)) { /* it's a branch */ + if (!opts->force) + die_if_checked_out(symref.buf); + } else { /* must be a commit */ + commit = lookup_commit_reference_by_name(refname); + if (!commit) + die(_("invalid reference: %s"), refname); + } + name = worktree_basename(path, &len); strbuf_addstr(&sb_repo, git_path("worktrees/%.*s", (int)(path + len - name), name)); @@ -228,32 +253,40 @@ static int add_worktree(const char *path, const char **child_argv) real_path(get_git_common_dir()), name); /* * This is to keep resolve_ref() happy. We need a valid HEAD - * or is_git_directory() will reject the directory. Moreover, HEAD - * in the new worktree must resolve to the same value as HEAD in - * the current tree since the command invoked to populate the new - * worktree will be handed the branch/ref specified by the user. - * For instance, if the user asks for the new worktree to be based - * at HEAD~5, then the resolved HEAD~5 in the new worktree must - * match the resolved HEAD~5 in the current tree in order to match - * the user's expectation. + * or is_git_directory() will reject the directory. Any value which + * looks like an object ID will do since it will be immediately + * replaced by the symbolic-ref or update-ref invocation in the new + * worktree. */ - if (!resolve_ref_unsafe("HEAD", 0, rev, NULL)) - die(_("unable to resolve HEAD")); strbuf_reset(&sb); strbuf_addf(&sb, "%s/HEAD", sb_repo.buf); - write_file(sb.buf, "%s", sha1_to_hex(rev)); + write_file(sb.buf, "0000000000000000000000000000000000000000"); strbuf_reset(&sb); strbuf_addf(&sb, "%s/commondir", sb_repo.buf); write_file(sb.buf, "../.."); - fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name); + fprintf_ln(stderr, _("Preparing %s (identifier %s)"), path, name); - setenv("GIT_CHECKOUT_NEW_WORKTREE", "1", 1); - setenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1); - setenv(GIT_WORK_TREE_ENVIRONMENT, path, 1); + argv_array_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf); + argv_array_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path); memset(&cp, 0, sizeof(cp)); cp.git_cmd = 1; - cp.argv = child_argv; + + if (commit) + argv_array_pushl(&cp.args, "update-ref", "HEAD", + sha1_to_hex(commit->object.sha1), NULL); + else + argv_array_pushl(&cp.args, "symbolic-ref", "HEAD", + symref.buf, NULL); + cp.env = child_env.argv; + ret = run_command(&cp); + if (ret) + goto done; + + cp.argv = NULL; + argv_array_clear(&cp.args); + argv_array_pushl(&cp.args, "reset", "--hard", NULL); + cp.env = child_env.argv; ret = run_command(&cp); if (!ret) { is_junk = 0; @@ -262,10 +295,13 @@ static int add_worktree(const char *path, const char **child_argv) junk_work_tree = NULL; junk_git_dir = NULL; } +done: strbuf_reset(&sb); strbuf_addf(&sb, "%s/locked", sb_repo.buf); unlink_or_warn(sb.buf); + argv_array_clear(&child_env); strbuf_release(&sb); + strbuf_release(&symref); strbuf_release(&sb_repo); strbuf_release(&sb_git); return ret; @@ -273,47 +309,54 @@ static int add_worktree(const char *path, const char **child_argv) static int add(int ac, const char **av, const char *prefix) { - int force = 0, detach = 0; - const char *new_branch = NULL, *new_branch_force = NULL; + struct add_opts opts; + const char *new_branch_force = NULL; const char *path, *branch; - struct argv_array cmd = ARGV_ARRAY_INIT; struct option options[] = { - OPT__FORCE(&force, N_("checkout <branch> even if already checked out in other worktree")), - OPT_STRING('b', NULL, &new_branch, N_("branch"), + OPT__FORCE(&opts.force, N_("checkout <branch> even if already checked out in other worktree")), + OPT_STRING('b', NULL, &opts.new_branch, N_("branch"), N_("create a new branch")), OPT_STRING('B', NULL, &new_branch_force, N_("branch"), N_("create or reset a branch")), - OPT_BOOL(0, "detach", &detach, N_("detach HEAD at named commit")), + OPT_BOOL(0, "detach", &opts.detach, N_("detach HEAD at named commit")), OPT_END() }; + memset(&opts, 0, sizeof(opts)); ac = parse_options(ac, av, prefix, options, worktree_usage, 0); - if (new_branch && new_branch_force) - die(_("-b and -B are mutually exclusive")); + if (!!opts.detach + !!opts.new_branch + !!new_branch_force > 1) + die(_("-b, -B, and --detach are mutually exclusive")); if (ac < 1 || ac > 2) usage_with_options(worktree_usage, options); path = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0]; branch = ac < 2 ? "HEAD" : av[1]; - if (ac < 2 && !new_branch && !new_branch_force) { + opts.force_new_branch = !!new_branch_force; + if (opts.force_new_branch) + opts.new_branch = new_branch_force; + + if (ac < 2 && !opts.new_branch && !opts.detach) { int n; const char *s = worktree_basename(path, &n); - new_branch = xstrndup(s, n); + opts.new_branch = xstrndup(s, n); + } + + if (opts.new_branch) { + struct child_process cp; + memset(&cp, 0, sizeof(cp)); + cp.git_cmd = 1; + argv_array_push(&cp.args, "branch"); + if (opts.force_new_branch) + argv_array_push(&cp.args, "--force"); + argv_array_push(&cp.args, opts.new_branch); + argv_array_push(&cp.args, branch); + if (run_command(&cp)) + return -1; + branch = opts.new_branch; } - argv_array_push(&cmd, "checkout"); - if (force) - argv_array_push(&cmd, "--ignore-other-worktrees"); - if (new_branch) - argv_array_pushl(&cmd, "-b", new_branch, NULL); - if (new_branch_force) - argv_array_pushl(&cmd, "-B", new_branch_force, NULL); - if (detach) - argv_array_push(&cmd, "--detach"); - argv_array_push(&cmd, branch); - - return add_worktree(path, cmd.argv); + return add_worktree(path, branch, &opts); } int cmd_worktree(int ac, const char **av, const char *prefix) @@ -235,7 +235,9 @@ out: return result; } -static int write_pack_data(int bundle_fd, struct lock_file *lock, struct rev_info *revs) + +/* Write the pack data to bundle_fd, then close it if it is > 1. */ +static int write_pack_data(int bundle_fd, struct rev_info *revs) { struct child_process pack_objects = CHILD_PROCESS_INIT; int i; @@ -250,13 +252,6 @@ static int write_pack_data(int bundle_fd, struct lock_file *lock, struct rev_inf if (start_command(&pack_objects)) return error(_("Could not spawn pack-objects")); - /* - * start_command closed bundle_fd if it was > 1 - * so set the lock fd to -1 so commit_lock_file() - * won't fail trying to close it. - */ - lock->fd = -1; - for (i = 0; i < revs->pending.nr; i++) { struct object *object = revs->pending.objects[i].item; if (object->flags & UNINTERESTING) @@ -416,10 +411,21 @@ int create_bundle(struct bundle_header *header, const char *path, bundle_to_stdout = !strcmp(path, "-"); if (bundle_to_stdout) bundle_fd = 1; - else + else { bundle_fd = hold_lock_file_for_update(&lock, path, LOCK_DIE_ON_ERROR); + /* + * write_pack_data() will close the fd passed to it, + * but commit_lock_file() will also try to close the + * lockfile's fd. So make a copy of the file + * descriptor to avoid trying to close it twice. + */ + bundle_fd = dup(bundle_fd); + if (bundle_fd < 0) + die_errno("unable to dup file descriptor"); + } + /* write signature */ write_or_die(bundle_fd, bundle_signature, strlen(bundle_signature)); @@ -445,7 +451,7 @@ int create_bundle(struct bundle_header *header, const char *path, return -1; /* write pack */ - if (write_pack_data(bundle_fd, &lock, &revs)) + if (write_pack_data(bundle_fd, &revs)) return -1; if (!bundle_to_stdout) { @@ -708,23 +708,60 @@ extern int check_repository_format(void); #define DATA_CHANGED 0x0020 #define TYPE_CHANGED 0x0040 +/* + * Return a statically allocated filename, either generically (mkpath), in + * the repository directory (git_path), or in a submodule's repository + * directory (git_path_submodule). In all cases, note that the result + * may be overwritten by another call to _any_ of the functions. Consider + * using the safer "dup" or "strbuf" formats below (in some cases, the + * unsafe versions have already been removed). + */ +extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2))); +extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2))); + extern char *mksnpath(char *buf, size_t n, const char *fmt, ...) __attribute__((format (printf, 3, 4))); extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...) __attribute__((format (printf, 2, 3))); +extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path, + const char *fmt, ...) + __attribute__((format (printf, 3, 4))); extern char *git_pathdup(const char *fmt, ...) __attribute__((format (printf, 1, 2))); extern char *mkpathdup(const char *fmt, ...) __attribute__((format (printf, 1, 2))); - -/* Return a statically allocated filename matching the sha1 signature */ -extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2))); -extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2))); -extern const char *git_path_submodule(const char *path, const char *fmt, ...) +extern char *git_pathdup_submodule(const char *path, const char *fmt, ...) __attribute__((format (printf, 2, 3))); + extern void report_linked_checkout_garbage(void); /* + * You can define a static memoized git path like: + * + * static GIT_PATH_FUNC(git_path_foo, "FOO"); + * + * or use one of the global ones below. + */ +#define GIT_PATH_FUNC(func, filename) \ + const char *func(void) \ + { \ + static char *ret; \ + if (!ret) \ + ret = git_pathdup(filename); \ + return ret; \ + } + +const char *git_path_cherry_pick_head(void); +const char *git_path_revert_head(void); +const char *git_path_squash_msg(void); +const char *git_path_merge_msg(void); +const char *git_path_merge_rr(void); +const char *git_path_merge_mode(void); +const char *git_path_merge_head(void); +const char *git_path_fetch_head(void); +const char *git_path_shallow(void); + +/* * Return the name of the file in the local object database that would * be used to store a loose object with the specified sha1. The * return value is a pointer to a statically allocated buffer that is @@ -945,7 +982,7 @@ extern int do_check_packed_object_crc; extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type); -extern int move_temp_to_file(const char *tmpfile, const char *filename); +extern int finalize_object_file(const char *tmpfile, const char *filename); extern int has_sha1_pack(const unsigned char *sha1); @@ -1403,6 +1440,7 @@ extern int git_config_pathname(const char **, const char *, const char *); extern int git_config_set_in_file(const char *, const char *, const char *); extern int git_config_set(const char *, const char *); extern int git_config_parse_key(const char *, char **, int *); +extern int git_config_key_is_valid(const char *key); extern int git_config_set_multivar(const char *, const char *, const char *, int); extern int git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int); extern int git_config_rename_section(const char *, const char *); diff --git a/compat/mingw.c b/compat/mingw.c index 496e6f8bb0..f74da235f5 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -681,7 +681,7 @@ int pipe(int filedes[2]) return -1; } filedes[1] = _open_osfhandle((int)h[1], O_NOINHERIT); - if (filedes[0] < 0) { + if (filedes[1] < 0) { close(filedes[0]); CloseHandle(h[1]); return -1; @@ -1848,7 +1848,7 @@ int git_config_set(const char *key, const char *value) * baselen - pointer to int which will hold the length of the * section + subsection part, can be NULL */ -int git_config_parse_key(const char *key, char **store_key, int *baselen_) +static int git_config_parse_key_1(const char *key, char **store_key, int *baselen_, int quiet) { int i, dot, baselen; const char *last_dot = strrchr(key, '.'); @@ -1859,12 +1859,14 @@ int git_config_parse_key(const char *key, char **store_key, int *baselen_) */ if (last_dot == NULL || last_dot == key) { - error("key does not contain a section: %s", key); + if (!quiet) + error("key does not contain a section: %s", key); return -CONFIG_NO_SECTION_OR_NAME; } if (!last_dot[1]) { - error("key does not contain variable name: %s", key); + if (!quiet) + error("key does not contain variable name: %s", key); return -CONFIG_NO_SECTION_OR_NAME; } @@ -1875,7 +1877,8 @@ int git_config_parse_key(const char *key, char **store_key, int *baselen_) /* * Validate the key and while at it, lower case it for matching. */ - *store_key = xmalloc(strlen(key) + 1); + if (store_key) + *store_key = xmalloc(strlen(key) + 1); dot = 0; for (i = 0; key[i]; i++) { @@ -1886,26 +1889,42 @@ int git_config_parse_key(const char *key, char **store_key, int *baselen_) if (!dot || i > baselen) { if (!iskeychar(c) || (i == baselen + 1 && !isalpha(c))) { - error("invalid key: %s", key); + if (!quiet) + error("invalid key: %s", key); goto out_free_ret_1; } c = tolower(c); } else if (c == '\n') { - error("invalid key (newline): %s", key); + if (!quiet) + error("invalid key (newline): %s", key); goto out_free_ret_1; } - (*store_key)[i] = c; + if (store_key) + (*store_key)[i] = c; } - (*store_key)[i] = 0; + if (store_key) + (*store_key)[i] = 0; return 0; out_free_ret_1: - free(*store_key); - *store_key = NULL; + if (store_key) { + free(*store_key); + *store_key = NULL; + } return -CONFIG_INVALID_KEY; } +int git_config_parse_key(const char *key, char **store_key, int *baselen) +{ + return git_config_parse_key_1(key, store_key, baselen, 0); +} + +int git_config_key_is_valid(const char *key) +{ + return !git_config_parse_key_1(key, NULL, NULL, 1); +} + /* * If value==NULL, unset in (remove from) config, * if value_regex!=NULL, disregard key/value pairs where value does not match. @@ -1935,7 +1954,7 @@ int git_config_set_multivar_in_file(const char *config_filename, const char *key, const char *value, const char *value_regex, int multi_replace) { - int fd = -1, in_fd; + int fd = -1, in_fd = -1; int ret; struct lock_file *lock = NULL; char *filename_buf = NULL; @@ -2065,10 +2084,11 @@ int git_config_set_multivar_in_file(const char *config_filename, goto out_free; } close(in_fd); + in_fd = -1; - if (chmod(lock->filename.buf, st.st_mode & 07777) < 0) { + if (chmod(get_lock_file_path(lock), st.st_mode & 07777) < 0) { error("chmod on %s failed: %s", - lock->filename.buf, strerror(errno)); + get_lock_file_path(lock), strerror(errno)); ret = CONFIG_NO_WRITE; goto out_free; } @@ -2148,10 +2168,12 @@ out_free: free(filename_buf); if (contents) munmap(contents, contents_sz); + if (in_fd >= 0) + close(in_fd); return ret; write_err_out: - ret = write_error(lock->filename.buf); + ret = write_error(get_lock_file_path(lock)); goto out_free; } @@ -2252,9 +2274,9 @@ int git_config_rename_section_in_file(const char *config_filename, fstat(fileno(config_file), &st); - if (chmod(lock->filename.buf, st.st_mode & 07777) < 0) { + if (chmod(get_lock_file_path(lock), st.st_mode & 07777) < 0) { ret = error("chmod on %s failed: %s", - lock->filename.buf, strerror(errno)); + get_lock_file_path(lock), strerror(errno)); goto out; } @@ -2275,7 +2297,7 @@ int git_config_rename_section_in_file(const char *config_filename, } store.baselen = strlen(new_name); if (!store_write_section(out_fd, new_name)) { - ret = write_error(lock->filename.buf); + ret = write_error(get_lock_file_path(lock)); goto out; } /* @@ -2301,7 +2323,7 @@ int git_config_rename_section_in_file(const char *config_filename, continue; length = strlen(output); if (write_in_full(out_fd, output, length) != length) { - ret = write_error(lock->filename.buf); + ret = write_error(get_lock_file_path(lock)); goto out; } } diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index c97c648d7e..482ca84b45 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -744,9 +744,8 @@ __git_compute_porcelain_commands () __git_get_config_variables () { local section="$1" i IFS=$'\n' - for i in $(git --git-dir="$(__gitdir)" config --get-regexp "^$section\..*" 2>/dev/null); do - i="${i#$section.}" - echo "${i/ */}" + for i in $(git --git-dir="$(__gitdir)" config --name-only --get-regexp "^$section\..*" 2>/dev/null); do + echo "${i#$section.}" done } @@ -1667,7 +1666,10 @@ _git_push () _git_rebase () { local dir="$(__gitdir)" - if [ -d "$dir"/rebase-apply ] || [ -d "$dir"/rebase-merge ]; then + if [ -f "$dir"/rebase-merge/interactive ]; then + __gitcomp "--continue --skip --abort --edit-todo" + return + elif [ -d "$dir"/rebase-apply ] || [ -d "$dir"/rebase-merge ]; then __gitcomp "--continue --skip --abort" return fi @@ -1774,15 +1776,7 @@ __git_config_get_set_variables () c=$((--c)) done - git --git-dir="$(__gitdir)" config $config_file --list 2>/dev/null | - while read -r line - do - case "$line" in - *.*=*) - echo "${line/=*/}" - ;; - esac - done + git --git-dir="$(__gitdir)" config $config_file --name-only --list 2>/dev/null } _git_config () @@ -1887,6 +1881,7 @@ _git_config () --get --get-all --get-regexp --add --unset --unset-all --remove-section --rename-section + --name-only " return ;; @@ -2118,6 +2113,7 @@ _git_config () http.postBuffer http.proxy http.sslCipherList + http.sslVersion http.sslCAInfo http.sslCAPath http.sslCert diff --git a/contrib/examples/builtin-fetch--tool.c b/contrib/examples/builtin-fetch--tool.c index ee1916641e..a3eb19de04 100644 --- a/contrib/examples/builtin-fetch--tool.c +++ b/contrib/examples/builtin-fetch--tool.c @@ -516,7 +516,7 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix) if (argc != 8) return error("append-fetch-head takes 6 args"); - filename = git_path("FETCH_HEAD"); + filename = git_path_fetch_head(); fp = fopen(filename, "a"); if (!fp) return error("cannot open %s: %s", filename, strerror(errno)); @@ -534,7 +534,7 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix) if (argc != 5) return error("fetch-native-store takes 3 args"); - filename = git_path("FETCH_HEAD"); + filename = git_path_fetch_head(); fp = fopen(filename, "a"); if (!fp) return error("cannot open %s: %s", filename, strerror(errno)); diff --git a/contrib/examples/git-pull.sh b/contrib/examples/git-pull.sh index a814bf61aa..e8dc2e0e7d 100755 --- a/contrib/examples/git-pull.sh +++ b/contrib/examples/git-pull.sh @@ -295,7 +295,7 @@ test true = "$rebase" && { } orig_head=$(git rev-parse -q --verify HEAD) git fetch $verbosity $progress $dry_run $recurse_submodules $all $append \ -$upload_pack $force $tags $prune $keep $depth $unshallow $update_shallow \ +${upload_pack:+"$upload_pack"} $force $tags $prune $keep $depth $unshallow $update_shallow \ $refmap --update-head-ok "$@" || exit 1 test -z "$dry_run" || exit 0 diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh index 07bd77c4c8..9f06571851 100755 --- a/contrib/subtree/git-subtree.sh +++ b/contrib/subtree/git-subtree.sh @@ -305,7 +305,7 @@ copy_commit() # We're going to set some environment vars here, so # do it in a subshell to get rid of them safely later debug copy_commit "{$1}" "{$2}" "{$3}" - git log -1 --pretty=format:'%an%n%ae%n%ad%n%cn%n%ce%n%cd%n%B' "$1" | + git log -1 --pretty=format:'%an%n%ae%n%aD%n%cn%n%ce%n%cD%n%B' "$1" | ( read GIT_AUTHOR_NAME read GIT_AUTHOR_EMAIL diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh index bd3df97936..90519823be 100755 --- a/contrib/subtree/t/t7900-subtree.sh +++ b/contrib/subtree/t/t7900-subtree.sh @@ -94,6 +94,10 @@ test_expect_success 'add sub3' ' # Back to mainline cd .. +test_expect_success 'enable log.date=relative to catch errors' ' + git config log.date relative +' + test_expect_success 'add main4' ' create main4 && git commit -m "main4" && diff --git a/credential-cache--daemon.c b/credential-cache--daemon.c index c2f00498f6..eef6fce4c7 100644 --- a/credential-cache--daemon.c +++ b/credential-cache--daemon.c @@ -1,23 +1,11 @@ #include "cache.h" +#include "tempfile.h" #include "credential.h" #include "unix-socket.h" #include "sigchain.h" #include "parse-options.h" -static const char *socket_path; - -static void cleanup_socket(void) -{ - if (socket_path) - unlink(socket_path); -} - -static void cleanup_socket_on_signal(int sig) -{ - cleanup_socket(); - sigchain_pop(sig); - raise(sig); -} +static struct tempfile socket_file; struct credential_cache_entry { struct credential item; @@ -221,7 +209,6 @@ static void serve_cache(const char *socket_path, int debug) ; /* nothing */ close(fd); - unlink(socket_path); } static const char permissions_advice[] = @@ -257,6 +244,7 @@ static void check_socket_directory(const char *path) int main(int argc, const char **argv) { + const char *socket_path; static const char *usage[] = { "git-credential-cache--daemon [opts] <socket_path>", NULL @@ -273,12 +261,11 @@ int main(int argc, const char **argv) if (!socket_path) usage_with_options(usage, options); - check_socket_directory(socket_path); - - atexit(cleanup_socket); - sigchain_push_common(cleanup_socket_on_signal); + check_socket_directory(socket_path); + register_tempfile(&socket_file, socket_path); serve_cache(socket_path, debug); + delete_tempfile(&socket_file); return 0; } diff --git a/credential-store.c b/credential-store.c index f6925096ff..00aea3aa30 100644 --- a/credential-store.c +++ b/credential-store.c @@ -52,7 +52,7 @@ static void print_entry(struct credential *c) static void print_line(struct strbuf *buf) { strbuf_addch(buf, '\n'); - write_or_die(credential_lock.fd, buf->buf, buf->len); + write_or_die(get_lock_file_fd(&credential_lock), buf->buf, buf->len); } static void rewrite_credential_file(const char *fn, struct credential *c, @@ -2,6 +2,7 @@ * Copyright (C) 2005 Junio C Hamano */ #include "cache.h" +#include "tempfile.h" #include "quote.h" #include "diff.h" #include "diffcore.h" @@ -13,6 +14,7 @@ #include "utf8.h" #include "userdiff.h" #include "sigchain.h" +#include "submodule-config.h" #include "submodule.h" #include "ll-merge.h" #include "string-list.h" @@ -308,11 +310,26 @@ static const char *external_diff(void) return external_diff_cmd; } +/* + * Keep track of files used for diffing. Sometimes such an entry + * refers to a temporary file, sometimes to an existing file, and + * sometimes to "/dev/null". + */ static struct diff_tempfile { - const char *name; /* filename external diff should read from */ + /* + * filename external diff should read from, or NULL if this + * entry is currently not in use: + */ + const char *name; + char hex[41]; char mode[10]; - char tmp_path[PATH_MAX]; + + /* + * If this diff_tempfile instance refers to a temporary file, + * this tempfile object is used to manage its lifetime. + */ + struct tempfile tempfile; } diff_temp[2]; typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len); @@ -597,25 +614,16 @@ static struct diff_tempfile *claim_diff_tempfile(void) { die("BUG: diff is failing to clean up its tempfiles"); } -static int remove_tempfile_installed; - static void remove_tempfile(void) { int i; for (i = 0; i < ARRAY_SIZE(diff_temp); i++) { - if (diff_temp[i].name == diff_temp[i].tmp_path) - unlink_or_warn(diff_temp[i].name); + if (is_tempfile_active(&diff_temp[i].tempfile)) + delete_tempfile(&diff_temp[i].tempfile); diff_temp[i].name = NULL; } } -static void remove_tempfile_on_signal(int signo) -{ - remove_tempfile(); - sigchain_pop(signo); - raise(signo); -} - static void print_line_count(FILE *file, int count) { switch (count) { @@ -2858,8 +2866,7 @@ static void prep_temp_blob(const char *path, struct diff_tempfile *temp, strbuf_addstr(&template, "XXXXXX_"); strbuf_addstr(&template, base); - fd = git_mkstemps(temp->tmp_path, PATH_MAX, template.buf, - strlen(base) + 1); + fd = mks_tempfile_ts(&temp->tempfile, template.buf, strlen(base) + 1); if (fd < 0) die_errno("unable to create temp-file"); if (convert_to_working_tree(path, @@ -2869,8 +2876,8 @@ static void prep_temp_blob(const char *path, struct diff_tempfile *temp, } if (write_in_full(fd, blob, size) != size) die_errno("unable to write temp-file"); - close(fd); - temp->name = temp->tmp_path; + close_tempfile(&temp->tempfile); + temp->name = get_tempfile_path(&temp->tempfile); strcpy(temp->hex, sha1_to_hex(sha1)); temp->hex[40] = 0; sprintf(temp->mode, "%06o", mode); @@ -2895,12 +2902,6 @@ static struct diff_tempfile *prepare_temp_file(const char *name, return temp; } - if (!remove_tempfile_installed) { - atexit(remove_tempfile); - sigchain_push_common(remove_tempfile_on_signal); - remove_tempfile_installed = 1; - } - if (!S_ISGITLINK(one->mode) && (!one->sha1_valid || reuse_worktree_file(name, one->sha1, 1))) { @@ -1078,10 +1078,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) (!untracked || !untracked->valid || /* * .. and .gitignore does not exist before - * (i.e. null exclude_sha1 and skip_worktree is - * not set). Then we can skip loading .gitignore, - * which would result in ENOENT anyway. - * skip_worktree is taken care in read_directory() + * (i.e. null exclude_sha1). Then we can skip + * loading .gitignore, which would result in + * ENOENT anyway. */ !is_null_sha1(untracked->exclude_sha1))) { /* @@ -1298,7 +1297,7 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len) */ static enum path_treatment treat_directory(struct dir_struct *dir, struct untracked_cache_dir *untracked, - const char *dirname, int len, int exclude, + const char *dirname, int len, int baselen, int exclude, const struct path_simplify *simplify) { /* The "len-1" is to strip the final '/' */ @@ -1325,7 +1324,8 @@ static enum path_treatment treat_directory(struct dir_struct *dir, if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES)) return exclude ? path_excluded : path_untracked; - untracked = lookup_untracked(dir->untracked, untracked, dirname, len); + untracked = lookup_untracked(dir->untracked, untracked, + dirname + baselen, len - baselen); return read_directory_recursive(dir, dirname, len, untracked, 1, simplify); } @@ -1445,6 +1445,7 @@ static int get_dtype(struct dirent *de, const char *path, int len) static enum path_treatment treat_one_path(struct dir_struct *dir, struct untracked_cache_dir *untracked, struct strbuf *path, + int baselen, const struct path_simplify *simplify, int dtype, struct dirent *de) { @@ -1496,8 +1497,8 @@ static enum path_treatment treat_one_path(struct dir_struct *dir, return path_none; case DT_DIR: strbuf_addch(path, '/'); - return treat_directory(dir, untracked, path->buf, path->len, exclude, - simplify); + return treat_directory(dir, untracked, path->buf, path->len, + baselen, exclude, simplify); case DT_REG: case DT_LNK: return exclude ? path_excluded : path_untracked; @@ -1558,7 +1559,7 @@ static enum path_treatment treat_path(struct dir_struct *dir, return path_none; dtype = DTYPE(de); - return treat_one_path(dir, untracked, path, simplify, dtype, de); + return treat_one_path(dir, untracked, path, baselen, simplify, dtype, de); } static void add_untracked(struct untracked_cache_dir *dir, const char *name) @@ -1828,7 +1829,7 @@ static int treat_leading_path(struct dir_struct *dir, break; if (simplify_away(sb.buf, sb.len, simplify)) break; - if (treat_one_path(dir, NULL, &sb, simplify, + if (treat_one_path(dir, NULL, &sb, baselen, simplify, DT_DIR, NULL) == path_none) break; /* do not recurse into it */ if (len <= baselen) { @@ -1880,7 +1881,6 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d const struct pathspec *pathspec) { struct untracked_cache_dir *root; - int i; if (!dir->untracked || getenv("GIT_DISABLE_UNTRACKED_CACHE")) return NULL; @@ -1932,15 +1932,6 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d if (dir->exclude_list_group[EXC_CMDL].nr) return NULL; - /* - * An optimization in prep_exclude() does not play well with - * CE_SKIP_WORKTREE. It's a rare case anyway, if a single - * entry has that bit set, disable the whole untracked cache. - */ - for (i = 0; i < active_nr; i++) - if (ce_skip_worktree(active_cache[i])) - return NULL; - if (!ident_in_untracked(dir->untracked)) { warning(_("Untracked cache is disabled on this system.")); return NULL; @@ -2185,6 +2176,8 @@ int remove_dir_recursively(struct strbuf *path, int flag) return remove_dir_recurse(path, flag, NULL); } +static GIT_PATH_FUNC(git_path_info_exclude, "info/exclude") + void setup_standard_excludes(struct dir_struct *dir) { const char *path; @@ -2199,7 +2192,7 @@ void setup_standard_excludes(struct dir_struct *dir) dir->untracked ? &dir->ss_excludes_file : NULL); /* per repository user preference */ - path = git_path("info/exclude"); + path = git_path_info_exclude(); if (!access_or_warn(path, R_OK, 0)) add_excludes_from_file_1(dir, path, dir->untracked ? &dir->ss_info_exclude : NULL); @@ -2625,23 +2618,67 @@ done2: return uc; } +static void invalidate_one_directory(struct untracked_cache *uc, + struct untracked_cache_dir *ucd) +{ + uc->dir_invalidated++; + ucd->valid = 0; + ucd->untracked_nr = 0; +} + +/* + * Normally when an entry is added or removed from a directory, + * invalidating that directory is enough. No need to touch its + * ancestors. When a directory is shown as "foo/bar/" in git-status + * however, deleting or adding an entry may have cascading effect. + * + * Say the "foo/bar/file" has become untracked, we need to tell the + * untracked_cache_dir of "foo" that "bar/" is not an untracked + * directory any more (because "bar" is managed by foo as an untracked + * "file"). + * + * Similarly, if "foo/bar/file" moves from untracked to tracked and it + * was the last untracked entry in the entire "foo", we should show + * "foo/" instead. Which means we have to invalidate past "bar" up to + * "foo". + * + * This function traverses all directories from root to leaf. If there + * is a chance of one of the above cases happening, we invalidate back + * to root. Otherwise we just invalidate the leaf. There may be a more + * sophisticated way than checking for SHOW_OTHER_DIRECTORIES to + * detect these cases and avoid unnecessary invalidation, for example, + * checking for the untracked entry named "bar/" in "foo", but for now + * stick to something safe and simple. + */ +static int invalidate_one_component(struct untracked_cache *uc, + struct untracked_cache_dir *dir, + const char *path, int len) +{ + const char *rest = strchr(path, '/'); + + if (rest) { + int component_len = rest - path; + struct untracked_cache_dir *d = + lookup_untracked(uc, dir, path, component_len); + int ret = + invalidate_one_component(uc, d, rest + 1, + len - (component_len + 1)); + if (ret) + invalidate_one_directory(uc, dir); + return ret; + } + + invalidate_one_directory(uc, dir); + return uc->dir_flags & DIR_SHOW_OTHER_DIRECTORIES; +} + void untracked_cache_invalidate_path(struct index_state *istate, const char *path) { - const char *sep; - struct untracked_cache_dir *d; if (!istate->untracked || !istate->untracked->root) return; - sep = strrchr(path, '/'); - if (sep) - d = lookup_untracked(istate->untracked, - istate->untracked->root, - path, sep - path); - else - d = istate->untracked->root; - istate->untracked->dir_invalidated++; - d->valid = 0; - d->untracked_nr = 0; + invalidate_one_component(istate->untracked, istate->untracked->root, + path, strlen(path)); } void untracked_cache_remove_from_index(struct index_state *istate, diff --git a/fast-import.c b/fast-import.c index 2ad4fee07e..6c7c3c9b66 100644 --- a/fast-import.c +++ b/fast-import.c @@ -407,7 +407,7 @@ static void dump_marks_helper(FILE *, uintmax_t, struct mark_set *); static void write_crash_report(const char *err) { - const char *loc = git_path("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid()); + char *loc = git_pathdup("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid()); FILE *rpt = fopen(loc, "w"); struct branch *b; unsigned long lu; @@ -415,6 +415,7 @@ static void write_crash_report(const char *err) if (!rpt) { error("can't write crash report %s: %s", loc, strerror(errno)); + free(loc); return; } @@ -488,6 +489,7 @@ static void write_crash_report(const char *err) fputs("-------------------\n", rpt); fputs("END OF CRASH REPORT\n", rpt); fclose(rpt); + free(loc); } static void end_packfile(void); @@ -923,12 +925,12 @@ static char *keep_pack(const char *curr_index_name) snprintf(name, sizeof(name), "%s/pack/pack-%s.pack", get_object_directory(), sha1_to_hex(pack_data->sha1)); - if (move_temp_to_file(pack_data->pack_name, name)) + if (finalize_object_file(pack_data->pack_name, name)) die("cannot store pack file"); snprintf(name, sizeof(name), "%s/pack/pack-%s.idx", get_object_directory(), sha1_to_hex(pack_data->sha1)); - if (move_temp_to_file(curr_index_name, name)) + if (finalize_object_file(curr_index_name, name)) die("cannot store index file"); free((void *)curr_index_name); return name; diff --git a/fetch-pack.c b/fetch-pack.c index a136772152..820251a8d8 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -948,7 +948,7 @@ static void update_shallow(struct fetch_pack_args *args, if (args->depth > 0 && alternate_shallow_file) { if (*alternate_shallow_file == '\0') { /* --unshallow */ - unlink_or_warn(git_path("shallow")); + unlink_or_warn(git_path_shallow()); rollback_lock_file(&shallow_lock); } else commit_lock_file(&shallow_lock); diff --git a/generate-cmdlist.perl b/generate-cmdlist.perl deleted file mode 100755 index 31516e36ac..0000000000 --- a/generate-cmdlist.perl +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/perl -use strict; -use warnings; - -print <<"EOT"; -/* Automatically generated by $0 */ - -struct cmdname_help { - char name[16]; - char help[80]; - unsigned char group; -}; - -static char *common_cmd_groups[] = { -EOT - -my $n = 0; -my %grp; -while (<>) { - last if /^### command list/; - next if (1../^### common groups/) || /^#/ || /^\s*$/; - chop; - my ($k, $v) = split ' ', $_, 2; - $grp{$k} = $n++; - print "\tN_(\"$v\"),\n"; -} - -print "};\n\nstatic struct cmdname_help common_cmds[] = {\n"; - -while (<>) { - next if /^#/ || /^\s*$/; - my @tags = split; - my $cmd = shift @tags; - for my $t (@tags) { - if (exists $grp{$t}) { - my $s; - open my $f, '<', "Documentation/$cmd.txt" or die; - while (<$f>) { - ($s) = /^$cmd - (.+)$/; - last if $s; - } - close $f; - $cmd =~ s/^git-//; - print "\t{\"$cmd\", N_(\"$s\"), $grp{$t}},\n"; - last; - } - } -} - -print "};\n"; diff --git a/generate-cmdlist.sh b/generate-cmdlist.sh new file mode 100755 index 0000000000..ab0d1b0c06 --- /dev/null +++ b/generate-cmdlist.sh @@ -0,0 +1,50 @@ +#!/bin/sh + +echo "/* Automatically generated by $0 */ +struct cmdname_help { + char name[16]; + char help[80]; + unsigned char group; +}; + +static const char *common_cmd_groups[] = {" + +grps=grps$$.tmp +match=match$$.tmp +trap "rm -f '$grps' '$match'" 0 1 2 3 15 + +sed -n ' + 1,/^### common groups/b + /^### command list/q + /^#/b + /^[ ]*$/b + h;s/^[^ ][^ ]*[ ][ ]*\(.*\)/ N_("\1"),/p + g;s/^\([^ ][^ ]*\)[ ].*/\1/w '$grps' + ' "$1" +printf '};\n\n' + +n=0 +substnum= +while read grp +do + echo "^git-..*[ ]$grp" + substnum="$substnum${substnum:+;}s/[ ]$grp/$n/" + n=$(($n+1)) +done <"$grps" >"$match" + +printf 'static struct cmdname_help common_cmds[] = {\n' +grep -f "$match" "$1" | +sed 's/^git-//' | +sort | +while read cmd tags +do + tag=$(echo "$tags" | sed "$substnum; s/[^0-9]//g") + sed -n ' + /^NAME/,/git-'"$cmd"'/H + ${ + x + s/.*git-'"$cmd"' - \(.*\)/ {"'"$cmd"'", N_("\1"), '$tag'},/ + p + }' "Documentation/git-$cmd.txt" +done +echo "};" diff --git a/git-bisect.sh b/git-bisect.sh index ae3fec22c4..ea63223ab3 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -32,6 +32,8 @@ OPTIONS_SPEC= _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40" +TERM_BAD=bad +TERM_GOOD=good bisect_head() { @@ -75,6 +77,8 @@ bisect_start() { orig_args=$(git rev-parse --sq-quote "$@") bad_seen=0 eval='' + must_write_terms=0 + revs='' if test "z$(git rev-parse --is-bare-repository)" != zfalse then mode=--no-checkout @@ -99,16 +103,27 @@ bisect_start() { die "$(eval_gettext "'\$arg' does not appear to be a valid revision")" break } - case $bad_seen in - 0) state='bad' ; bad_seen=1 ;; - *) state='good' ;; - esac - eval="$eval bisect_write '$state' '$rev' 'nolog' &&" + revs="$revs $rev" shift ;; esac done + for rev in $revs + do + # The user ran "git bisect start <sha1> + # <sha1>", hence did not explicitly specify + # the terms, but we are already starting to + # set references named with the default terms, + # and won't be able to change afterwards. + must_write_terms=1 + + case $bad_seen in + 0) state=$TERM_BAD ; bad_seen=1 ;; + *) state=$TERM_GOOD ;; + esac + eval="$eval bisect_write '$state' '$rev' 'nolog' &&" + done # # Verify HEAD. # @@ -170,6 +185,10 @@ bisect_start() { } && git rev-parse --sq-quote "$@" >"$GIT_DIR/BISECT_NAMES" && eval "$eval true" && + if test $must_write_terms -eq 1 + then + write_terms "$TERM_BAD" "$TERM_GOOD" + fi && echo "git bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" || exit # # Check if we can proceed to the next bisect state. @@ -184,9 +203,12 @@ bisect_write() { rev="$2" nolog="$3" case "$state" in - bad) tag="$state" ;; - good|skip) tag="$state"-"$rev" ;; - *) die "$(eval_gettext "Bad bisect_write argument: \$state")" ;; + "$TERM_BAD") + tag="$state" ;; + "$TERM_GOOD"|skip) + tag="$state"-"$rev" ;; + *) + die "$(eval_gettext "Bad bisect_write argument: \$state")" ;; esac git update-ref "refs/bisect/$tag" "$rev" || exit echo "# $state: $(git show-branch $rev)" >>"$GIT_DIR/BISECT_LOG" @@ -227,15 +249,16 @@ bisect_skip() { bisect_state() { bisect_autostart state=$1 + check_and_set_terms $state case "$#,$state" in 0,*) die "$(gettext "Please call 'bisect_state' with at least one argument.")" ;; - 1,bad|1,good|1,skip) + 1,"$TERM_BAD"|1,"$TERM_GOOD"|1,skip) rev=$(git rev-parse --verify $(bisect_head)) || die "$(gettext "Bad rev input: $(bisect_head)")" bisect_write "$state" "$rev" check_expected_revs "$rev" ;; - 2,bad|*,good|*,skip) + 2,"$TERM_BAD"|*,"$TERM_GOOD"|*,skip) shift hash_list='' for rev in "$@" @@ -249,8 +272,8 @@ bisect_state() { bisect_write "$state" "$rev" done check_expected_revs $hash_list ;; - *,bad) - die "$(gettext "'git bisect bad' can take only one argument.")" ;; + *,"$TERM_BAD") + die "$(eval_gettext "'git bisect \$TERM_BAD' can take only one argument.")" ;; *) usage ;; esac @@ -259,21 +282,21 @@ bisect_state() { bisect_next_check() { missing_good= missing_bad= - git show-ref -q --verify refs/bisect/bad || missing_bad=t - test -n "$(git for-each-ref "refs/bisect/good-*")" || missing_good=t + git show-ref -q --verify refs/bisect/$TERM_BAD || missing_bad=t + test -n "$(git for-each-ref "refs/bisect/$TERM_GOOD-*")" || missing_good=t case "$missing_good,$missing_bad,$1" in ,,*) - : have both good and bad - ok + : have both $TERM_GOOD and $TERM_BAD - ok ;; *,) # do not have both but not asked to fail - just report. false ;; - t,,good) + t,,"$TERM_GOOD") # have bad but not good. we could bisect although # this is less optimum. - gettextln "Warning: bisecting only with a bad commit." >&2 + eval_gettextln "Warning: bisecting only with a \$TERM_BAD commit." >&2 if test -t 0 then # TRANSLATORS: Make sure to include [Y] and [n] in your @@ -283,18 +306,20 @@ bisect_next_check() { read yesno case "$yesno" in [Nn]*) exit 1 ;; esac fi - : bisect without good... + : bisect without $TERM_GOOD... ;; *) - + bad_syn=$(bisect_voc bad) + good_syn=$(bisect_voc good) if test -s "$GIT_DIR/BISECT_START" then - gettextln "You need to give me at least one good and one bad revision. -(You can use \"git bisect bad\" and \"git bisect good\" for that.)" >&2 + + eval_gettextln "You need to give me at least one \$bad_syn and one \$good_syn revision. +(You can use \"git bisect \$bad_syn\" and \"git bisect \$good_syn\" for that.)" >&2 else - gettextln "You need to start by \"git bisect start\". -You then need to give me at least one good and one bad revision. -(You can use \"git bisect bad\" and \"git bisect good\" for that.)" >&2 + eval_gettextln "You need to start by \"git bisect start\". +You then need to give me at least one \$good_syn and one \$bad_syn revision. +(You can use \"git bisect \$bad_syn\" and \"git bisect \$good_syn\" for that.)" >&2 fi exit 1 ;; esac @@ -307,7 +332,7 @@ bisect_auto_next() { bisect_next() { case "$#" in 0) ;; *) usage ;; esac bisect_autostart - bisect_next_check good + bisect_next_check $TERM_GOOD # Perform all bisection computation, display and checkout git bisect--helper --next-all $(test -f "$GIT_DIR/BISECT_HEAD" && echo --no-checkout) @@ -316,18 +341,18 @@ bisect_next() { # Check if we should exit because bisection is finished if test $res -eq 10 then - bad_rev=$(git show-ref --hash --verify refs/bisect/bad) + bad_rev=$(git show-ref --hash --verify refs/bisect/$TERM_BAD) bad_commit=$(git show-branch $bad_rev) - echo "# first bad commit: $bad_commit" >>"$GIT_DIR/BISECT_LOG" + echo "# first $TERM_BAD commit: $bad_commit" >>"$GIT_DIR/BISECT_LOG" exit 0 elif test $res -eq 2 then echo "# only skipped commits left to test" >>"$GIT_DIR/BISECT_LOG" - good_revs=$(git for-each-ref --format="%(objectname)" "refs/bisect/good-*") - for skipped in $(git rev-list refs/bisect/bad --not $good_revs) + good_revs=$(git for-each-ref --format="%(objectname)" "refs/bisect/$TERM_GOOD-*") + for skipped in $(git rev-list refs/bisect/$TERM_BAD --not $good_revs) do skipped_commit=$(git show-branch $skipped) - echo "# possible first bad commit: $skipped_commit" >>"$GIT_DIR/BISECT_LOG" + echo "# possible first $TERM_BAD commit: $skipped_commit" >>"$GIT_DIR/BISECT_LOG" done exit $res fi @@ -397,6 +422,7 @@ bisect_clean_state() { rm -f "$GIT_DIR/BISECT_LOG" && rm -f "$GIT_DIR/BISECT_NAMES" && rm -f "$GIT_DIR/BISECT_RUN" && + rm -f "$GIT_DIR/BISECT_TERMS" && # Cleanup head-name if it got left by an old version of git-bisect rm -f "$GIT_DIR/head-name" && git update-ref -d --no-deref BISECT_HEAD && @@ -417,11 +443,13 @@ bisect_replay () { rev="$command" command="$bisect" fi + get_terms + check_and_set_terms "$command" case "$command" in start) cmd="bisect_start $rev" eval "$cmd" ;; - good|bad|skip) + "$TERM_GOOD"|"$TERM_BAD"|skip) bisect_write "$command" "$rev" ;; *) die "$(gettext "?? what are you talking about?")" ;; @@ -455,9 +483,9 @@ exit code \$res from '\$command' is < 0 or >= 128" >&2 state='skip' elif [ $res -gt 0 ] then - state='bad' + state="$TERM_BAD" else - state='good' + state="$TERM_GOOD" fi # We have to use a subshell because "bisect_state" can exit. @@ -466,7 +494,7 @@ exit code \$res from '\$command' is < 0 or >= 128" >&2 cat "$GIT_DIR/BISECT_RUN" - if sane_grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \ + if sane_grep "first $TERM_BAD commit could be any of" "$GIT_DIR/BISECT_RUN" \ >/dev/null then gettextln "bisect run cannot continue any more" >&2 @@ -480,7 +508,7 @@ exit code \$res from '\$command' is < 0 or >= 128" >&2 exit $res fi - if sane_grep "is the first bad commit" "$GIT_DIR/BISECT_RUN" >/dev/null + if sane_grep "is the first $TERM_BAD commit" "$GIT_DIR/BISECT_RUN" >/dev/null then gettextln "bisect run success" exit 0; @@ -494,18 +522,62 @@ bisect_log () { cat "$GIT_DIR/BISECT_LOG" } +get_terms () { + if test -s "$GIT_DIR/BISECT_TERMS" + then + { + read TERM_BAD + read TERM_GOOD + } <"$GIT_DIR/BISECT_TERMS" + fi +} + +write_terms () { + TERM_BAD=$1 + TERM_GOOD=$2 + printf '%s\n%s\n' "$TERM_BAD" "$TERM_GOOD" >"$GIT_DIR/BISECT_TERMS" +} + +check_and_set_terms () { + cmd="$1" + case "$cmd" in + skip|start|terms) ;; + *) + if test -s "$GIT_DIR/BISECT_TERMS" && test "$cmd" != "$TERM_BAD" && test "$cmd" != "$TERM_GOOD" + then + die "$(eval_gettext "Invalid command: you're currently in a \$TERM_BAD/\$TERM_GOOD bisect.")" + fi + case "$cmd" in + bad|good) + if ! test -s "$GIT_DIR/BISECT_TERMS" + then + write_terms bad good + fi + ;; + esac ;; + esac +} + +bisect_voc () { + case "$1" in + bad) echo "bad" ;; + good) echo "good" ;; + esac +} + case "$#" in 0) usage ;; *) cmd="$1" + get_terms shift case "$cmd" in help) git bisect -h ;; start) bisect_start "$@" ;; - bad|good) + bad|good|"$TERM_BAD"|"$TERM_GOOD") bisect_state "$cmd" "$@" ;; skip) bisect_skip "$@" ;; diff --git a/git-compat-util.h b/git-compat-util.h index 392da79029..f649e81f11 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -389,7 +389,6 @@ struct strbuf; /* General helper functions */ extern void vreportf(const char *prefix, const char *err, va_list params); -extern void vwritef(int fd, const char *prefix, const char *err, va_list params); extern NORETURN void usage(const char *err); extern NORETURN void usagef(const char *err, ...) __attribute__((format (printf, 1, 2))); extern NORETURN void die(const char *err, ...) __attribute__((format (printf, 1, 2))); @@ -425,6 +424,7 @@ static inline int const_error(void) extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params)); extern void set_error_routine(void (*routine)(const char *err, va_list params)); extern void set_die_is_recursing_routine(int (*routine)(void)); +extern void set_error_handle(FILE *); extern int starts_with(const char *str, const char *prefix); diff --git a/git-send-email.perl b/git-send-email.perl index b660cc2382..c5a3f766f7 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -75,6 +75,8 @@ git send-email [options] <file | directory | rev-list options > Pass an empty string to disable certificate verification. --smtp-domain <str> * The domain name sent to HELO/EHLO handshake + --smtp-auth <str> * Space-separated list of allowed AUTH mechanisms. + This setting forces to use one of the listed mechanisms. --smtp-debug <0|1> * Disable, enable Net::SMTP debug. Automating: @@ -208,7 +210,7 @@ my ($cover_cc, $cover_to); my ($to_cmd, $cc_cmd); my ($smtp_server, $smtp_server_port, @smtp_server_options); my ($smtp_authuser, $smtp_encryption, $smtp_ssl_cert_path); -my ($identity, $aliasfiletype, @alias_files, $smtp_domain); +my ($identity, $aliasfiletype, @alias_files, $smtp_domain, $smtp_auth); my ($validate, $confirm); my (@suppress_cc); my ($auto_8bit_encoding); @@ -239,6 +241,7 @@ my %config_settings = ( "smtppass" => \$smtp_authpass, "smtpsslcertpath" => \$smtp_ssl_cert_path, "smtpdomain" => \$smtp_domain, + "smtpauth" => \$smtp_auth, "to" => \@initial_to, "tocmd" => \$to_cmd, "cc" => \@initial_cc, @@ -310,6 +313,7 @@ my $rc = GetOptions("h" => \$help, "smtp-ssl-cert-path=s" => \$smtp_ssl_cert_path, "smtp-debug:i" => \$debug_net_smtp, "smtp-domain:s" => \$smtp_domain, + "smtp-auth=s" => \$smtp_auth, "identity=s" => \$identity, "annotate!" => \$annotate, "no-annotate" => sub {$annotate = 0}, @@ -1130,6 +1134,12 @@ sub smtp_auth_maybe { Authen::SASL->import(qw(Perl)); }; + # Check mechanism naming as defined in: + # https://tools.ietf.org/html/rfc4422#page-8 + if ($smtp_auth !~ /^(\b[A-Z0-9-_]{1,20}\s*)*$/) { + die "invalid smtp auth: '${smtp_auth}'"; + } + # TODO: Authentication may fail not because credentials were # invalid but due to other reasons, in which we should not # reject credentials. @@ -1142,6 +1152,20 @@ sub smtp_auth_maybe { 'password' => $smtp_authpass }, sub { my $cred = shift; + + if ($smtp_auth) { + my $sasl = Authen::SASL->new( + mechanism => $smtp_auth, + callback => { + user => $cred->{'username'}, + pass => $cred->{'password'}, + authname => $cred->{'username'}, + } + ); + + return !!$smtp->auth($sasl); + } + return !!$smtp->auth($cred->{'username'}, $cred->{'password'}); }); diff --git a/git-stash.sh b/git-stash.sh index 8e9e2cd7d5..1d5ba7a4f9 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -183,9 +183,7 @@ store_stash () { stash_msg="Created via \"git stash store\"." fi - # Make sure the reflog for stash is kept. - : >>"$(git rev-parse --git-path logs/$ref_stash)" - git update-ref -m "$stash_msg" $ref_stash $w_commit + git update-ref --create-reflog -m "$stash_msg" $ref_stash $w_commit ret=$? test $ret != 0 && test -z $quiet && die "$(eval_gettext "Cannot update \$ref_stash with \$w_commit")" @@ -262,7 +260,7 @@ save_stash () { say "$(gettext "No local changes to save")" exit 0 fi - test -f "$(git rev-parse --git-path logs/$ref_stash)" || + git reflog exists $ref_stash || clear_stash || die "$(gettext "Cannot initialize stash")" create_stash "$stash_msg" $untracked diff --git a/http-backend.c b/http-backend.c index b977c006a4..bac40ef6e6 100644 --- a/http-backend.c +++ b/http-backend.c @@ -164,7 +164,7 @@ static void send_strbuf(const char *type, struct strbuf *buf) static void send_local_file(const char *the_type, const char *name) { - const char *p = git_path("%s", name); + char *p = git_pathdup("%s", name); size_t buf_alloc = 8192; char *buf = xmalloc(buf_alloc); int fd; @@ -191,6 +191,7 @@ static void send_local_file(const char *the_type, const char *name) } close(fd); free(buf); + free(p); } static void get_text_file(char *name) @@ -37,6 +37,20 @@ static int curl_ssl_verify = -1; static int curl_ssl_try; static const char *ssl_cert; static const char *ssl_cipherlist; +static const char *ssl_version; +static struct { + const char *name; + long ssl_version; +} sslversions[] = { + { "sslv2", CURL_SSLVERSION_SSLv2 }, + { "sslv3", CURL_SSLVERSION_SSLv3 }, + { "tlsv1", CURL_SSLVERSION_TLSv1 }, +#if LIBCURL_VERSION_NUM >= 0x072200 + { "tlsv1.0", CURL_SSLVERSION_TLSv1_0 }, + { "tlsv1.1", CURL_SSLVERSION_TLSv1_1 }, + { "tlsv1.2", CURL_SSLVERSION_TLSv1_2 }, +#endif +}; #if LIBCURL_VERSION_NUM >= 0x070903 static const char *ssl_key; #endif @@ -190,6 +204,8 @@ static int http_options(const char *var, const char *value, void *cb) } if (!strcmp("http.sslcipherlist", var)) return git_config_string(&ssl_cipherlist, var, value); + if (!strcmp("http.sslversion", var)) + return git_config_string(&ssl_version, var, value); if (!strcmp("http.sslcert", var)) return git_config_string(&ssl_cert, var, value); #if LIBCURL_VERSION_NUM >= 0x070903 @@ -364,9 +380,24 @@ static CURL *get_curl_handle(void) if (http_proactive_auth) init_curl_http_auth(result); + if (getenv("GIT_SSL_VERSION")) + ssl_version = getenv("GIT_SSL_VERSION"); + if (ssl_version && *ssl_version) { + int i; + for (i = 0; i < ARRAY_SIZE(sslversions); i++) { + if (!strcmp(ssl_version, sslversions[i].name)) { + curl_easy_setopt(result, CURLOPT_SSLVERSION, + sslversions[i].ssl_version); + break; + } + } + if (i == ARRAY_SIZE(sslversions)) + warning("unsupported ssl version %s: using default", + ssl_version); + } + if (getenv("GIT_SSL_CIPHER_LIST")) ssl_cipherlist = getenv("GIT_SSL_CIPHER_LIST"); - if (ssl_cipherlist != NULL && *ssl_cipherlist) curl_easy_setopt(result, CURLOPT_SSL_CIPHER_LIST, ssl_cipherlist); @@ -1318,7 +1349,7 @@ static int http_get_file(const char *url, const char *filename, ret = http_request_reauth(url, result, HTTP_REQUEST_FILE, options); fclose(result); - if (ret == HTTP_OK && move_temp_to_file(tmpfile.buf, filename)) + if (ret == HTTP_OK && finalize_object_file(tmpfile.buf, filename)) ret = HTTP_ERROR; cleanup: strbuf_release(&tmpfile); @@ -1405,7 +1436,7 @@ static int fetch_and_setup_pack_index(struct packed_git **packs_head, ret = verify_pack_index(new_pack); if (!ret) { close_pack_index(new_pack); - ret = move_temp_to_file(tmp_idx, sha1_pack_index_name(sha1)); + ret = finalize_object_file(tmp_idx, sha1_pack_index_name(sha1)); } free(tmp_idx); if (ret) @@ -1517,8 +1548,8 @@ int finish_http_pack_request(struct http_pack_request *preq) unlink(sha1_pack_index_name(p->sha1)); - if (move_temp_to_file(preq->tmpfile, sha1_pack_name(p->sha1)) - || move_temp_to_file(tmp_idx, sha1_pack_index_name(p->sha1))) { + if (finalize_object_file(preq->tmpfile, sha1_pack_name(p->sha1)) + || finalize_object_file(tmp_idx, sha1_pack_index_name(p->sha1))) { free(tmp_idx); return -1; } @@ -1782,7 +1813,7 @@ int finish_http_object_request(struct http_object_request *freq) return -1; } freq->rename = - move_temp_to_file(freq->tmpfile, sha1_file_name(freq->sha1)); + finalize_object_file(freq->tmpfile, sha1_file_name(freq->sha1)); return freq->rename; } diff --git a/lockfile.c b/lockfile.c index 993bb82748..637b8cf743 100644 --- a/lockfile.c +++ b/lockfile.c @@ -1,38 +1,9 @@ /* * Copyright (c) 2005, Junio C Hamano */ + #include "cache.h" #include "lockfile.h" -#include "sigchain.h" - -static struct lock_file *volatile lock_file_list; - -static void remove_lock_files(int skip_fclose) -{ - pid_t me = getpid(); - - while (lock_file_list) { - if (lock_file_list->owner == me) { - /* fclose() is not safe to call in a signal handler */ - if (skip_fclose) - lock_file_list->fp = NULL; - rollback_lock_file(lock_file_list); - } - lock_file_list = lock_file_list->next; - } -} - -static void remove_lock_files_on_exit(void) -{ - remove_lock_files(0); -} - -static void remove_lock_files_on_signal(int signo) -{ - remove_lock_files(1); - sigchain_pop(signo); - raise(signo); -} /* * path = absolute or relative path name @@ -101,60 +72,17 @@ static void resolve_symlink(struct strbuf *path) /* Make sure errno contains a meaningful value on error */ static int lock_file(struct lock_file *lk, const char *path, int flags) { - size_t pathlen = strlen(path); - - if (!lock_file_list) { - /* One-time initialization */ - sigchain_push_common(remove_lock_files_on_signal); - atexit(remove_lock_files_on_exit); - } - - if (lk->active) - die("BUG: cannot lock_file(\"%s\") using active struct lock_file", - path); - if (!lk->on_list) { - /* Initialize *lk and add it to lock_file_list: */ - lk->fd = -1; - lk->fp = NULL; - lk->active = 0; - lk->owner = 0; - strbuf_init(&lk->filename, pathlen + LOCK_SUFFIX_LEN); - lk->next = lock_file_list; - lock_file_list = lk; - lk->on_list = 1; - } else if (lk->filename.len) { - /* This shouldn't happen, but better safe than sorry. */ - die("BUG: lock_file(\"%s\") called with improperly-reset lock_file object", - path); - } + int fd; + struct strbuf filename = STRBUF_INIT; - if (flags & LOCK_NO_DEREF) { - strbuf_add_absolute_path(&lk->filename, path); - } else { - struct strbuf resolved_path = STRBUF_INIT; - - strbuf_add(&resolved_path, path, pathlen); - resolve_symlink(&resolved_path); - strbuf_add_absolute_path(&lk->filename, resolved_path.buf); - strbuf_release(&resolved_path); - } + strbuf_addstr(&filename, path); + if (!(flags & LOCK_NO_DEREF)) + resolve_symlink(&filename); - strbuf_addstr(&lk->filename, LOCK_SUFFIX); - lk->fd = open(lk->filename.buf, O_RDWR | O_CREAT | O_EXCL, 0666); - if (lk->fd < 0) { - strbuf_reset(&lk->filename); - return -1; - } - lk->owner = getpid(); - lk->active = 1; - if (adjust_shared_perm(lk->filename.buf)) { - int save_errno = errno; - error("cannot fix permission bits on %s", lk->filename.buf); - rollback_lock_file(lk); - errno = save_errno; - return -1; - } - return lk->fd; + strbuf_addstr(&filename, LOCK_SUFFIX); + fd = create_tempfile(&lk->tempfile, filename.buf); + strbuf_release(&filename); + return fd; } /* @@ -287,116 +215,29 @@ int hold_lock_file_for_append(struct lock_file *lk, const char *path, int flags) return fd; } -FILE *fdopen_lock_file(struct lock_file *lk, const char *mode) -{ - if (!lk->active) - die("BUG: fdopen_lock_file() called for unlocked object"); - if (lk->fp) - die("BUG: fdopen_lock_file() called twice for file '%s'", lk->filename.buf); - - lk->fp = fdopen(lk->fd, mode); - return lk->fp; -} - char *get_locked_file_path(struct lock_file *lk) { - if (!lk->active) - die("BUG: get_locked_file_path() called for unlocked object"); - if (lk->filename.len <= LOCK_SUFFIX_LEN) - die("BUG: get_locked_file_path() called for malformed lock object"); - return xmemdupz(lk->filename.buf, lk->filename.len - LOCK_SUFFIX_LEN); -} - -int close_lock_file(struct lock_file *lk) -{ - int fd = lk->fd; - FILE *fp = lk->fp; - int err; - - if (fd < 0) - return 0; - - lk->fd = -1; - if (fp) { - lk->fp = NULL; - - /* - * Note: no short-circuiting here; we want to fclose() - * in any case! - */ - err = ferror(fp) | fclose(fp); - } else { - err = close(fd); - } + struct strbuf ret = STRBUF_INIT; - if (err) { - int save_errno = errno; - rollback_lock_file(lk); - errno = save_errno; - return -1; - } - - return 0; -} - -int reopen_lock_file(struct lock_file *lk) -{ - if (0 <= lk->fd) - die(_("BUG: reopen a lockfile that is still open")); - if (!lk->active) - die(_("BUG: reopen a lockfile that has been committed")); - lk->fd = open(lk->filename.buf, O_WRONLY); - return lk->fd; + strbuf_addstr(&ret, get_tempfile_path(&lk->tempfile)); + if (ret.len <= LOCK_SUFFIX_LEN || + strcmp(ret.buf + ret.len - LOCK_SUFFIX_LEN, LOCK_SUFFIX)) + die("BUG: get_locked_file_path() called for malformed lock object"); + /* remove ".lock": */ + strbuf_setlen(&ret, ret.len - LOCK_SUFFIX_LEN); + return strbuf_detach(&ret, NULL); } -int commit_lock_file_to(struct lock_file *lk, const char *path) +int commit_lock_file(struct lock_file *lk) { - if (!lk->active) - die("BUG: attempt to commit unlocked object to \"%s\"", path); + char *result_path = get_locked_file_path(lk); - if (close_lock_file(lk)) - return -1; - - if (rename(lk->filename.buf, path)) { + if (commit_lock_file_to(lk, result_path)) { int save_errno = errno; - rollback_lock_file(lk); + free(result_path); errno = save_errno; return -1; } - - lk->active = 0; - strbuf_reset(&lk->filename); + free(result_path); return 0; } - -int commit_lock_file(struct lock_file *lk) -{ - static struct strbuf result_file = STRBUF_INIT; - int err; - - if (!lk->active) - die("BUG: attempt to commit unlocked object"); - - if (lk->filename.len <= LOCK_SUFFIX_LEN || - strcmp(lk->filename.buf + lk->filename.len - LOCK_SUFFIX_LEN, LOCK_SUFFIX)) - die("BUG: lockfile filename corrupt"); - - /* remove ".lock": */ - strbuf_add(&result_file, lk->filename.buf, - lk->filename.len - LOCK_SUFFIX_LEN); - err = commit_lock_file_to(lk, result_file.buf); - strbuf_reset(&result_file); - return err; -} - -void rollback_lock_file(struct lock_file *lk) -{ - if (!lk->active) - return; - - if (!close_lock_file(lk)) { - unlink_or_warn(lk->filename.buf); - lk->active = 0; - strbuf_reset(&lk->filename); - } -} diff --git a/lockfile.h b/lockfile.h index b4abc61c00..8131fa31b4 100644 --- a/lockfile.h +++ b/lockfile.h @@ -4,80 +4,162 @@ /* * File write-locks as used by Git. * - * For an overview of how to use the lockfile API, please see - * - * Documentation/technical/api-lockfile.txt - * - * This module keeps track of all locked files in lock_file_list for - * use at cleanup. This list and the lock_file objects that comprise - * it must be kept in self-consistent states at all time, because the - * program can be interrupted any time by a signal, in which case the - * signal handler will walk through the list attempting to clean up - * any open lock files. - * - * A lockfile is owned by the process that created it. The lock_file - * object has an "owner" field that records its owner. This field is - * used to prevent a forked process from closing a lockfile created by - * its parent. - * - * The possible states of a lock_file object are as follows: - * - * - Uninitialized. In this state the object's on_list field must be - * zero but the rest of its contents need not be initialized. As - * soon as the object is used in any way, it is irrevocably - * registered in the lock_file_list, and on_list is set. - * - * - Locked, lockfile open (after hold_lock_file_for_update(), - * hold_lock_file_for_append(), or reopen_lock_file()). In this - * state: - * - the lockfile exists - * - active is set - * - filename holds the filename of the lockfile - * - fd holds a file descriptor open for writing to the lockfile - * - fp holds a pointer to an open FILE object if and only if - * fdopen_lock_file() has been called on the object - * - owner holds the PID of the process that locked the file - * - * - Locked, lockfile closed (after successful close_lock_file()). - * Same as the previous state, except that the lockfile is closed - * and fd is -1. - * - * - Unlocked (after commit_lock_file(), commit_lock_file_to(), - * rollback_lock_file(), a failed attempt to lock, or a failed - * close_lock_file()). In this state: - * - active is unset - * - filename is empty (usually, though there are transitory - * states in which this condition doesn't hold). Client code should - * *not* rely on the filename being empty in this state. - * - fd is -1 - * - the object is left registered in the lock_file_list, and - * on_list is set. + * The lockfile API serves two purposes: + * + * * Mutual exclusion and atomic file updates. When we want to change + * a file, we create a lockfile `<filename>.lock`, write the new + * file contents into it, and then rename the lockfile to its final + * destination `<filename>`. We create the `<filename>.lock` file + * with `O_CREAT|O_EXCL` so that we can notice and fail if somebody + * else has already locked the file, then atomically rename the + * lockfile to its final destination to commit the changes and + * unlock the file. + * + * * Automatic cruft removal. If the program exits after we lock a + * file but before the changes have been committed, we want to make + * sure that we remove the lockfile. This is done by remembering the + * lockfiles we have created in a linked list and setting up an + * `atexit(3)` handler and a signal handler that clean up the + * lockfiles. This mechanism ensures that outstanding lockfiles are + * cleaned up if the program exits (including when `die()` is + * called) or if the program is terminated by a signal. + * + * Please note that lockfiles only block other writers. Readers do not + * block, but they are guaranteed to see either the old contents of + * the file or the new contents of the file (assuming that the + * filesystem implements `rename(2)` atomically). + * + * Most of the heavy lifting is done by the tempfile module (see + * "tempfile.h"). + * + * Calling sequence + * ---------------- + * + * The caller: + * + * * Allocates a `struct lock_file` either as a static variable or on + * the heap, initialized to zeros. Once you use the structure to + * call the `hold_lock_file_for_*()` family of functions, it belongs + * to the lockfile subsystem and its storage must remain valid + * throughout the life of the program (i.e. you cannot use an + * on-stack variable to hold this structure). + * + * * Attempts to create a lockfile by calling + * `hold_lock_file_for_update()` or `hold_lock_file_for_append()`. + * + * * Writes new content for the destination file by either: + * + * * writing to the file descriptor returned by the + * `hold_lock_file_for_*()` functions (also available via + * `lock->fd`). + * + * * calling `fdopen_lock_file()` to get a `FILE` pointer for the + * open file and writing to the file using stdio. + * + * When finished writing, the caller can: + * + * * Close the file descriptor and rename the lockfile to its final + * destination by calling `commit_lock_file()` or + * `commit_lock_file_to()`. + * + * * Close the file descriptor and remove the lockfile by calling + * `rollback_lock_file()`. + * + * * Close the file descriptor without removing or renaming the + * lockfile by calling `close_lock_file()`, and later call + * `commit_lock_file()`, `commit_lock_file_to()`, + * `rollback_lock_file()`, or `reopen_lock_file()`. + * + * Even after the lockfile is committed or rolled back, the + * `lock_file` object must not be freed or altered by the caller. + * However, it may be reused; just pass it to another call of + * `hold_lock_file_for_update()` or `hold_lock_file_for_append()`. + * + * If the program exits before `commit_lock_file()`, + * `commit_lock_file_to()`, or `rollback_lock_file()` is called, the + * tempfile module will close and remove the lockfile, thereby rolling + * back any uncommitted changes. + * + * If you need to close the file descriptor you obtained from a + * `hold_lock_file_for_*()` function yourself, do so by calling + * `close_lock_file()`. See "tempfile.h" for more information. + * + * + * Under the covers, a lockfile is just a tempfile with a few helper + * functions. In particular, the state diagram and the cleanup + * machinery are all implemented in the tempfile module. + * + * + * Error handling + * -------------- + * + * The `hold_lock_file_for_*()` functions return a file descriptor on + * success or -1 on failure (unless `LOCK_DIE_ON_ERROR` is used; see + * "flags" below). On errors, `errno` describes the reason for + * failure. Errors can be reported by passing `errno` to + * `unable_to_lock_message()` or `unable_to_lock_die()`. + * + * Similarly, `commit_lock_file`, `commit_lock_file_to`, and + * `close_lock_file` return 0 on success. On failure they set `errno` + * appropriately, do their best to roll back the lockfile, and return + * -1. */ +#include "tempfile.h" + struct lock_file { - struct lock_file *volatile next; - volatile sig_atomic_t active; - volatile int fd; - FILE *volatile fp; - volatile pid_t owner; - char on_list; - struct strbuf filename; + struct tempfile tempfile; }; /* String appended to a filename to derive the lockfile name: */ #define LOCK_SUFFIX ".lock" #define LOCK_SUFFIX_LEN 5 + +/* + * Flags + * ----- + * + * The following flags can be passed to `hold_lock_file_for_update()` + * or `hold_lock_file_for_append()`. + */ + +/* + * If a lock is already taken for the file, `die()` with an error + * message. If this flag is not specified, trying to lock a file that + * is already locked returns -1 to the caller. + */ #define LOCK_DIE_ON_ERROR 1 + +/* + * Usually symbolic links in the destination path are resolved. This + * means that (1) the lockfile is created by adding ".lock" to the + * resolved path, and (2) upon commit, the resolved path is + * overwritten. However, if `LOCK_NO_DEREF` is set, then the lockfile + * is created by adding ".lock" to the path argument itself. This + * option is used, for example, when detaching a symbolic reference, + * which for backwards-compatibility reasons, can be a symbolic link + * containing the name of the referred-to-reference. + */ #define LOCK_NO_DEREF 2 -extern void unable_to_lock_message(const char *path, int err, - struct strbuf *buf); -extern NORETURN void unable_to_lock_die(const char *path, int err); +/* + * Attempt to create a lockfile for the file at `path` and return a + * file descriptor for writing to it, or -1 on error. If the file is + * currently locked, retry with quadratic backoff for at least + * timeout_ms milliseconds. If timeout_ms is 0, try exactly once; if + * timeout_ms is -1, retry indefinitely. The flags argument and error + * handling are described above. + */ extern int hold_lock_file_for_update_timeout( struct lock_file *lk, const char *path, int flags, long timeout_ms); +/* + * Attempt to create a lockfile for the file at `path` and return a + * file descriptor for writing to it, or -1 on error. The flags + * argument and error handling are described above. + */ static inline int hold_lock_file_for_update( struct lock_file *lk, const char *path, int flags) @@ -85,15 +167,135 @@ static inline int hold_lock_file_for_update( return hold_lock_file_for_update_timeout(lk, path, flags, 0); } -extern int hold_lock_file_for_append(struct lock_file *lk, const char *path, - int flags); +/* + * Like `hold_lock_file_for_update()`, but before returning copy the + * existing contents of the file (if any) to the lockfile and position + * its write pointer at the end of the file. The flags argument and + * error handling are described above. + */ +extern int hold_lock_file_for_append(struct lock_file *lk, + const char *path, int flags); + +/* + * Append an appropriate error message to `buf` following the failure + * of `hold_lock_file_for_update()` or `hold_lock_file_for_append()` + * to lock `path`. `err` should be the `errno` set by the failing + * call. + */ +extern void unable_to_lock_message(const char *path, int err, + struct strbuf *buf); -extern FILE *fdopen_lock_file(struct lock_file *, const char *mode); -extern char *get_locked_file_path(struct lock_file *); -extern int commit_lock_file_to(struct lock_file *, const char *path); -extern int commit_lock_file(struct lock_file *); -extern int reopen_lock_file(struct lock_file *); -extern int close_lock_file(struct lock_file *); -extern void rollback_lock_file(struct lock_file *); +/* + * Emit an appropriate error message and `die()` following the failure + * of `hold_lock_file_for_update()` or `hold_lock_file_for_append()` + * to lock `path`. `err` should be the `errno` set by the failing + * call. + */ +extern NORETURN void unable_to_lock_die(const char *path, int err); + +/* + * Associate a stdio stream with the lockfile (which must still be + * open). Return `NULL` (*without* rolling back the lockfile) on + * error. The stream is closed automatically when `close_lock_file()` + * is called or when the file is committed or rolled back. + */ +static inline FILE *fdopen_lock_file(struct lock_file *lk, const char *mode) +{ + return fdopen_tempfile(&lk->tempfile, mode); +} + +/* + * Return the path of the lockfile. The return value is a pointer to a + * field within the lock_file object and should not be freed. + */ +static inline const char *get_lock_file_path(struct lock_file *lk) +{ + return get_tempfile_path(&lk->tempfile); +} + +static inline int get_lock_file_fd(struct lock_file *lk) +{ + return get_tempfile_fd(&lk->tempfile); +} + +static inline FILE *get_lock_file_fp(struct lock_file *lk) +{ + return get_tempfile_fp(&lk->tempfile); +} + +/* + * Return the path of the file that is locked by the specified + * lock_file object. The caller must free the memory. + */ +extern char *get_locked_file_path(struct lock_file *lk); + +/* + * If the lockfile is still open, close it (and the file pointer if it + * has been opened using `fdopen_lock_file()`) without renaming the + * lockfile over the file being locked. Return 0 upon success. On + * failure to `close(2)`, return a negative value and roll back the + * lock file. Usually `commit_lock_file()`, `commit_lock_file_to()`, + * or `rollback_lock_file()` should eventually be called if + * `close_lock_file()` succeeds. + */ +static inline int close_lock_file(struct lock_file *lk) +{ + return close_tempfile(&lk->tempfile); +} + +/* + * Re-open a lockfile that has been closed using `close_lock_file()` + * but not yet committed or rolled back. This can be used to implement + * a sequence of operations like the following: + * + * * Lock file. + * + * * Write new contents to lockfile, then `close_lock_file()` to + * cause the contents to be written to disk. + * + * * Pass the name of the lockfile to another program to allow it (and + * nobody else) to inspect the contents you wrote, while still + * holding the lock yourself. + * + * * `reopen_lock_file()` to reopen the lockfile. Make further updates + * to the contents. + * + * * `commit_lock_file()` to make the final version permanent. + */ +static inline int reopen_lock_file(struct lock_file *lk) +{ + return reopen_tempfile(&lk->tempfile); +} + +/* + * Commit the change represented by `lk`: close the file descriptor + * and/or file pointer if they are still open and rename the lockfile + * to its final destination. Return 0 upon success. On failure, roll + * back the lock file and return -1, with `errno` set to the value + * from the failing call to `close(2)` or `rename(2)`. It is a bug to + * call `commit_lock_file()` for a `lock_file` object that is not + * currently locked. + */ +extern int commit_lock_file(struct lock_file *lk); + +/* + * Like `commit_lock_file()`, but rename the lockfile to the provided + * `path`. `path` must be on the same filesystem as the lock file. + */ +static inline int commit_lock_file_to(struct lock_file *lk, const char *path) +{ + return rename_tempfile(&lk->tempfile, path); +} + +/* + * Roll back `lk`: close the file descriptor and/or file pointer and + * remove the lockfile. It is a NOOP to call `rollback_lock_file()` + * for a `lock_file` object that has already been committed or rolled + * back. + */ +static inline void rollback_lock_file(struct lock_file *lk) +{ + delete_tempfile(&lk->tempfile); +} #endif /* LOCKFILE_H */ diff --git a/notes-merge.c b/notes-merge.c index 0b2b82c41f..b3d1dab51f 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -295,7 +295,7 @@ static void write_buf_to_worktree(const unsigned char *obj, const char *buf, unsigned long size) { int fd; - const char *path = git_path(NOTES_MERGE_WORKTREE "/%s", sha1_to_hex(obj)); + char *path = git_pathdup(NOTES_MERGE_WORKTREE "/%s", sha1_to_hex(obj)); if (safe_create_leading_directories_const(path)) die_errno("unable to create directory for '%s'", path); if (file_exists(path)) @@ -320,6 +320,7 @@ static void write_buf_to_worktree(const unsigned char *obj, } close(fd); + free(path); } static void write_note_to_worktree(const unsigned char *obj, @@ -150,7 +150,8 @@ int check_pager_config(const char *cmd) struct strbuf key = STRBUF_INIT; const char *value = NULL; strbuf_addf(&key, "pager.%s", cmd); - if (!git_config_get_value(key.buf, &value)) { + if (git_config_key_is_valid(key.buf) && + !git_config_get_value(key.buf, &value)) { int b = git_config_maybe_bool(key.buf, value); if (b >= 0) want = b; diff --git a/parse-options.h b/parse-options.h index 6ca8388a53..3f1cc3aee0 100644 --- a/parse-options.h +++ b/parse-options.h @@ -127,7 +127,7 @@ struct option { #define OPT_BOOL(s, l, v, h) OPT_SET_INT(s, l, v, h, 1) #define OPT_HIDDEN_BOOL(s, l, v, h) { OPTION_SET_INT, (s), (l), (v), NULL, \ (h), PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1} -#define OPT_CMDMODE(s, l, v, h, i) { OPTION_CMDMODE, (s), (l), (v), NULL, \ +#define OPT_CMDMODE(s, l, v, h, i) { OPTION_CMDMODE, (s), (l), (v), NULL, \ (h), PARSE_OPT_NOARG|PARSE_OPT_NONEG, NULL, (i) } #define OPT_INTEGER(s, l, v, h) { OPTION_INTEGER, (s), (l), (v), N_("n"), (h) } #define OPT_MAGNITUDE(s, l, v, h) { OPTION_MAGNITUDE, (s), (l), (v), \ @@ -224,11 +224,10 @@ const char *mkpath(const char *fmt, ...) return cleanup_path(pathname->buf); } -const char *git_path_submodule(const char *path, const char *fmt, ...) +static void do_submodule_path(struct strbuf *buf, const char *path, + const char *fmt, va_list args) { - struct strbuf *buf = get_pathname(); const char *git_dir; - va_list args; strbuf_addstr(buf, path); if (buf->len && buf->buf[buf->len - 1] != '/') @@ -242,11 +241,27 @@ const char *git_path_submodule(const char *path, const char *fmt, ...) } strbuf_addch(buf, '/'); - va_start(args, fmt); strbuf_vaddf(buf, fmt, args); - va_end(args); strbuf_cleanup_path(buf); - return buf->buf; +} + +char *git_pathdup_submodule(const char *path, const char *fmt, ...) +{ + va_list args; + struct strbuf buf = STRBUF_INIT; + va_start(args, fmt); + do_submodule_path(&buf, path, fmt, args); + va_end(args); + return strbuf_detach(&buf, NULL); +} + +void strbuf_git_path_submodule(struct strbuf *buf, const char *path, + const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + do_submodule_path(buf, path, fmt, args); + va_end(args); } int validate_headref(const char *path) @@ -918,3 +933,13 @@ char *xdg_config_home(const char *filename) return mkpathdup("%s/.config/git/%s", home, filename); return NULL; } + +GIT_PATH_FUNC(git_path_cherry_pick_head, "CHERRY_PICK_HEAD") +GIT_PATH_FUNC(git_path_revert_head, "REVERT_HEAD") +GIT_PATH_FUNC(git_path_squash_msg, "SQUASH_MSG") +GIT_PATH_FUNC(git_path_merge_msg, "MERGE_MSG") +GIT_PATH_FUNC(git_path_merge_rr, "MERGE_RR") +GIT_PATH_FUNC(git_path_merge_mode, "MERGE_MODE") +GIT_PATH_FUNC(git_path_merge_head, "MERGE_HEAD") +GIT_PATH_FUNC(git_path_fetch_head, "FETCH_HEAD") +GIT_PATH_FUNC(git_path_shallow, "shallow") @@ -10,10 +10,26 @@ coordinates our localization effort in the l10 coordinator repository: https://github.com/git-l10n/git-po/ +The two character language translation codes are defined by ISO_639-1, as +stated in the gettext(1) full manual, appendix A.1, Usual Language Codes. + + +Contributing to an existing translation +--------------------------------------- As a contributor for a language XX, you should first check TEAMS file in this directory to see whether a dedicated repository for your language XX exists. Fork the dedicated repository and start to work if it exists. +Sometime, contributors may find that the translations of their Git +distributions are quite different with the translations of the +corresponding version from Git official. This is because some Git +distributions (such as from Ubuntu, etc.) have their own l10n workflow. +For this case, wrong translations should be reported and fixed through +their workflows. + + +Creating a new language translation +----------------------------------- If you are the first contributor for the language XX, please fork this repository, prepare and/or update the translated message file po/XX.po (described later), and ask the l10n coordinator to pull your work. @@ -23,6 +39,9 @@ coordinate among yourselves and nominate the team leader for your language, so that the l10n coordinator only needs to interact with one person per language. + +Translation Process Flow +------------------------ The overall data-flow looks like this: +-------------------+ +------------------+ diff --git a/read-cache.c b/read-cache.c index 89dbc0837a..ab1bfc94d6 100644 --- a/read-cache.c +++ b/read-cache.c @@ -5,6 +5,7 @@ */ #define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" +#include "tempfile.h" #include "lockfile.h" #include "cache-tree.h" #include "refs.h" @@ -2113,7 +2114,7 @@ static int commit_locked_index(struct lock_file *lk) static int do_write_locked_index(struct index_state *istate, struct lock_file *lock, unsigned flags) { - int ret = do_write_index(istate, lock->fd, 0); + int ret = do_write_index(istate, get_lock_file_fd(lock), 0); if (ret) return ret; assert((flags & (COMMIT_LOCK | CLOSE_LOCK)) != @@ -2137,54 +2138,27 @@ static int write_split_index(struct index_state *istate, return ret; } -static char *temporary_sharedindex; - -static void remove_temporary_sharedindex(void) -{ - if (temporary_sharedindex) { - unlink_or_warn(temporary_sharedindex); - free(temporary_sharedindex); - temporary_sharedindex = NULL; - } -} - -static void remove_temporary_sharedindex_on_signal(int signo) -{ - remove_temporary_sharedindex(); - sigchain_pop(signo); - raise(signo); -} +static struct tempfile temporary_sharedindex; static int write_shared_index(struct index_state *istate, struct lock_file *lock, unsigned flags) { struct split_index *si = istate->split_index; - static int installed_handler; int fd, ret; - temporary_sharedindex = git_pathdup("sharedindex_XXXXXX"); - fd = mkstemp(temporary_sharedindex); + fd = mks_tempfile(&temporary_sharedindex, git_path("sharedindex_XXXXXX")); if (fd < 0) { - free(temporary_sharedindex); - temporary_sharedindex = NULL; hashclr(si->base_sha1); return do_write_locked_index(istate, lock, flags); } - if (!installed_handler) { - atexit(remove_temporary_sharedindex); - sigchain_push_common(remove_temporary_sharedindex_on_signal); - } move_cache_to_base_index(istate); ret = do_write_index(si->base, fd, 1); - close(fd); if (ret) { - remove_temporary_sharedindex(); + delete_tempfile(&temporary_sharedindex); return ret; } - ret = rename(temporary_sharedindex, - git_path("sharedindex.%s", sha1_to_hex(si->base->sha1))); - free(temporary_sharedindex); - temporary_sharedindex = NULL; + ret = rename_tempfile(&temporary_sharedindex, + git_path("sharedindex.%s", sha1_to_hex(si->base->sha1))); if (!ret) hashcpy(si->base_sha1, si->base->sha1); return ret; @@ -19,12 +19,14 @@ struct ref_lock { * 1: End-of-component * 2: ., look for a preceding . to reject .. in refs * 3: {, look for a preceding @ to reject @{ in refs - * 4: A bad character: ASCII control characters, "~", "^", ":" or SP + * 4: A bad character: ASCII control characters, and + * ":", "?", "[", "\", "^", "~", SP, or TAB + * 5: *, reject unless REFNAME_REFSPEC_PATTERN is set */ static unsigned char refname_disposition[256] = { 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 2, 1, + 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 4, 0, @@ -63,6 +65,11 @@ static unsigned char refname_disposition[256] = { #define REF_NEEDS_COMMIT 0x20 /* + * 0x40 is REF_FORCE_CREATE_REFLOG, so skip it if you're adding a + * value to ref_update::flags + */ + +/* * Try to read one refname component from the front of refname. * Return the length of the component found, or -1 if the component is * not legal. It is legal if it is something reasonable to have under @@ -70,12 +77,14 @@ static unsigned char refname_disposition[256] = { * * - any path component of it begins with ".", or * - it has double dots "..", or - * - it has ASCII control character, "~", "^", ":" or SP, anywhere, or - * - it ends with a "/". - * - it ends with ".lock" - * - it contains a "\" (backslash) + * - it has ASCII control characters, or + * - it has ":", "?", "[", "\", "^", "~", SP, or TAB anywhere, or + * - it has "*" anywhere unless REFNAME_REFSPEC_PATTERN is set, or + * - it ends with a "/", or + * - it ends with ".lock", or + * - it contains a "@{" portion */ -static int check_refname_component(const char *refname, int flags) +static int check_refname_component(const char *refname, int *flags) { const char *cp; char last = '\0'; @@ -96,6 +105,16 @@ static int check_refname_component(const char *refname, int flags) break; case 4: return -1; + case 5: + if (!(*flags & REFNAME_REFSPEC_PATTERN)) + return -1; /* refspec can't be a pattern */ + + /* + * Unset the pattern flag so that we only accept + * a single asterisk for one side of refspec. + */ + *flags &= ~ REFNAME_REFSPEC_PATTERN; + break; } last = ch; } @@ -120,18 +139,10 @@ int check_refname_format(const char *refname, int flags) while (1) { /* We are at the start of a path component. */ - component_len = check_refname_component(refname, flags); - if (component_len <= 0) { - if ((flags & REFNAME_REFSPEC_PATTERN) && - refname[0] == '*' && - (refname[1] == '\0' || refname[1] == '/')) { - /* Accept one wildcard as a full refname component. */ - flags &= ~REFNAME_REFSPEC_PATTERN; - component_len = 1; - } else { - return -1; - } - } + component_len = check_refname_component(refname, &flags); + if (component_len <= 0) + return -1; + component_count++; if (refname[component_len] == '\0') break; @@ -1277,12 +1288,12 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir) */ static struct packed_ref_cache *get_packed_ref_cache(struct ref_cache *refs) { - const char *packed_refs_file; + char *packed_refs_file; if (*refs->name) - packed_refs_file = git_path_submodule(refs->name, "packed-refs"); + packed_refs_file = git_pathdup_submodule(refs->name, "packed-refs"); else - packed_refs_file = git_path("packed-refs"); + packed_refs_file = git_pathdup("packed-refs"); if (refs->packed && !stat_validity_check(&refs->packed->validity, packed_refs_file)) @@ -1301,6 +1312,7 @@ static struct packed_ref_cache *get_packed_ref_cache(struct ref_cache *refs) fclose(f); } } + free(packed_refs_file); return refs->packed; } @@ -1340,19 +1352,23 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir) { struct ref_cache *refs = dir->ref_cache; DIR *d; - const char *path; struct dirent *de; int dirnamelen = strlen(dirname); struct strbuf refname; + struct strbuf path = STRBUF_INIT; + size_t path_baselen; if (*refs->name) - path = git_path_submodule(refs->name, "%s", dirname); + strbuf_git_path_submodule(&path, refs->name, "%s", dirname); else - path = git_path("%s", dirname); + strbuf_git_path(&path, "%s", dirname); + path_baselen = path.len; - d = opendir(path); - if (!d) + d = opendir(path.buf); + if (!d) { + strbuf_release(&path); return; + } strbuf_init(&refname, dirnamelen + 257); strbuf_add(&refname, dirname, dirnamelen); @@ -1361,17 +1377,14 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir) unsigned char sha1[20]; struct stat st; int flag; - const char *refdir; if (de->d_name[0] == '.') continue; if (ends_with(de->d_name, ".lock")) continue; strbuf_addstr(&refname, de->d_name); - refdir = *refs->name - ? git_path_submodule(refs->name, "%s", refname.buf) - : git_path("%s", refname.buf); - if (stat(refdir, &st) < 0) { + strbuf_addstr(&path, de->d_name); + if (stat(path.buf, &st) < 0) { ; /* silently ignore */ } else if (S_ISDIR(st.st_mode)) { strbuf_addch(&refname, '/'); @@ -1418,8 +1431,10 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir) create_ref_entry(refname.buf, sha1, flag, 0)); } strbuf_setlen(&refname, dirnamelen); + strbuf_setlen(&path, path_baselen); } strbuf_release(&refname); + strbuf_release(&path); closedir(d); } @@ -1470,14 +1485,15 @@ static int resolve_gitlink_ref_recursive(struct ref_cache *refs, { int fd, len; char buffer[128], *p; - const char *path; + char *path; if (recursion > MAXDEPTH || strlen(refname) > MAXREFLEN) return -1; path = *refs->name - ? git_path_submodule(refs->name, "%s", refname) - : git_path("%s", refname); + ? git_pathdup_submodule(refs->name, "%s", refname) + : git_pathdup("%s", refname); fd = open(path, O_RDONLY); + free(path); if (fd < 0) return resolve_gitlink_packed_ref(refs, refname, sha1); @@ -2274,25 +2290,14 @@ static int verify_lock(struct ref_lock *lock, return 0; } -static int remove_empty_directories(const char *file) +static int remove_empty_directories(struct strbuf *path) { - /* we want to create a file but there is a directory there; + /* + * we want to create a file but there is a directory there; * if that is an empty directory (or a directory that contains * only empty directories), remove them. */ - struct strbuf path; - int result, save_errno; - - strbuf_init(&path, 20); - strbuf_addstr(&path, file); - - result = remove_dir_recursively(&path, REMOVE_DIR_EMPTY_ONLY); - save_errno = errno; - - strbuf_release(&path); - errno = save_errno; - - return result; + return remove_dir_recursively(path, REMOVE_DIR_EMPTY_ONLY); } /* @@ -2392,7 +2397,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, unsigned int flags, int *type_p, struct strbuf *err) { - const char *ref_file; + struct strbuf ref_file = STRBUF_INIT; + struct strbuf orig_ref_file = STRBUF_INIT; const char *orig_refname = refname; struct ref_lock *lock; int last_errno = 0; @@ -2416,20 +2422,19 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, refname = resolve_ref_unsafe(refname, resolve_flags, lock->old_oid.hash, &type); if (!refname && errno == EISDIR) { - /* we are trying to lock foo but we used to + /* + * we are trying to lock foo but we used to * have foo/bar which now does not exist; * it is normal for the empty directory 'foo' * to remain. */ - ref_file = git_path("%s", orig_refname); - if (remove_empty_directories(ref_file)) { + strbuf_git_path(&orig_ref_file, "%s", orig_refname); + if (remove_empty_directories(&orig_ref_file)) { last_errno = errno; - if (!verify_refname_available(orig_refname, extras, skip, get_loose_refs(&ref_cache), err)) strbuf_addf(err, "there are still refs under '%s'", orig_refname); - goto error_return; } refname = resolve_ref_unsafe(orig_refname, resolve_flags, @@ -2469,10 +2474,10 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, } lock->ref_name = xstrdup(refname); lock->orig_ref_name = xstrdup(orig_refname); - ref_file = git_path("%s", refname); + strbuf_git_path(&ref_file, "%s", refname); retry: - switch (safe_create_leading_directories_const(ref_file)) { + switch (safe_create_leading_directories_const(ref_file.buf)) { case SCLD_OK: break; /* success */ case SCLD_VANISHED: @@ -2481,11 +2486,12 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, /* fall through */ default: last_errno = errno; - strbuf_addf(err, "unable to create directory for %s", ref_file); + strbuf_addf(err, "unable to create directory for %s", + ref_file.buf); goto error_return; } - if (hold_lock_file_for_update(lock->lk, ref_file, lflags) < 0) { + if (hold_lock_file_for_update(lock->lk, ref_file.buf, lflags) < 0) { last_errno = errno; if (errno == ENOENT && --attempts_remaining > 0) /* @@ -2495,7 +2501,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, */ goto retry; else { - unable_to_lock_message(ref_file, errno, err); + unable_to_lock_message(ref_file.buf, errno, err); goto error_return; } } @@ -2503,12 +2509,17 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, last_errno = errno; goto error_return; } - return lock; + goto out; error_return: unlock_ref(lock); + lock = NULL; + + out: + strbuf_release(&ref_file); + strbuf_release(&orig_ref_file); errno = last_errno; - return NULL; + return lock; } /* @@ -2843,12 +2854,117 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err) return 0; } +static int is_per_worktree_ref(const char *refname) +{ + return !strcmp(refname, "HEAD"); +} + +static int is_pseudoref_syntax(const char *refname) +{ + const char *c; + + for (c = refname; *c; c++) { + if (!isupper(*c) && *c != '-' && *c != '_') + return 0; + } + + return 1; +} + +enum ref_type ref_type(const char *refname) +{ + if (is_per_worktree_ref(refname)) + return REF_TYPE_PER_WORKTREE; + if (is_pseudoref_syntax(refname)) + return REF_TYPE_PSEUDOREF; + return REF_TYPE_NORMAL; +} + +static int write_pseudoref(const char *pseudoref, const unsigned char *sha1, + const unsigned char *old_sha1, struct strbuf *err) +{ + const char *filename; + int fd; + static struct lock_file lock; + struct strbuf buf = STRBUF_INIT; + int ret = -1; + + strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1)); + + filename = git_path("%s", pseudoref); + fd = hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR); + if (fd < 0) { + strbuf_addf(err, "Could not open '%s' for writing: %s", + filename, strerror(errno)); + return -1; + } + + if (old_sha1) { + unsigned char actual_old_sha1[20]; + + if (read_ref(pseudoref, actual_old_sha1)) + die("could not read ref '%s'", pseudoref); + if (hashcmp(actual_old_sha1, old_sha1)) { + strbuf_addf(err, "Unexpected sha1 when writing %s", pseudoref); + rollback_lock_file(&lock); + goto done; + } + } + + if (write_in_full(fd, buf.buf, buf.len) != buf.len) { + strbuf_addf(err, "Could not write to '%s'", filename); + rollback_lock_file(&lock); + goto done; + } + + commit_lock_file(&lock); + ret = 0; +done: + strbuf_release(&buf); + return ret; +} + +static int delete_pseudoref(const char *pseudoref, const unsigned char *old_sha1) +{ + static struct lock_file lock; + const char *filename; + + filename = git_path("%s", pseudoref); + + if (old_sha1 && !is_null_sha1(old_sha1)) { + int fd; + unsigned char actual_old_sha1[20]; + + fd = hold_lock_file_for_update(&lock, filename, + LOCK_DIE_ON_ERROR); + if (fd < 0) + die_errno(_("Could not open '%s' for writing"), filename); + if (read_ref(pseudoref, actual_old_sha1)) + die("could not read ref '%s'", pseudoref); + if (hashcmp(actual_old_sha1, old_sha1)) { + warning("Unexpected sha1 when deleting %s", pseudoref); + rollback_lock_file(&lock); + return -1; + } + + unlink(filename); + rollback_lock_file(&lock); + } else { + unlink(filename); + } + + return 0; +} + int delete_ref(const char *refname, const unsigned char *old_sha1, unsigned int flags) { struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; + if (ref_type(refname) == REF_TYPE_PSEUDOREF) + return delete_pseudoref(refname, old_sha1); + transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_delete(transaction, refname, old_sha1, @@ -2914,9 +3030,13 @@ out: static int rename_tmp_log(const char *newrefname) { int attempts_remaining = 4; + struct strbuf path = STRBUF_INIT; + int ret = -1; retry: - switch (safe_create_leading_directories_const(git_path("logs/%s", newrefname))) { + strbuf_reset(&path); + strbuf_git_path(&path, "logs/%s", newrefname); + switch (safe_create_leading_directories_const(path.buf)) { case SCLD_OK: break; /* success */ case SCLD_VANISHED: @@ -2925,19 +3045,19 @@ static int rename_tmp_log(const char *newrefname) /* fall through */ default: error("unable to create directory for %s", newrefname); - return -1; + goto out; } - if (rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newrefname))) { + if (rename(git_path(TMP_RENAMED_LOG), path.buf)) { if ((errno==EISDIR || errno==ENOTDIR) && --attempts_remaining > 0) { /* * rename(a, b) when b is an existing * directory ought to result in ISDIR, but * Solaris 5.8 gives ENOTDIR. Sheesh. */ - if (remove_empty_directories(git_path("logs/%s", newrefname))) { + if (remove_empty_directories(&path)) { error("Directory not empty: logs/%s", newrefname); - return -1; + goto out; } goto retry; } else if (errno == ENOENT && --attempts_remaining > 0) { @@ -2950,10 +3070,13 @@ static int rename_tmp_log(const char *newrefname) } else { error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s", newrefname, strerror(errno)); - return -1; + goto out; } } - return 0; + ret = 0; +out: + strbuf_release(&path); + return ret; } static int rename_ref_available(const char *oldname, const char *newname) @@ -2975,9 +3098,11 @@ static int rename_ref_available(const char *oldname, const char *newname) return ret; } -static int write_ref_to_lockfile(struct ref_lock *lock, const unsigned char *sha1); +static int write_ref_to_lockfile(struct ref_lock *lock, + const unsigned char *sha1, struct strbuf *err); static int commit_ref_update(struct ref_lock *lock, - const unsigned char *sha1, const char *logmsg); + const unsigned char *sha1, const char *logmsg, + int flags, struct strbuf *err); int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg) { @@ -3015,7 +3140,14 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms if (!read_ref_full(newrefname, RESOLVE_REF_READING, sha1, NULL) && delete_ref(newrefname, sha1, REF_NODEREF)) { if (errno==EISDIR) { - if (remove_empty_directories(git_path("%s", newrefname))) { + struct strbuf path = STRBUF_INIT; + int result; + + strbuf_git_path(&path, "%s", newrefname); + result = remove_empty_directories(&path); + strbuf_release(&path); + + if (result) { error("Directory not empty: %s", newrefname); goto rollback; } @@ -3038,9 +3170,10 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms } hashcpy(lock->old_oid.hash, orig_sha1); - if (write_ref_to_lockfile(lock, orig_sha1) || - commit_ref_update(lock, orig_sha1, logmsg)) { - error("unable to write current sha1 into %s", newrefname); + if (write_ref_to_lockfile(lock, orig_sha1, &err) || + commit_ref_update(lock, orig_sha1, logmsg, 0, &err)) { + error("unable to write current sha1 into %s: %s", newrefname, err.buf); + strbuf_release(&err); goto rollback; } @@ -3056,9 +3189,11 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms flag = log_all_ref_updates; log_all_ref_updates = 0; - if (write_ref_to_lockfile(lock, orig_sha1) || - commit_ref_update(lock, orig_sha1, NULL)) - error("unable to write current sha1 into %s", oldrefname); + if (write_ref_to_lockfile(lock, orig_sha1, &err) || + commit_ref_update(lock, orig_sha1, NULL, 0, &err)) { + error("unable to write current sha1 into %s: %s", oldrefname, err.buf); + strbuf_release(&err); + } log_all_ref_updates = flag; rollbacklog: @@ -3113,60 +3248,73 @@ static int copy_msg(char *buf, const char *msg) return cp - buf; } -/* This function must set a meaningful errno on failure */ -int log_ref_setup(const char *refname, struct strbuf *sb_logfile) +static int should_autocreate_reflog(const char *refname) +{ + if (!log_all_ref_updates) + return 0; + return starts_with(refname, "refs/heads/") || + starts_with(refname, "refs/remotes/") || + starts_with(refname, "refs/notes/") || + !strcmp(refname, "HEAD"); +} + +/* + * Create a reflog for a ref. If force_create = 0, the reflog will + * only be created for certain refs (those for which + * should_autocreate_reflog returns non-zero. Otherwise, create it + * regardless of the ref name. Fill in *err and return -1 on failure. + */ +static int log_ref_setup(const char *refname, struct strbuf *logfile, struct strbuf *err, int force_create) { int logfd, oflags = O_APPEND | O_WRONLY; - char *logfile; - - strbuf_git_path(sb_logfile, "logs/%s", refname); - logfile = sb_logfile->buf; - /* make sure the rest of the function can't change "logfile" */ - sb_logfile = NULL; - if (log_all_ref_updates && - (starts_with(refname, "refs/heads/") || - starts_with(refname, "refs/remotes/") || - starts_with(refname, "refs/notes/") || - !strcmp(refname, "HEAD"))) { - if (safe_create_leading_directories(logfile) < 0) { - int save_errno = errno; - error("unable to create directory for %s", logfile); - errno = save_errno; + + strbuf_git_path(logfile, "logs/%s", refname); + if (force_create || should_autocreate_reflog(refname)) { + if (safe_create_leading_directories(logfile->buf) < 0) { + strbuf_addf(err, "unable to create directory for %s: " + "%s", logfile->buf, strerror(errno)); return -1; } oflags |= O_CREAT; } - logfd = open(logfile, oflags, 0666); + logfd = open(logfile->buf, oflags, 0666); if (logfd < 0) { if (!(oflags & O_CREAT) && (errno == ENOENT || errno == EISDIR)) return 0; if (errno == EISDIR) { if (remove_empty_directories(logfile)) { - int save_errno = errno; - error("There are still logs under '%s'", - logfile); - errno = save_errno; + strbuf_addf(err, "There are still logs under " + "'%s'", logfile->buf); return -1; } - logfd = open(logfile, oflags, 0666); + logfd = open(logfile->buf, oflags, 0666); } if (logfd < 0) { - int save_errno = errno; - error("Unable to append to %s: %s", logfile, - strerror(errno)); - errno = save_errno; + strbuf_addf(err, "unable to append to %s: %s", + logfile->buf, strerror(errno)); return -1; } } - adjust_shared_perm(logfile); + adjust_shared_perm(logfile->buf); close(logfd); return 0; } + +int safe_create_reflog(const char *refname, int force_create, struct strbuf *err) +{ + int ret; + struct strbuf sb = STRBUF_INIT; + + ret = log_ref_setup(refname, &sb, err, force_create); + strbuf_release(&sb); + return ret; +} + static int log_ref_write_fd(int fd, const unsigned char *old_sha1, const unsigned char *new_sha1, const char *committer, const char *msg) @@ -3195,47 +3343,45 @@ static int log_ref_write_fd(int fd, const unsigned char *old_sha1, static int log_ref_write_1(const char *refname, const unsigned char *old_sha1, const unsigned char *new_sha1, const char *msg, - struct strbuf *sb_log_file) + struct strbuf *logfile, int flags, + struct strbuf *err) { int logfd, result, oflags = O_APPEND | O_WRONLY; - char *log_file; if (log_all_ref_updates < 0) log_all_ref_updates = !is_bare_repository(); - result = log_ref_setup(refname, sb_log_file); + result = log_ref_setup(refname, logfile, err, flags & REF_FORCE_CREATE_REFLOG); + if (result) return result; - log_file = sb_log_file->buf; - /* make sure the rest of the function can't change "log_file" */ - sb_log_file = NULL; - logfd = open(log_file, oflags); + logfd = open(logfile->buf, oflags); if (logfd < 0) return 0; result = log_ref_write_fd(logfd, old_sha1, new_sha1, git_committer_info(0), msg); if (result) { - int save_errno = errno; + strbuf_addf(err, "unable to append to %s: %s", logfile->buf, + strerror(errno)); close(logfd); - error("Unable to append to %s", log_file); - errno = save_errno; return -1; } if (close(logfd)) { - int save_errno = errno; - error("Unable to append to %s", log_file); - errno = save_errno; + strbuf_addf(err, "unable to append to %s: %s", logfile->buf, + strerror(errno)); return -1; } return 0; } static int log_ref_write(const char *refname, const unsigned char *old_sha1, - const unsigned char *new_sha1, const char *msg) + const unsigned char *new_sha1, const char *msg, + int flags, struct strbuf *err) { struct strbuf sb = STRBUF_INIT; - int ret = log_ref_write_1(refname, old_sha1, new_sha1, msg, &sb); + int ret = log_ref_write_1(refname, old_sha1, new_sha1, msg, &sb, flags, + err); strbuf_release(&sb); return ret; } @@ -3247,36 +3393,38 @@ int is_branch(const char *refname) /* * Write sha1 into the open lockfile, then close the lockfile. On - * errors, rollback the lockfile and set errno to reflect the problem. + * errors, rollback the lockfile, fill in *err and + * return -1. */ static int write_ref_to_lockfile(struct ref_lock *lock, - const unsigned char *sha1) + const unsigned char *sha1, struct strbuf *err) { static char term = '\n'; struct object *o; + int fd; o = parse_object(sha1); if (!o) { - error("Trying to write ref %s with nonexistent object %s", - lock->ref_name, sha1_to_hex(sha1)); + strbuf_addf(err, + "Trying to write ref %s with nonexistent object %s", + lock->ref_name, sha1_to_hex(sha1)); unlock_ref(lock); - errno = EINVAL; return -1; } if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) { - error("Trying to write non-commit object %s to branch %s", - sha1_to_hex(sha1), lock->ref_name); + strbuf_addf(err, + "Trying to write non-commit object %s to branch %s", + sha1_to_hex(sha1), lock->ref_name); unlock_ref(lock); - errno = EINVAL; return -1; } - if (write_in_full(lock->lk->fd, sha1_to_hex(sha1), 40) != 40 || - write_in_full(lock->lk->fd, &term, 1) != 1 || + fd = get_lock_file_fd(lock->lk); + if (write_in_full(fd, sha1_to_hex(sha1), 40) != 40 || + write_in_full(fd, &term, 1) != 1 || close_ref(lock) < 0) { - int save_errno = errno; - error("Couldn't write %s", lock->lk->filename.buf); + strbuf_addf(err, + "Couldn't write %s", get_lock_file_path(lock->lk)); unlock_ref(lock); - errno = save_errno; return -1; } return 0; @@ -3288,12 +3436,17 @@ static int write_ref_to_lockfile(struct ref_lock *lock, * necessary, using the specified lockmsg (which can be NULL). */ static int commit_ref_update(struct ref_lock *lock, - const unsigned char *sha1, const char *logmsg) + const unsigned char *sha1, const char *logmsg, + int flags, struct strbuf *err) { clear_loose_ref_cache(&ref_cache); - if (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg) < 0 || + if (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg, flags, err) < 0 || (strcmp(lock->ref_name, lock->orig_ref_name) && - log_ref_write(lock->orig_ref_name, lock->old_oid.hash, sha1, logmsg) < 0)) { + log_ref_write(lock->orig_ref_name, lock->old_oid.hash, sha1, logmsg, flags, err) < 0)) { + char *old_msg = strbuf_detach(err, NULL); + strbuf_addf(err, "Cannot update the ref '%s': %s", + lock->ref_name, old_msg); + free(old_msg); unlock_ref(lock); return -1; } @@ -3316,14 +3469,21 @@ static int commit_ref_update(struct ref_lock *lock, head_ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_sha1, &head_flag); if (head_ref && (head_flag & REF_ISSYMREF) && - !strcmp(head_ref, lock->ref_name)) - log_ref_write("HEAD", lock->old_oid.hash, sha1, logmsg); + !strcmp(head_ref, lock->ref_name)) { + struct strbuf log_err = STRBUF_INIT; + if (log_ref_write("HEAD", lock->old_oid.hash, sha1, + logmsg, 0, &log_err)) { + error("%s", log_err.buf); + strbuf_release(&log_err); + } + } } if (commit_ref(lock)) { error("Couldn't set %s", lock->ref_name); unlock_ref(lock); return -1; } + unlock_ref(lock); return 0; } @@ -3331,11 +3491,12 @@ static int commit_ref_update(struct ref_lock *lock, int create_symref(const char *ref_target, const char *refs_heads_master, const char *logmsg) { - const char *lockpath; + char *lockpath = NULL; char ref[1000]; int fd, len, written; char *git_HEAD = git_pathdup("%s", ref_target); unsigned char old_sha1[20], new_sha1[20]; + struct strbuf err = STRBUF_INIT; if (logmsg && read_ref(ref_target, old_sha1)) hashclr(old_sha1); @@ -3357,7 +3518,7 @@ int create_symref(const char *ref_target, const char *refs_heads_master, error("refname too long: %s", refs_heads_master); goto error_free_return; } - lockpath = mkpath("%s.lock", git_HEAD); + lockpath = mkpathdup("%s.lock", git_HEAD); fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666); if (fd < 0) { error("Unable to open %s for writing", lockpath); @@ -3377,15 +3538,20 @@ int create_symref(const char *ref_target, const char *refs_heads_master, error_unlink_return: unlink_or_warn(lockpath); error_free_return: + free(lockpath); free(git_HEAD); return -1; } + free(lockpath); #ifndef NO_SYMLINK_HEAD done: #endif - if (logmsg && !read_ref(refs_heads_master, new_sha1)) - log_ref_write(ref_target, old_sha1, new_sha1, logmsg); + if (logmsg && !read_ref(refs_heads_master, new_sha1) && + log_ref_write(ref_target, old_sha1, new_sha1, logmsg, 0, &err)) { + error("%s", err.buf); + strbuf_release(&err); + } free(git_HEAD); return 0; @@ -3902,17 +4068,25 @@ int update_ref(const char *msg, const char *refname, const unsigned char *new_sha1, const unsigned char *old_sha1, unsigned int flags, enum action_on_err onerr) { - struct ref_transaction *t; + struct ref_transaction *t = NULL; struct strbuf err = STRBUF_INIT; + int ret = 0; - t = ref_transaction_begin(&err); - if (!t || - ref_transaction_update(t, refname, new_sha1, old_sha1, - flags, msg, &err) || - ref_transaction_commit(t, &err)) { + if (ref_type(refname) == REF_TYPE_PSEUDOREF) { + ret = write_pseudoref(refname, new_sha1, old_sha1, &err); + } else { + t = ref_transaction_begin(&err); + if (!t || + ref_transaction_update(t, refname, new_sha1, old_sha1, + flags, msg, &err) || + ref_transaction_commit(t, &err)) { + ret = 1; + ref_transaction_free(t); + } + } + if (ret) { const char *str = "update_ref failed for ref '%s': %s"; - ref_transaction_free(t); switch (onerr) { case UPDATE_REFS_MSG_ON_ERR: error(str, refname, err.buf); @@ -3927,7 +4101,8 @@ int update_ref(const char *msg, const char *refname, return 1; } strbuf_release(&err); - ref_transaction_free(t); + if (t) + ref_transaction_free(t); return 0; } @@ -4021,14 +4196,19 @@ int ref_transaction_commit(struct ref_transaction *transaction, * value, so we don't need to write it. */ } else if (write_ref_to_lockfile(update->lock, - update->new_sha1)) { + update->new_sha1, + err)) { + char *write_err = strbuf_detach(err, NULL); + /* * The lock was freed upon failure of * write_ref_to_lockfile(): */ update->lock = NULL; - strbuf_addf(err, "cannot update the ref '%s'.", - update->refname); + strbuf_addf(err, + "cannot update the ref '%s': %s", + update->refname, write_err); + free(write_err); ret = TRANSACTION_GENERIC_ERROR; goto cleanup; } else { @@ -4054,11 +4234,10 @@ int ref_transaction_commit(struct ref_transaction *transaction, if (update->flags & REF_NEEDS_COMMIT) { if (commit_ref_update(update->lock, - update->new_sha1, update->msg)) { + update->new_sha1, update->msg, + update->flags, err)) { /* freed by commit_ref_update(): */ update->lock = NULL; - strbuf_addf(err, "Cannot update the ref '%s'.", - update->refname); ret = TRANSACTION_GENERIC_ERROR; goto cleanup; } else { @@ -4316,17 +4495,25 @@ int parse_hide_refs_config(const char *var, const char *value, const char *secti int ref_is_hidden(const char *refname) { - struct string_list_item *item; + int i; if (!hide_refs) return 0; - for_each_string_list_item(item, hide_refs) { + for (i = hide_refs->nr - 1; i >= 0; i--) { + const char *match = hide_refs->items[i].string; + int neg = 0; int len; - if (!starts_with(refname, item->string)) + + if (*match == '!') { + neg = 1; + match++; + } + + if (!starts_with(refname, match)) continue; - len = strlen(item->string); + len = strlen(match); if (!refname[len] || refname[len] == '/') - return 1; + return !neg; } return 0; } @@ -4423,7 +4610,7 @@ int reflog_expire(const char *refname, const unsigned char *sha1, cb.newlog = fdopen_lock_file(&reflog_lock, "w"); if (!cb.newlog) { error("cannot fdopen %s (%s)", - reflog_lock.filename.buf, strerror(errno)); + get_lock_file_path(&reflog_lock), strerror(errno)); goto failure; } } @@ -4448,12 +4635,12 @@ int reflog_expire(const char *refname, const unsigned char *sha1, status |= error("couldn't write %s: %s", log_file, strerror(errno)); } else if (update && - (write_in_full(lock->lk->fd, + (write_in_full(get_lock_file_fd(lock->lk), sha1_to_hex(cb.last_kept_sha1), 40) != 40 || - write_str_in_full(lock->lk->fd, "\n") != 1 || - close_ref(lock) < 0)) { + write_str_in_full(get_lock_file_fd(lock->lk), "\n") != 1 || + close_ref(lock) < 0)) { status |= error("couldn't write %s", - lock->lk->filename.buf); + get_lock_file_path(lock->lk)); rollback_lock_file(&reflog_lock); } else if (commit_lock_file(&reflog_lock)) { status |= error("unable to commit reflog '%s' (%s)", @@ -224,11 +224,12 @@ int pack_refs(unsigned int flags); * Other flags are reserved for internal use. */ #define REF_NODEREF 0x01 +#define REF_FORCE_CREATE_REFLOG 0x40 /* - * Setup reflog before using. Set errno to something meaningful on failure. + * Setup reflog before using. Fill in err and return -1 on failure. */ -int log_ref_setup(const char *refname, struct strbuf *logfile); +int safe_create_reflog(const char *refname, int force_create, struct strbuf *err); /** Reads log for the value of ref during at_time. **/ extern int read_ref_at(const char *refname, unsigned int flags, @@ -278,8 +279,8 @@ extern int for_each_reflog(each_ref_fn, void *); * to the rules described in Documentation/git-check-ref-format.txt. * If REFNAME_ALLOW_ONELEVEL is set in flags, then accept one-level * reference names. If REFNAME_REFSPEC_PATTERN is set in flags, then - * allow a "*" wildcard character in place of one of the name - * components. No leading or repeated slashes are accepted. + * allow a single "*" wildcard character in the refspec. No leading or + * repeated slashes are accepted. */ extern int check_refname_format(const char *refname, int flags); @@ -444,6 +445,14 @@ extern int parse_hide_refs_config(const char *var, const char *value, const char extern int ref_is_hidden(const char *); +enum ref_type { + REF_TYPE_PER_WORKTREE, + REF_TYPE_PSEUDOREF, + REF_TYPE_NORMAL, +}; + +enum ref_type ref_type(const char *refname); + enum expire_reflog_flags { EXPIRE_REFLOGS_DRY_RUN = 1 << 0, EXPIRE_REFLOGS_UPDATE_REF = 1 << 1, @@ -20,8 +20,6 @@ static int rerere_enabled = -1; /* automatically update cleanly resolved paths to the index */ static int rerere_autoupdate; -static char *merge_rr_path; - const char *rerere_path(const char *hex, const char *file) { return git_path("rr-cache/%s/%s", hex, file); @@ -37,7 +35,7 @@ static void read_rr(struct string_list *rr) { unsigned char sha1[20]; char buf[PATH_MAX]; - FILE *in = fopen(merge_rr_path, "r"); + FILE *in = fopen(git_path_merge_rr(), "r"); if (!in) return; while (fread(buf, 40, 1, in) == 1) { @@ -577,21 +575,21 @@ static void git_rerere_config(void) git_config(git_default_config, NULL); } +static GIT_PATH_FUNC(git_path_rr_cache, "rr-cache") + static int is_rerere_enabled(void) { - const char *rr_cache; int rr_cache_exists; if (!rerere_enabled) return 0; - rr_cache = git_path("rr-cache"); - rr_cache_exists = is_directory(rr_cache); + rr_cache_exists = is_directory(git_path_rr_cache()); if (rerere_enabled < 0) return rr_cache_exists; - if (!rr_cache_exists && mkdir_in_gitdir(rr_cache)) - die("Could not create directory %s", rr_cache); + if (!rr_cache_exists && mkdir_in_gitdir(git_path_rr_cache())) + die("Could not create directory %s", git_path_rr_cache()); return 1; } @@ -605,8 +603,7 @@ int setup_rerere(struct string_list *merge_rr, int flags) if (flags & (RERERE_AUTOUPDATE|RERERE_NOAUTOUPDATE)) rerere_autoupdate = !!(flags & RERERE_AUTOUPDATE); - merge_rr_path = git_pathdup("MERGE_RR"); - fd = hold_lock_file_for_update(&write_lock, merge_rr_path, + fd = hold_lock_file_for_update(&write_lock, git_path_merge_rr(), LOCK_DIE_ON_ERROR); read_rr(merge_rr); return fd; @@ -741,5 +738,5 @@ void rerere_clear(struct string_list *merge_rr) if (!has_rerere_resolution(name)) unlink_rr_item(name); } - unlink_or_warn(git_path("MERGE_RR")); + unlink_or_warn(git_path_merge_rr()); } diff --git a/revision.c b/revision.c index cf60c5d817..5350139599 100644 --- a/revision.c +++ b/revision.c @@ -18,9 +18,13 @@ #include "commit-slab.h" #include "dir.h" #include "cache-tree.h" +#include "bisect.h" volatile show_early_output_fn_t show_early_output; +static const char *term_bad; +static const char *term_good; + char *path_name(const struct name_path *path, const char *name) { const struct name_path *p; @@ -2076,14 +2080,23 @@ void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx, ctx->argc -= n; } +static int for_each_bisect_ref(const char *submodule, each_ref_fn fn, void *cb_data, const char *term) { + struct strbuf bisect_refs = STRBUF_INIT; + int status; + strbuf_addf(&bisect_refs, "refs/bisect/%s", term); + status = for_each_ref_in_submodule(submodule, bisect_refs.buf, fn, cb_data); + strbuf_release(&bisect_refs); + return status; +} + static int for_each_bad_bisect_ref(const char *submodule, each_ref_fn fn, void *cb_data) { - return for_each_ref_in_submodule(submodule, "refs/bisect/bad", fn, cb_data); + return for_each_bisect_ref(submodule, fn, cb_data, term_bad); } static int for_each_good_bisect_ref(const char *submodule, each_ref_fn fn, void *cb_data) { - return for_each_ref_in_submodule(submodule, "refs/bisect/good", fn, cb_data); + return for_each_bisect_ref(submodule, fn, cb_data, term_good); } static int handle_revision_pseudo_opt(const char *submodule, @@ -2112,6 +2125,7 @@ static int handle_revision_pseudo_opt(const char *submodule, handle_refs(submodule, revs, *flags, for_each_branch_ref_submodule); clear_ref_exclusion(&revs->ref_excludes); } else if (!strcmp(arg, "--bisect")) { + read_bisect_terms(&term_bad, &term_good); handle_refs(submodule, revs, *flags, for_each_bad_bisect_ref); handle_refs(submodule, revs, *flags ^ (UNINTERESTING | BOTTOM), for_each_good_bisect_ref); revs->bisect = 1; diff --git a/run-command.c b/run-command.c index 4d73e90fad..3277cf797e 100644 --- a/run-command.c +++ b/run-command.c @@ -200,7 +200,6 @@ static int execv_shell_cmd(const char **argv) #endif #ifndef GIT_WINDOWS_NATIVE -static int child_err = 2; static int child_notifier = -1; static void notify_parent(void) @@ -212,17 +211,6 @@ static void notify_parent(void) */ xwrite(child_notifier, "", 1); } - -static NORETURN void die_child(const char *err, va_list params) -{ - vwritef(child_err, "fatal: ", err, params); - exit(128); -} - -static void error_child(const char *err, va_list params) -{ - vwritef(child_err, "error: ", err, params); -} #endif static inline void set_cloexec(int fd) @@ -362,11 +350,10 @@ fail_pipe: * in subsequent call paths use the parent's stderr. */ if (cmd->no_stderr || need_err) { - child_err = dup(2); + int child_err = dup(2); set_cloexec(child_err); + set_error_handle(fdopen(child_err, "w")); } - set_die_routine(die_child); - set_error_routine(error_child); close(notify_pipe[0]); set_cloexec(notify_pipe[1]); @@ -797,11 +784,13 @@ int finish_async(struct async *async) const char *find_hook(const char *name) { - const char *path = git_path("hooks/%s", name); - if (access(path, X_OK) < 0) - path = NULL; + static struct strbuf path = STRBUF_INIT; - return path; + strbuf_reset(&path); + strbuf_git_path(&path, "hooks/%s", name); + if (access(path.buf, X_OK) < 0) + return NULL; + return path.buf; } int run_hook_ve(const char *const *env, const char *name, va_list args) diff --git a/run-command.h b/run-command.h index 1103805af1..5b4425a3cb 100644 --- a/run-command.h +++ b/run-command.h @@ -52,6 +52,11 @@ int start_command(struct child_process *); int finish_command(struct child_process *); int run_command(struct child_process *); +/* + * Returns the path to the hook file, or NULL if the hook is missing + * or disabled. Note that this points to static storage that will be + * overwritten by further calls to find_hook and run_hook_*. + */ extern const char *find_hook(const char *name); LAST_ARG_MUST_BE_NULL extern int run_hook_le(const char *const *env, const char *name, ...); diff --git a/sequencer.c b/sequencer.c index c4f4b7d571..a0600aebca 100644 --- a/sequencer.c +++ b/sequencer.c @@ -21,6 +21,11 @@ const char sign_off_header[] = "Signed-off-by: "; static const char cherry_picked_prefix[] = "(cherry picked from commit "; +static GIT_PATH_FUNC(git_path_todo_file, SEQ_TODO_FILE) +static GIT_PATH_FUNC(git_path_opts_file, SEQ_OPTS_FILE) +static GIT_PATH_FUNC(git_path_seq_dir, SEQ_DIR) +static GIT_PATH_FUNC(git_path_head_file, SEQ_HEAD_FILE) + static int is_rfc2822_line(const char *buf, int len) { int i; @@ -158,23 +163,6 @@ static void free_message(struct commit *commit, struct commit_message *msg) unuse_commit_buffer(commit, msg->message); } -static void write_cherry_pick_head(struct commit *commit, const char *pseudoref) -{ - const char *filename; - int fd; - struct strbuf buf = STRBUF_INIT; - - strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1)); - - filename = git_path("%s", pseudoref); - fd = open(filename, O_WRONLY | O_CREAT, 0666); - if (fd < 0) - die_errno(_("Could not open '%s' for writing"), filename); - if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd)) - die_errno(_("Could not write to '%s'"), filename); - strbuf_release(&buf); -} - static void print_advice(int show_hint, struct replay_opts *opts) { char *msg = getenv("GIT_CHERRY_PICK_HELP"); @@ -186,7 +174,7 @@ static void print_advice(int show_hint, struct replay_opts *opts) * (typically rebase --interactive) wants to take care * of the commit itself so remove CHERRY_PICK_HEAD */ - unlink(git_path("CHERRY_PICK_HEAD")); + unlink(git_path_cherry_pick_head()); return; } @@ -467,7 +455,6 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) struct commit *base, *next, *parent; const char *base_label, *next_label; struct commit_message msg = { NULL, NULL, NULL, NULL }; - char *defmsg = NULL; struct strbuf msgbuf = STRBUF_INIT; int res, unborn = 0, allow; @@ -537,8 +524,6 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) * reverse of it if we are revert. */ - defmsg = git_pathdup("MERGE_MSG"); - if (opts->action == REPLAY_REVERT) { base = commit; base_label = msg.label; @@ -585,12 +570,12 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) { res = do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); - write_message(&msgbuf, defmsg); + write_message(&msgbuf, git_path_merge_msg()); } else { struct commit_list *common = NULL; struct commit_list *remotes = NULL; - write_message(&msgbuf, defmsg); + write_message(&msgbuf, git_path_merge_msg()); commit_list_insert(base, &common); commit_list_insert(next, &remotes); @@ -607,9 +592,11 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) * write it at all. */ if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1)) - write_cherry_pick_head(commit, "CHERRY_PICK_HEAD"); + update_ref(NULL, "CHERRY_PICK_HEAD", commit->object.sha1, NULL, + REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1)) - write_cherry_pick_head(commit, "REVERT_HEAD"); + update_ref(NULL, "REVERT_HEAD", commit->object.sha1, NULL, + REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); if (res) { error(opts->action == REPLAY_REVERT @@ -628,11 +615,10 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) goto leave; } if (!opts->no_commit) - res = run_git_commit(defmsg, opts, allow); + res = run_git_commit(git_path_merge_msg(), opts, allow); leave: free_message(commit, &msg); - free(defmsg); return res; } @@ -756,24 +742,23 @@ static int parse_insn_buffer(char *buf, struct commit_list **todo_list, static void read_populate_todo(struct commit_list **todo_list, struct replay_opts *opts) { - const char *todo_file = git_path(SEQ_TODO_FILE); struct strbuf buf = STRBUF_INIT; int fd, res; - fd = open(todo_file, O_RDONLY); + fd = open(git_path_todo_file(), O_RDONLY); if (fd < 0) - die_errno(_("Could not open %s"), todo_file); + die_errno(_("Could not open %s"), git_path_todo_file()); if (strbuf_read(&buf, fd, 0) < 0) { close(fd); strbuf_release(&buf); - die(_("Could not read %s."), todo_file); + die(_("Could not read %s."), git_path_todo_file()); } close(fd); res = parse_insn_buffer(buf.buf, todo_list, opts); strbuf_release(&buf); if (res) - die(_("Unusable instruction sheet: %s"), todo_file); + die(_("Unusable instruction sheet: %s"), git_path_todo_file()); } static int populate_opts_cb(const char *key, const char *value, void *data) @@ -813,12 +798,10 @@ static int populate_opts_cb(const char *key, const char *value, void *data) static void read_populate_opts(struct replay_opts **opts_ptr) { - const char *opts_file = git_path(SEQ_OPTS_FILE); - - if (!file_exists(opts_file)) + if (!file_exists(git_path_opts_file())) return; - if (git_config_from_file(populate_opts_cb, opts_file, *opts_ptr) < 0) - die(_("Malformed options sheet: %s"), opts_file); + if (git_config_from_file(populate_opts_cb, git_path_opts_file(), *opts_ptr) < 0) + die(_("Malformed options sheet: %s"), git_path_opts_file()); } static void walk_revs_populate_todo(struct commit_list **todo_list, @@ -836,31 +819,29 @@ static void walk_revs_populate_todo(struct commit_list **todo_list, static int create_seq_dir(void) { - const char *seq_dir = git_path(SEQ_DIR); - - if (file_exists(seq_dir)) { + if (file_exists(git_path_seq_dir())) { error(_("a cherry-pick or revert is already in progress")); advise(_("try \"git cherry-pick (--continue | --quit | --abort)\"")); return -1; } - else if (mkdir(seq_dir, 0777) < 0) - die_errno(_("Could not create sequencer directory %s"), seq_dir); + else if (mkdir(git_path_seq_dir(), 0777) < 0) + die_errno(_("Could not create sequencer directory %s"), + git_path_seq_dir()); return 0; } static void save_head(const char *head) { - const char *head_file = git_path(SEQ_HEAD_FILE); static struct lock_file head_lock; struct strbuf buf = STRBUF_INIT; int fd; - fd = hold_lock_file_for_update(&head_lock, head_file, LOCK_DIE_ON_ERROR); + fd = hold_lock_file_for_update(&head_lock, git_path_head_file(), LOCK_DIE_ON_ERROR); strbuf_addf(&buf, "%s\n", head); if (write_in_full(fd, buf.buf, buf.len) < 0) - die_errno(_("Could not write to %s"), head_file); + die_errno(_("Could not write to %s"), git_path_head_file()); if (commit_lock_file(&head_lock) < 0) - die(_("Error wrapping up %s."), head_file); + die(_("Error wrapping up %s."), git_path_head_file()); } static int reset_for_rollback(const unsigned char *sha1) @@ -877,8 +858,8 @@ static int rollback_single_pick(void) { unsigned char head_sha1[20]; - if (!file_exists(git_path("CHERRY_PICK_HEAD")) && - !file_exists(git_path("REVERT_HEAD"))) + if (!file_exists(git_path_cherry_pick_head()) && + !file_exists(git_path_revert_head())) return error(_("no cherry-pick or revert in progress")); if (read_ref_full("HEAD", 0, head_sha1, NULL)) return error(_("cannot resolve HEAD")); @@ -889,13 +870,11 @@ static int rollback_single_pick(void) static int sequencer_rollback(struct replay_opts *opts) { - const char *filename; FILE *f; unsigned char sha1[20]; struct strbuf buf = STRBUF_INIT; - filename = git_path(SEQ_HEAD_FILE); - f = fopen(filename, "r"); + f = fopen(git_path_head_file(), "r"); if (!f && errno == ENOENT) { /* * There is no multiple-cherry-pick in progress. @@ -905,18 +884,18 @@ static int sequencer_rollback(struct replay_opts *opts) return rollback_single_pick(); } if (!f) - return error(_("cannot open %s: %s"), filename, + return error(_("cannot open %s: %s"), git_path_head_file(), strerror(errno)); if (strbuf_getline(&buf, f, '\n')) { - error(_("cannot read %s: %s"), filename, ferror(f) ? - strerror(errno) : _("unexpected end of file")); + error(_("cannot read %s: %s"), git_path_head_file(), + ferror(f) ? strerror(errno) : _("unexpected end of file")); fclose(f); goto fail; } fclose(f); if (get_sha1_hex(buf.buf, sha1) || buf.buf[40] != '\0') { error(_("stored pre-cherry-pick HEAD file '%s' is corrupt"), - filename); + git_path_head_file()); goto fail; } if (reset_for_rollback(sha1)) @@ -931,28 +910,27 @@ fail: static void save_todo(struct commit_list *todo_list, struct replay_opts *opts) { - const char *todo_file = git_path(SEQ_TODO_FILE); static struct lock_file todo_lock; struct strbuf buf = STRBUF_INIT; int fd; - fd = hold_lock_file_for_update(&todo_lock, todo_file, LOCK_DIE_ON_ERROR); + fd = hold_lock_file_for_update(&todo_lock, git_path_todo_file(), LOCK_DIE_ON_ERROR); if (format_todo(&buf, todo_list, opts) < 0) - die(_("Could not format %s."), todo_file); + die(_("Could not format %s."), git_path_todo_file()); if (write_in_full(fd, buf.buf, buf.len) < 0) { strbuf_release(&buf); - die_errno(_("Could not write to %s"), todo_file); + die_errno(_("Could not write to %s"), git_path_todo_file()); } if (commit_lock_file(&todo_lock) < 0) { strbuf_release(&buf); - die(_("Error wrapping up %s."), todo_file); + die(_("Error wrapping up %s."), git_path_todo_file()); } strbuf_release(&buf); } static void save_opts(struct replay_opts *opts) { - const char *opts_file = git_path(SEQ_OPTS_FILE); + const char *opts_file = git_path_opts_file(); if (opts->no_commit) git_config_set_in_file(opts_file, "options.no-commit", "true"); @@ -1013,8 +991,8 @@ static int continue_single_pick(void) { const char *argv[] = { "commit", NULL }; - if (!file_exists(git_path("CHERRY_PICK_HEAD")) && - !file_exists(git_path("REVERT_HEAD"))) + if (!file_exists(git_path_cherry_pick_head()) && + !file_exists(git_path_revert_head())) return error(_("no cherry-pick or revert in progress")); return run_command_v_opt(argv, RUN_GIT_CMD); } @@ -1023,14 +1001,14 @@ static int sequencer_continue(struct replay_opts *opts) { struct commit_list *todo_list = NULL; - if (!file_exists(git_path(SEQ_TODO_FILE))) + if (!file_exists(git_path_todo_file())) return continue_single_pick(); read_populate_opts(&opts); read_populate_todo(&todo_list, opts); /* Verify that the conflict has been resolved */ - if (file_exists(git_path("CHERRY_PICK_HEAD")) || - file_exists(git_path("REVERT_HEAD"))) { + if (file_exists(git_path_cherry_pick_head()) || + file_exists(git_path_revert_head())) { int ret = continue_single_pick(); if (ret) return ret; diff --git a/sha1_file.c b/sha1_file.c index 1cee438422..08302f5857 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -404,13 +404,46 @@ void read_info_alternates(const char * relative_base, int depth) void add_to_alternates_file(const char *reference) { struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); - int fd = hold_lock_file_for_append(lock, git_path("objects/info/alternates"), LOCK_DIE_ON_ERROR); - const char *alt = mkpath("%s\n", reference); - write_or_die(fd, alt, strlen(alt)); - if (commit_lock_file(lock)) - die("could not close alternates file"); - if (alt_odb_tail) - link_alt_odb_entries(alt, strlen(alt), '\n', NULL, 0); + char *alts = git_pathdup("objects/info/alternates"); + FILE *in, *out; + + hold_lock_file_for_update(lock, alts, LOCK_DIE_ON_ERROR); + out = fdopen_lock_file(lock, "w"); + if (!out) + die_errno("unable to fdopen alternates lockfile"); + + in = fopen(alts, "r"); + if (in) { + struct strbuf line = STRBUF_INIT; + int found = 0; + + while (strbuf_getline(&line, in, '\n') != EOF) { + if (!strcmp(reference, line.buf)) { + found = 1; + break; + } + fprintf_or_die(out, "%s\n", line.buf); + } + + strbuf_release(&line); + fclose(in); + + if (found) { + rollback_lock_file(lock); + lock = NULL; + } + } + else if (errno != ENOENT) + die_errno("unable to read alternates file"); + + if (lock) { + fprintf_or_die(out, "%s\n", reference); + if (commit_lock_file(lock)) + die_errno("unable to move new alternates file into place"); + if (alt_odb_tail) + link_alt_odb_entries(reference, strlen(reference), '\n', NULL, 0); + } + free(alts); } int foreach_alt_odb(alt_odb_fn fn, void *cb) @@ -1461,7 +1494,10 @@ int git_open_noatime(const char *name) static int sha1_file_open_flag = O_NOATIME; for (;;) { - int fd = open(name, O_RDONLY | sha1_file_open_flag); + int fd; + + errno = 0; + fd = open(name, O_RDONLY | sha1_file_open_flag); if (fd >= 0) return fd; @@ -2908,11 +2944,8 @@ static void write_sha1_file_prepare(const void *buf, unsigned long len, /* * Move the just written object into its final resting place. - * NEEDSWORK: this should be renamed to finalize_temp_file() as - * "moving" is only a part of what it does, when no patch between - * master to pu changes the call sites of this function. */ -int move_temp_to_file(const char *tmpfile, const char *filename) +int finalize_object_file(const char *tmpfile, const char *filename) { int ret = 0; @@ -3085,7 +3118,7 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen, tmp_file, strerror(errno)); } - return move_temp_to_file(tmp_file, filename); + return finalize_object_file(tmp_file, filename); } static int freshen_loose_object(const unsigned char *sha1) @@ -1,4 +1,5 @@ #include "cache.h" +#include "tempfile.h" #include "lockfile.h" #include "commit.h" #include "tag.h" @@ -48,7 +49,7 @@ int is_repository_shallow(void) return is_shallow; if (!path) - path = git_path("shallow"); + path = git_path_shallow(); /* * fetch-pack sets '--shallow-file ""' as an indicator that no * shallow file should be used. We could just open it and it @@ -142,7 +143,7 @@ static void check_shallow_file_for_update(void) if (is_shallow == -1) die("BUG: shallow must be initialized by now"); - if (!stat_validity_check(&shallow_stat, git_path("shallow"))) + if (!stat_validity_check(&shallow_stat, git_path_shallow())) die("shallow file has changed since we read it"); } @@ -208,50 +209,28 @@ int write_shallow_commits(struct strbuf *out, int use_pack_protocol, return write_shallow_commits_1(out, use_pack_protocol, extra, 0); } -static struct strbuf temporary_shallow = STRBUF_INIT; - -static void remove_temporary_shallow(void) -{ - if (temporary_shallow.len) { - unlink_or_warn(temporary_shallow.buf); - strbuf_reset(&temporary_shallow); - } -} - -static void remove_temporary_shallow_on_signal(int signo) -{ - remove_temporary_shallow(); - sigchain_pop(signo); - raise(signo); -} +static struct tempfile temporary_shallow; const char *setup_temporary_shallow(const struct sha1_array *extra) { struct strbuf sb = STRBUF_INIT; int fd; - if (temporary_shallow.len) - die("BUG: attempt to create two temporary shallow files"); - if (write_shallow_commits(&sb, 0, extra)) { - strbuf_addstr(&temporary_shallow, git_path("shallow_XXXXXX")); - fd = xmkstemp(temporary_shallow.buf); - - atexit(remove_temporary_shallow); - sigchain_push_common(remove_temporary_shallow_on_signal); + fd = xmks_tempfile(&temporary_shallow, git_path("shallow_XXXXXX")); if (write_in_full(fd, sb.buf, sb.len) != sb.len) die_errno("failed to write to %s", - temporary_shallow.buf); - close(fd); + get_tempfile_path(&temporary_shallow)); + close_tempfile(&temporary_shallow); strbuf_release(&sb); - return temporary_shallow.buf; + return get_tempfile_path(&temporary_shallow); } /* * is_repository_shallow() sees empty string as "no shallow * file". */ - return temporary_shallow.buf; + return get_tempfile_path(&temporary_shallow); } void setup_alternate_shallow(struct lock_file *shallow_lock, @@ -261,14 +240,14 @@ void setup_alternate_shallow(struct lock_file *shallow_lock, struct strbuf sb = STRBUF_INIT; int fd; - fd = hold_lock_file_for_update(shallow_lock, git_path("shallow"), + fd = hold_lock_file_for_update(shallow_lock, git_path_shallow(), LOCK_DIE_ON_ERROR); check_shallow_file_for_update(); if (write_shallow_commits(&sb, 0, extra)) { if (write_in_full(fd, sb.buf, sb.len) != sb.len) die_errno("failed to write to %s", - shallow_lock->filename.buf); - *alternate_shallow_file = shallow_lock->filename.buf; + get_lock_file_path(shallow_lock)); + *alternate_shallow_file = get_lock_file_path(shallow_lock); } else /* * is_repository_shallow() sees empty string as "no @@ -308,16 +287,16 @@ void prune_shallow(int show_only) strbuf_release(&sb); return; } - fd = hold_lock_file_for_update(&shallow_lock, git_path("shallow"), + fd = hold_lock_file_for_update(&shallow_lock, git_path_shallow(), LOCK_DIE_ON_ERROR); check_shallow_file_for_update(); if (write_shallow_commits_1(&sb, 0, NULL, SEEN_ONLY)) { if (write_in_full(fd, sb.buf, sb.len) != sb.len) die_errno("failed to write to %s", - shallow_lock.filename.buf); + get_lock_file_path(&shallow_lock)); commit_lock_file(&shallow_lock); } else { - unlink(git_path("shallow")); + unlink(git_path_shallow()); rollback_lock_file(&shallow_lock); } strbuf_release(&sb); @@ -364,19 +364,19 @@ ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint) strbuf_grow(sb, hint ? hint : 8192); for (;;) { - ssize_t cnt; + ssize_t want = sb->alloc - sb->len - 1; + ssize_t got = read_in_full(fd, sb->buf + sb->len, want); - cnt = xread(fd, sb->buf + sb->len, sb->alloc - sb->len - 1); - if (cnt < 0) { + if (got < 0) { if (oldalloc == 0) strbuf_release(sb); else strbuf_setlen(sb, oldlen); return -1; } - if (!cnt) + sb->len += got; + if (got < want) break; - sb->len += cnt; strbuf_grow(sb, 8192); } diff --git a/submodule-config.c b/submodule-config.c new file mode 100644 index 0000000000..393de5357e --- /dev/null +++ b/submodule-config.c @@ -0,0 +1,482 @@ +#include "cache.h" +#include "submodule-config.h" +#include "submodule.h" +#include "strbuf.h" + +/* + * submodule cache lookup structure + * There is one shared set of 'struct submodule' entries which can be + * looked up by their sha1 blob id of the .gitmodule file and either + * using path or name as key. + * for_path stores submodule entries with path as key + * for_name stores submodule entries with name as key + */ +struct submodule_cache { + struct hashmap for_path; + struct hashmap for_name; +}; + +/* + * thin wrapper struct needed to insert 'struct submodule' entries to + * the hashmap + */ +struct submodule_entry { + struct hashmap_entry ent; + struct submodule *config; +}; + +enum lookup_type { + lookup_name, + lookup_path +}; + +static struct submodule_cache cache; +static int is_cache_init; + +static int config_path_cmp(const struct submodule_entry *a, + const struct submodule_entry *b, + const void *unused) +{ + return strcmp(a->config->path, b->config->path) || + hashcmp(a->config->gitmodules_sha1, b->config->gitmodules_sha1); +} + +static int config_name_cmp(const struct submodule_entry *a, + const struct submodule_entry *b, + const void *unused) +{ + return strcmp(a->config->name, b->config->name) || + hashcmp(a->config->gitmodules_sha1, b->config->gitmodules_sha1); +} + +static void cache_init(struct submodule_cache *cache) +{ + hashmap_init(&cache->for_path, (hashmap_cmp_fn) config_path_cmp, 0); + hashmap_init(&cache->for_name, (hashmap_cmp_fn) config_name_cmp, 0); +} + +static void free_one_config(struct submodule_entry *entry) +{ + free((void *) entry->config->path); + free((void *) entry->config->name); + free(entry->config); +} + +static void cache_free(struct submodule_cache *cache) +{ + struct hashmap_iter iter; + struct submodule_entry *entry; + + /* + * We iterate over the name hash here to be symmetric with the + * allocation of struct submodule entries. Each is allocated by + * their .gitmodule blob sha1 and submodule name. + */ + hashmap_iter_init(&cache->for_name, &iter); + while ((entry = hashmap_iter_next(&iter))) + free_one_config(entry); + + hashmap_free(&cache->for_path, 1); + hashmap_free(&cache->for_name, 1); +} + +static unsigned int hash_sha1_string(const unsigned char *sha1, + const char *string) +{ + return memhash(sha1, 20) + strhash(string); +} + +static void cache_put_path(struct submodule_cache *cache, + struct submodule *submodule) +{ + unsigned int hash = hash_sha1_string(submodule->gitmodules_sha1, + submodule->path); + struct submodule_entry *e = xmalloc(sizeof(*e)); + hashmap_entry_init(e, hash); + e->config = submodule; + hashmap_put(&cache->for_path, e); +} + +static void cache_remove_path(struct submodule_cache *cache, + struct submodule *submodule) +{ + unsigned int hash = hash_sha1_string(submodule->gitmodules_sha1, + submodule->path); + struct submodule_entry e; + struct submodule_entry *removed; + hashmap_entry_init(&e, hash); + e.config = submodule; + removed = hashmap_remove(&cache->for_path, &e, NULL); + free(removed); +} + +static void cache_add(struct submodule_cache *cache, + struct submodule *submodule) +{ + unsigned int hash = hash_sha1_string(submodule->gitmodules_sha1, + submodule->name); + struct submodule_entry *e = xmalloc(sizeof(*e)); + hashmap_entry_init(e, hash); + e->config = submodule; + hashmap_add(&cache->for_name, e); +} + +static const struct submodule *cache_lookup_path(struct submodule_cache *cache, + const unsigned char *gitmodules_sha1, const char *path) +{ + struct submodule_entry *entry; + unsigned int hash = hash_sha1_string(gitmodules_sha1, path); + struct submodule_entry key; + struct submodule key_config; + + hashcpy(key_config.gitmodules_sha1, gitmodules_sha1); + key_config.path = path; + + hashmap_entry_init(&key, hash); + key.config = &key_config; + + entry = hashmap_get(&cache->for_path, &key, NULL); + if (entry) + return entry->config; + return NULL; +} + +static struct submodule *cache_lookup_name(struct submodule_cache *cache, + const unsigned char *gitmodules_sha1, const char *name) +{ + struct submodule_entry *entry; + unsigned int hash = hash_sha1_string(gitmodules_sha1, name); + struct submodule_entry key; + struct submodule key_config; + + hashcpy(key_config.gitmodules_sha1, gitmodules_sha1); + key_config.name = name; + + hashmap_entry_init(&key, hash); + key.config = &key_config; + + entry = hashmap_get(&cache->for_name, &key, NULL); + if (entry) + return entry->config; + return NULL; +} + +static int name_and_item_from_var(const char *var, struct strbuf *name, + struct strbuf *item) +{ + const char *subsection, *key; + int subsection_len, parse; + parse = parse_config_key(var, "submodule", &subsection, + &subsection_len, &key); + if (parse < 0 || !subsection) + return 0; + + strbuf_add(name, subsection, subsection_len); + strbuf_addstr(item, key); + + return 1; +} + +static struct submodule *lookup_or_create_by_name(struct submodule_cache *cache, + const unsigned char *gitmodules_sha1, const char *name) +{ + struct submodule *submodule; + struct strbuf name_buf = STRBUF_INIT; + + submodule = cache_lookup_name(cache, gitmodules_sha1, name); + if (submodule) + return submodule; + + submodule = xmalloc(sizeof(*submodule)); + + strbuf_addstr(&name_buf, name); + submodule->name = strbuf_detach(&name_buf, NULL); + + submodule->path = NULL; + submodule->url = NULL; + submodule->fetch_recurse = RECURSE_SUBMODULES_NONE; + submodule->ignore = NULL; + + hashcpy(submodule->gitmodules_sha1, gitmodules_sha1); + + cache_add(cache, submodule); + + return submodule; +} + +static int parse_fetch_recurse(const char *opt, const char *arg, + int die_on_error) +{ + switch (git_config_maybe_bool(opt, arg)) { + case 1: + return RECURSE_SUBMODULES_ON; + case 0: + return RECURSE_SUBMODULES_OFF; + default: + if (!strcmp(arg, "on-demand")) + return RECURSE_SUBMODULES_ON_DEMAND; + + if (die_on_error) + die("bad %s argument: %s", opt, arg); + else + return RECURSE_SUBMODULES_ERROR; + } +} + +int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg) +{ + return parse_fetch_recurse(opt, arg, 1); +} + +static void warn_multiple_config(const unsigned char *commit_sha1, + const char *name, const char *option) +{ + const char *commit_string = "WORKTREE"; + if (commit_sha1) + commit_string = sha1_to_hex(commit_sha1); + warning("%s:.gitmodules, multiple configurations found for " + "'submodule.%s.%s'. Skipping second one!", + commit_string, name, option); +} + +struct parse_config_parameter { + struct submodule_cache *cache; + const unsigned char *commit_sha1; + const unsigned char *gitmodules_sha1; + int overwrite; +}; + +static int parse_config(const char *var, const char *value, void *data) +{ + struct parse_config_parameter *me = data; + struct submodule *submodule; + struct strbuf name = STRBUF_INIT, item = STRBUF_INIT; + int ret = 0; + + /* this also ensures that we only parse submodule entries */ + if (!name_and_item_from_var(var, &name, &item)) + return 0; + + submodule = lookup_or_create_by_name(me->cache, me->gitmodules_sha1, + name.buf); + + if (!strcmp(item.buf, "path")) { + struct strbuf path = STRBUF_INIT; + if (!value) { + ret = config_error_nonbool(var); + goto release_return; + } + if (!me->overwrite && submodule->path != NULL) { + warn_multiple_config(me->commit_sha1, submodule->name, + "path"); + goto release_return; + } + + if (submodule->path) + cache_remove_path(me->cache, submodule); + free((void *) submodule->path); + strbuf_addstr(&path, value); + submodule->path = strbuf_detach(&path, NULL); + cache_put_path(me->cache, submodule); + } else if (!strcmp(item.buf, "fetchrecursesubmodules")) { + /* when parsing worktree configurations we can die early */ + int die_on_error = is_null_sha1(me->gitmodules_sha1); + if (!me->overwrite && + submodule->fetch_recurse != RECURSE_SUBMODULES_NONE) { + warn_multiple_config(me->commit_sha1, submodule->name, + "fetchrecursesubmodules"); + goto release_return; + } + + submodule->fetch_recurse = parse_fetch_recurse(var, value, + die_on_error); + } else if (!strcmp(item.buf, "ignore")) { + struct strbuf ignore = STRBUF_INIT; + if (!me->overwrite && submodule->ignore != NULL) { + warn_multiple_config(me->commit_sha1, submodule->name, + "ignore"); + goto release_return; + } + if (!value) { + ret = config_error_nonbool(var); + goto release_return; + } + if (strcmp(value, "untracked") && strcmp(value, "dirty") && + strcmp(value, "all") && strcmp(value, "none")) { + warning("Invalid parameter '%s' for config option " + "'submodule.%s.ignore'", value, var); + goto release_return; + } + + free((void *) submodule->ignore); + strbuf_addstr(&ignore, value); + submodule->ignore = strbuf_detach(&ignore, NULL); + } else if (!strcmp(item.buf, "url")) { + struct strbuf url = STRBUF_INIT; + if (!value) { + ret = config_error_nonbool(var); + goto release_return; + } + if (!me->overwrite && submodule->url != NULL) { + warn_multiple_config(me->commit_sha1, submodule->name, + "url"); + goto release_return; + } + + free((void *) submodule->url); + strbuf_addstr(&url, value); + submodule->url = strbuf_detach(&url, NULL); + } + +release_return: + strbuf_release(&name); + strbuf_release(&item); + + return ret; +} + +static int gitmodule_sha1_from_commit(const unsigned char *commit_sha1, + unsigned char *gitmodules_sha1) +{ + struct strbuf rev = STRBUF_INIT; + int ret = 0; + + if (is_null_sha1(commit_sha1)) { + hashcpy(gitmodules_sha1, null_sha1); + return 1; + } + + strbuf_addf(&rev, "%s:.gitmodules", sha1_to_hex(commit_sha1)); + if (get_sha1(rev.buf, gitmodules_sha1) >= 0) + ret = 1; + + strbuf_release(&rev); + return ret; +} + +/* This does a lookup of a submodule configuration by name or by path + * (key) with on-demand reading of the appropriate .gitmodules from + * revisions. + */ +static const struct submodule *config_from(struct submodule_cache *cache, + const unsigned char *commit_sha1, const char *key, + enum lookup_type lookup_type) +{ + struct strbuf rev = STRBUF_INIT; + unsigned long config_size; + char *config; + unsigned char sha1[20]; + enum object_type type; + const struct submodule *submodule = NULL; + struct parse_config_parameter parameter; + + /* + * If any parameter except the cache is a NULL pointer just + * return the first submodule. Can be used to check whether + * there are any submodules parsed. + */ + if (!commit_sha1 || !key) { + struct hashmap_iter iter; + struct submodule_entry *entry; + + hashmap_iter_init(&cache->for_name, &iter); + entry = hashmap_iter_next(&iter); + if (!entry) + return NULL; + return entry->config; + } + + if (!gitmodule_sha1_from_commit(commit_sha1, sha1)) + return NULL; + + switch (lookup_type) { + case lookup_name: + submodule = cache_lookup_name(cache, sha1, key); + break; + case lookup_path: + submodule = cache_lookup_path(cache, sha1, key); + break; + } + if (submodule) + return submodule; + + config = read_sha1_file(sha1, &type, &config_size); + if (!config) + return NULL; + + if (type != OBJ_BLOB) { + free(config); + return NULL; + } + + /* fill the submodule config into the cache */ + parameter.cache = cache; + parameter.commit_sha1 = commit_sha1; + parameter.gitmodules_sha1 = sha1; + parameter.overwrite = 0; + git_config_from_buf(parse_config, rev.buf, config, config_size, + ¶meter); + free(config); + + switch (lookup_type) { + case lookup_name: + return cache_lookup_name(cache, sha1, key); + case lookup_path: + return cache_lookup_path(cache, sha1, key); + default: + return NULL; + } +} + +static const struct submodule *config_from_path(struct submodule_cache *cache, + const unsigned char *commit_sha1, const char *path) +{ + return config_from(cache, commit_sha1, path, lookup_path); +} + +static const struct submodule *config_from_name(struct submodule_cache *cache, + const unsigned char *commit_sha1, const char *name) +{ + return config_from(cache, commit_sha1, name, lookup_name); +} + +static void ensure_cache_init(void) +{ + if (is_cache_init) + return; + + cache_init(&cache); + is_cache_init = 1; +} + +int parse_submodule_config_option(const char *var, const char *value) +{ + struct parse_config_parameter parameter; + parameter.cache = &cache; + parameter.commit_sha1 = NULL; + parameter.gitmodules_sha1 = null_sha1; + parameter.overwrite = 1; + + ensure_cache_init(); + return parse_config(var, value, ¶meter); +} + +const struct submodule *submodule_from_name(const unsigned char *commit_sha1, + const char *name) +{ + ensure_cache_init(); + return config_from_name(&cache, commit_sha1, name); +} + +const struct submodule *submodule_from_path(const unsigned char *commit_sha1, + const char *path) +{ + ensure_cache_init(); + return config_from_path(&cache, commit_sha1, path); +} + +void submodule_free(void) +{ + cache_free(&cache); + is_cache_init = 0; +} diff --git a/submodule-config.h b/submodule-config.h new file mode 100644 index 0000000000..9061e4ed38 --- /dev/null +++ b/submodule-config.h @@ -0,0 +1,29 @@ +#ifndef SUBMODULE_CONFIG_CACHE_H +#define SUBMODULE_CONFIG_CACHE_H + +#include "hashmap.h" +#include "strbuf.h" + +/* + * Submodule entry containing the information about a certain submodule + * in a certain revision. + */ +struct submodule { + const char *path; + const char *name; + const char *url; + int fetch_recurse; + const char *ignore; + /* the sha1 blob id of the responsible .gitmodules file */ + unsigned char gitmodules_sha1[20]; +}; + +int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg); +int parse_submodule_config_option(const char *var, const char *value); +const struct submodule *submodule_from_name(const unsigned char *commit_sha1, + const char *name); +const struct submodule *submodule_from_path(const unsigned char *commit_sha1, + const char *path); +void submodule_free(void); + +#endif /* SUBMODULE_CONFIG_H */ diff --git a/submodule.c b/submodule.c index 4549c1baf4..245ed4dfbb 100644 --- a/submodule.c +++ b/submodule.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "submodule-config.h" #include "submodule.h" #include "dir.h" #include "diff.h" @@ -12,9 +13,6 @@ #include "argv-array.h" #include "blob.h" -static struct string_list config_name_for_path; -static struct string_list config_fetch_recurse_submodules_for_name; -static struct string_list config_ignore_for_name; static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND; static struct string_list changed_submodule_paths; static int initialized_fetch_ref_tips; @@ -41,7 +39,6 @@ static int gitmodules_is_unmerged; */ static int gitmodules_is_modified; - int is_staging_gitmodules_ok(void) { return !gitmodules_is_modified; @@ -55,7 +52,7 @@ int is_staging_gitmodules_ok(void) int update_path_in_gitmodules(const char *oldpath, const char *newpath) { struct strbuf entry = STRBUF_INIT; - struct string_list_item *path_option; + const struct submodule *submodule; if (!file_exists(".gitmodules")) /* Do nothing without .gitmodules */ return -1; @@ -63,13 +60,13 @@ int update_path_in_gitmodules(const char *oldpath, const char *newpath) if (gitmodules_is_unmerged) die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first")); - path_option = unsorted_string_list_lookup(&config_name_for_path, oldpath); - if (!path_option) { + submodule = submodule_from_path(null_sha1, oldpath); + if (!submodule || !submodule->name) { warning(_("Could not find section in .gitmodules where path=%s"), oldpath); return -1; } strbuf_addstr(&entry, "submodule."); - strbuf_addstr(&entry, path_option->util); + strbuf_addstr(&entry, submodule->name); strbuf_addstr(&entry, ".path"); if (git_config_set_in_file(".gitmodules", entry.buf, newpath) < 0) { /* Maybe the user already did that, don't error out here */ @@ -89,7 +86,7 @@ int update_path_in_gitmodules(const char *oldpath, const char *newpath) int remove_path_from_gitmodules(const char *path) { struct strbuf sect = STRBUF_INIT; - struct string_list_item *path_option; + const struct submodule *submodule; if (!file_exists(".gitmodules")) /* Do nothing without .gitmodules */ return -1; @@ -97,13 +94,13 @@ int remove_path_from_gitmodules(const char *path) if (gitmodules_is_unmerged) die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first")); - path_option = unsorted_string_list_lookup(&config_name_for_path, path); - if (!path_option) { + submodule = submodule_from_path(null_sha1, path); + if (!submodule || !submodule->name) { warning(_("Could not find section in .gitmodules where path=%s"), path); return -1; } strbuf_addstr(§, "submodule."); - strbuf_addstr(§, path_option->util); + strbuf_addstr(§, submodule->name); if (git_config_rename_section_in_file(".gitmodules", sect.buf, NULL) < 0) { /* Maybe the user already did that, don't error out here */ warning(_("Could not remove .gitmodules entry for %s"), path); @@ -165,12 +162,10 @@ done: void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, const char *path) { - struct string_list_item *path_option, *ignore_option; - path_option = unsorted_string_list_lookup(&config_name_for_path, path); - if (path_option) { - ignore_option = unsorted_string_list_lookup(&config_ignore_for_name, path_option->util); - if (ignore_option) - handle_ignore_submodules_arg(diffopt, ignore_option->util); + const struct submodule *submodule = submodule_from_path(null_sha1, path); + if (submodule) { + if (submodule->ignore) + handle_ignore_submodules_arg(diffopt, submodule->ignore); else if (gitmodules_is_unmerged) DIFF_OPT_SET(diffopt, IGNORE_SUBMODULES); } @@ -219,58 +214,6 @@ void gitmodules_config(void) } } -int parse_submodule_config_option(const char *var, const char *value) -{ - struct string_list_item *config; - const char *name, *key; - int namelen; - - if (parse_config_key(var, "submodule", &name, &namelen, &key) < 0 || !name) - return 0; - - if (!strcmp(key, "path")) { - if (!value) - return config_error_nonbool(var); - - config = unsorted_string_list_lookup(&config_name_for_path, value); - if (config) - free(config->util); - else - config = string_list_append(&config_name_for_path, xstrdup(value)); - config->util = xmemdupz(name, namelen); - } else if (!strcmp(key, "fetchrecursesubmodules")) { - char *name_cstr = xmemdupz(name, namelen); - config = unsorted_string_list_lookup(&config_fetch_recurse_submodules_for_name, name_cstr); - if (!config) - config = string_list_append(&config_fetch_recurse_submodules_for_name, name_cstr); - else - free(name_cstr); - config->util = (void *)(intptr_t)parse_fetch_recurse_submodules_arg(var, value); - } else if (!strcmp(key, "ignore")) { - char *name_cstr; - - if (!value) - return config_error_nonbool(var); - - if (strcmp(value, "untracked") && strcmp(value, "dirty") && - strcmp(value, "all") && strcmp(value, "none")) { - warning("Invalid parameter \"%s\" for config option \"submodule.%s.ignore\"", value, var); - return 0; - } - - name_cstr = xmemdupz(name, namelen); - config = unsorted_string_list_lookup(&config_ignore_for_name, name_cstr); - if (config) { - free(config->util); - free(name_cstr); - } else - config = string_list_append(&config_ignore_for_name, name_cstr); - config->util = xstrdup(value); - return 0; - } - return 0; -} - void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *arg) { @@ -345,20 +288,6 @@ static void print_submodule_summary(struct rev_info *rev, FILE *f, strbuf_release(&sb); } -int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg) -{ - switch (git_config_maybe_bool(opt, arg)) { - case 1: - return RECURSE_SUBMODULES_ON; - case 0: - return RECURSE_SUBMODULES_OFF; - default: - if (!strcmp(arg, "on-demand")) - return RECURSE_SUBMODULES_ON_DEMAND; - die("bad %s argument: %s", opt, arg); - } -} - void show_submodule_summary(FILE *f, const char *path, const char *line_prefix, unsigned char one[20], unsigned char two[20], @@ -646,7 +575,7 @@ static void calculate_changed_submodule_paths(void) struct argv_array argv = ARGV_ARRAY_INIT; /* No need to check if there are no submodules configured */ - if (!config_name_for_path.nr) + if (!submodule_from_path(NULL, NULL)) return; init_revisions(&rev, NULL); @@ -693,7 +622,6 @@ int fetch_populated_submodules(const struct argv_array *options, int i, result = 0; struct child_process cp = CHILD_PROCESS_INIT; struct argv_array argv = ARGV_ARRAY_INIT; - struct string_list_item *name_for_path; const char *work_tree = get_git_work_tree(); if (!work_tree) goto out; @@ -718,24 +646,26 @@ int fetch_populated_submodules(const struct argv_array *options, struct strbuf submodule_git_dir = STRBUF_INIT; struct strbuf submodule_prefix = STRBUF_INIT; const struct cache_entry *ce = active_cache[i]; - const char *git_dir, *name, *default_argv; + const char *git_dir, *default_argv; + const struct submodule *submodule; if (!S_ISGITLINK(ce->ce_mode)) continue; - name = ce->name; - name_for_path = unsorted_string_list_lookup(&config_name_for_path, ce->name); - if (name_for_path) - name = name_for_path->util; + submodule = submodule_from_path(null_sha1, ce->name); + if (!submodule) + submodule = submodule_from_name(null_sha1, ce->name); default_argv = "yes"; if (command_line_option == RECURSE_SUBMODULES_DEFAULT) { - struct string_list_item *fetch_recurse_submodules_option; - fetch_recurse_submodules_option = unsorted_string_list_lookup(&config_fetch_recurse_submodules_for_name, name); - if (fetch_recurse_submodules_option) { - if ((intptr_t)fetch_recurse_submodules_option->util == RECURSE_SUBMODULES_OFF) + if (submodule && + submodule->fetch_recurse != + RECURSE_SUBMODULES_NONE) { + if (submodule->fetch_recurse == + RECURSE_SUBMODULES_OFF) continue; - if ((intptr_t)fetch_recurse_submodules_option->util == RECURSE_SUBMODULES_ON_DEMAND) { + if (submodule->fetch_recurse == + RECURSE_SUBMODULES_ON_DEMAND) { if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name)) continue; default_argv = "on-demand"; diff --git a/submodule.h b/submodule.h index 7beec4822b..5507c3d9a0 100644 --- a/submodule.h +++ b/submodule.h @@ -5,6 +5,8 @@ struct diff_options; struct argv_array; enum { + RECURSE_SUBMODULES_ERROR = -3, + RECURSE_SUBMODULES_NONE = -2, RECURSE_SUBMODULES_ON_DEMAND = -1, RECURSE_SUBMODULES_OFF = 0, RECURSE_SUBMODULES_DEFAULT = 1, @@ -19,9 +21,7 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, const char *path); int submodule_config(const char *var, const char *value, void *cb); void gitmodules_config(void); -int parse_submodule_config_option(const char *var, const char *value); void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *); -int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg); void show_submodule_summary(FILE *f, const char *path, const char *line_prefix, unsigned char one[20], unsigned char two[20], diff --git a/t/t0090-cache-tree.sh b/t/t0090-cache-tree.sh index 601d02d71f..055cc19000 100755 --- a/t/t0090-cache-tree.sh +++ b/t/t0090-cache-tree.sh @@ -199,6 +199,30 @@ test_expect_success 'checkout -B gives cache-tree' ' test_cache_tree ' +test_expect_success 'merge --ff-only maintains cache-tree' ' + git checkout current && + git checkout -b changes && + test_commit llamas && + test_commit pachyderm && + test_cache_tree && + git checkout current && + test_cache_tree && + git merge --ff-only changes && + test_cache_tree +' + +test_expect_success 'merge maintains cache-tree' ' + git checkout current && + git checkout -b changes2 && + test_commit alpacas && + test_cache_tree && + git checkout current && + test_commit struthio && + test_cache_tree && + git merge changes2 && + test_cache_tree +' + test_expect_success 'partial commit gives cache-tree' ' git checkout -b partial no-children && test_commit one && diff --git a/t/t1090-sparse-checkout-scope.sh b/t/t1090-sparse-checkout-scope.sh new file mode 100755 index 0000000000..1f61eb3e88 --- /dev/null +++ b/t/t1090-sparse-checkout-scope.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +test_description='sparse checkout scope tests' + +. ./test-lib.sh + +test_expect_success 'setup' ' + echo "initial" >a && + echo "initial" >b && + echo "initial" >c && + git add a b c && + git commit -m "initial commit" +' + +test_expect_success 'create feature branch' ' + git checkout -b feature && + echo "modified" >b && + echo "modified" >c && + git add b c && + git commit -m "modification" +' + +test_expect_success 'perform sparse checkout of master' ' + git config --local --bool core.sparsecheckout true && + echo "!/*" >.git/info/sparse-checkout && + echo "/a" >>.git/info/sparse-checkout && + echo "/c" >>.git/info/sparse-checkout && + git checkout master && + test_path_is_file a && + test_path_is_missing b && + test_path_is_file c +' + +test_expect_success 'merge feature branch into sparse checkout of master' ' + git merge feature && + test_path_is_file a && + test_path_is_missing b && + test_path_is_file c && + test "$(cat c)" = "modified" +' + +test_expect_success 'return to full checkout of master' ' + git checkout feature && + echo "/*" >.git/info/sparse-checkout && + git checkout master && + test_path_is_file a && + test_path_is_file b && + test_path_is_file c && + test "$(cat b)" = "modified" +' + +test_done diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index 66dd28644f..52678e7d0a 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -353,6 +353,18 @@ test_expect_success '--list without repo produces empty output' ' ' cat > expect << EOF +beta.noindent +nextsection.nonewline +123456.a123 +version.1.2.3eX.alpha +EOF + +test_expect_success '--name-only --list' ' + git config --name-only --list >output && + test_cmp expect output +' + +cat > expect << EOF beta.noindent sillyValue nextsection.nonewline wow2 for me EOF @@ -363,6 +375,16 @@ test_expect_success '--get-regexp' ' ' cat > expect << EOF +beta.noindent +nextsection.nonewline +EOF + +test_expect_success '--name-only --get-regexp' ' + git config --name-only --get-regexp in >output && + test_cmp expect output +' + +cat > expect << EOF wow2 for me wow4 for you EOF diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index d787bf50f8..97406fa4b1 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -23,6 +23,7 @@ test_expect_success setup ' m=refs/heads/master n_dir=refs/heads/gu n=$n_dir/fixes +outside=foo test_expect_success \ "create $m" \ @@ -74,6 +75,24 @@ test_expect_success "delete $m (by HEAD)" ' ' rm -f .git/$m +test_expect_success 'update-ref does not create reflogs by default' ' + test_when_finished "git update-ref -d $outside" && + git update-ref $outside $A && + git rev-parse $A >expect && + git rev-parse $outside >actual && + test_cmp expect actual && + test_must_fail git reflog exists $outside +' + +test_expect_success 'update-ref creates reflogs with --create-reflog' ' + test_when_finished "git update-ref -d $outside" && + git update-ref --create-reflog $outside $A && + git rev-parse $A >expect && + git rev-parse $outside >actual && + test_cmp expect actual && + git reflog exists $outside +' + test_expect_success \ "create $m (by HEAD)" \ "git update-ref HEAD $A && @@ -155,12 +174,11 @@ test_expect_success "(not) changed .git/$m" " ' rm -f .git/$m -: a repository with working tree always has reflog these days... -: >.git/logs/refs/heads/master +rm -f .git/logs/refs/heads/master test_expect_success \ "create $m (logged by touch)" \ 'GIT_COMMITTER_DATE="2005-05-26 23:30" \ - git update-ref HEAD '"$A"' -m "Initial Creation" && + git update-ref --create-reflog HEAD '"$A"' -m "Initial Creation" && test '"$A"' = $(cat .git/'"$m"')' test_expect_success \ "update $m (logged by touch)" \ @@ -472,6 +490,25 @@ test_expect_success 'stdin create ref works' ' test_cmp expect actual ' +test_expect_success 'stdin does not create reflogs by default' ' + test_when_finished "git update-ref -d $outside" && + echo "create $outside $m" >stdin && + git update-ref --stdin <stdin && + git rev-parse $m >expect && + git rev-parse $outside >actual && + test_cmp expect actual && + test_must_fail git reflog exists $outside +' + +test_expect_success 'stdin creates reflogs with --create-reflog' ' + echo "create $outside $m" >stdin && + git update-ref --create-reflog --stdin <stdin && + git rev-parse $m >expect && + git rev-parse $outside >actual && + test_cmp expect actual && + git reflog exists $outside +' + test_expect_success 'stdin succeeds with quoted argument' ' git update-ref -d $a && echo "create $a \"$m\"" >stdin && diff --git a/t/t1402-check-ref-format.sh b/t/t1402-check-ref-format.sh index e5dc62e9ef..0790edf60d 100755 --- a/t/t1402-check-ref-format.sh +++ b/t/t1402-check-ref-format.sh @@ -62,9 +62,11 @@ invalid_ref 'heads/foo\bar' invalid_ref "$(printf 'heads/foo\t')" invalid_ref "$(printf 'heads/foo\177')" valid_ref "$(printf 'heads/fu\303\237')" -invalid_ref 'heads/*foo/bar' --refspec-pattern -invalid_ref 'heads/foo*/bar' --refspec-pattern -invalid_ref 'heads/f*o/bar' --refspec-pattern +valid_ref 'heads/*foo/bar' --refspec-pattern +valid_ref 'heads/foo*/bar' --refspec-pattern +valid_ref 'heads/f*o/bar' --refspec-pattern +invalid_ref 'heads/f*o*/bar' --refspec-pattern +invalid_ref 'heads/foo*/bar*' --refspec-pattern ref='foo' invalid_ref "$ref" diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh index 779d4e3829..b79049f6f6 100755 --- a/t/t1410-reflog.sh +++ b/t/t1410-reflog.sh @@ -100,7 +100,8 @@ test_expect_success setup ' check_fsck && - test_line_count = 4 .git/logs/refs/heads/master + git reflog refs/heads/master >output && + test_line_count = 4 output ' test_expect_success rewind ' @@ -116,7 +117,8 @@ test_expect_success rewind ' check_have A B C D E F G H I J K L && - test_line_count = 5 .git/logs/refs/heads/master + git reflog refs/heads/master >output && + test_line_count = 5 output ' test_expect_success 'corrupt and check' ' @@ -134,7 +136,8 @@ test_expect_success 'reflog expire --dry-run should not touch reflog' ' --stale-fix \ --all && - test_line_count = 5 .git/logs/refs/heads/master && + git reflog refs/heads/master >output && + test_line_count = 5 output && check_fsck "missing blob $F" ' @@ -147,7 +150,8 @@ test_expect_success 'reflog expire' ' --stale-fix \ --all && - test_line_count = 2 .git/logs/refs/heads/master && + git reflog refs/heads/master >output && + test_line_count = 2 output && check_fsck "dangling commit $K" ' @@ -213,7 +217,8 @@ test_expect_success 'delete' ' test_expect_success 'rewind2' ' test_tick && git reset --hard HEAD~2 && - test_line_count = 4 .git/logs/refs/heads/master + git reflog refs/heads/master >output && + test_line_count = 4 output ' test_expect_success '--expire=never' ' @@ -222,7 +227,8 @@ test_expect_success '--expire=never' ' --expire=never \ --expire-unreachable=never \ --all && - test_line_count = 4 .git/logs/refs/heads/master + git reflog refs/heads/master >output && + test_line_count = 4 output ' test_expect_success 'gc.reflogexpire=never' ' @@ -230,7 +236,8 @@ test_expect_success 'gc.reflogexpire=never' ' git config gc.reflogexpire never && git config gc.reflogexpireunreachable never && git reflog expire --verbose --all && - test_line_count = 4 .git/logs/refs/heads/master + git reflog refs/heads/master >output && + test_line_count = 4 output ' test_expect_success 'gc.reflogexpire=false' ' @@ -238,7 +245,8 @@ test_expect_success 'gc.reflogexpire=false' ' git config gc.reflogexpire false && git config gc.reflogexpireunreachable false && git reflog expire --verbose --all && - test_line_count = 4 .git/logs/refs/heads/master && + git reflog refs/heads/master >output && + test_line_count = 4 output && git config --unset gc.reflogexpire && git config --unset gc.reflogexpireunreachable diff --git a/t/t1411-reflog-show.sh b/t/t1411-reflog-show.sh index 6f47c0dd0e..6ac7734d79 100755 --- a/t/t1411-reflog-show.sh +++ b/t/t1411-reflog-show.sh @@ -138,7 +138,7 @@ test_expect_success '--date magic does not override explicit @{0} syntax' ' : >expect test_expect_success 'empty reflog file' ' git branch empty && - : >.git/logs/refs/heads/empty && + git reflog expire --expire=all refs/heads/empty && git log -g empty >actual && test_cmp expect actual @@ -166,4 +166,9 @@ test_expect_success 'git log -g -p shows diffs vs. parents' ' test_cmp expect actual ' +test_expect_success 'reflog exists works' ' + git reflog exists refs/heads/master && + ! git reflog exists refs/heads/nonexistent +' + test_done diff --git a/t/t1503-rev-parse-verify.sh b/t/t1503-rev-parse-verify.sh index 823fe1d799..ab27d0db5c 100755 --- a/t/t1503-rev-parse-verify.sh +++ b/t/t1503-rev-parse-verify.sh @@ -85,8 +85,7 @@ test_expect_success 'fails silently when using -q' ' test_expect_success 'fails silently when using -q with deleted reflogs' ' ref=$(git rev-parse HEAD) && - : >.git/logs/refs/test && - git update-ref -m "message for refs/test" refs/test "$ref" && + git update-ref --create-reflog -m "message for refs/test" refs/test "$ref" && git reflog delete --updateref --rewrite refs/test@{0} && test_must_fail git rev-parse -q --verify refs/test@{0} >error 2>&1 && test_must_be_empty error @@ -94,16 +93,14 @@ test_expect_success 'fails silently when using -q with deleted reflogs' ' test_expect_success 'fails silently when using -q with not enough reflogs' ' ref=$(git rev-parse HEAD) && - : >.git/logs/refs/test2 && - git update-ref -m "message for refs/test2" refs/test2 "$ref" && + git update-ref --create-reflog -m "message for refs/test2" refs/test2 "$ref" && test_must_fail git rev-parse -q --verify refs/test2@{999} >error 2>&1 && test_must_be_empty error ' test_expect_success 'succeeds silently with -q and reflogs that do not go far back enough in time' ' ref=$(git rev-parse HEAD) && - : >.git/logs/refs/test3 && - git update-ref -m "message for refs/test3" refs/test3 "$ref" && + git update-ref --create-reflog -m "message for refs/test3" refs/test3 "$ref" && git rev-parse -q --verify refs/test3@{1.year.ago} >actual 2>error && test_must_be_empty error && echo "$ref" >expect && diff --git a/t/t1509-root-worktree.sh b/t/t1509-root-worktree.sh index b6977d4b39..553a3f601b 100755 --- a/t/t1509-root-worktree.sh +++ b/t/t1509-root-worktree.sh @@ -125,7 +125,7 @@ fi ONE_SHA1=d00491fd7e5bb6fa28c517a0bb32b8b506539d4d test_expect_success 'setup' ' - rm -rf /foo + rm -rf /foo && mkdir /foo && mkdir /foo/bar && echo 1 > /foo/foome && @@ -218,7 +218,7 @@ unset GIT_WORK_TREE test_expect_success 'go to /' 'cd /' test_expect_success 'setup' ' - rm -rf /.git + rm -rf /.git && echo "Initialized empty Git repository in /.git/" > expected && git init > result && test_cmp expected result @@ -241,8 +241,8 @@ say "auto bare gitdir" # DESTROYYYYY!!!!! test_expect_success 'setup' ' - rm -rf /refs /objects /info /hooks - rm /* + rm -rf /refs /objects /info /hooks && + rm -f /expected /ls.expected /me /result && cd / && echo "Initialized empty Git repository in /" > expected && git init --bare > result && diff --git a/t/t2019-checkout-ambiguous-ref.sh b/t/t2019-checkout-ambiguous-ref.sh index 8396320d52..199b22d85e 100755 --- a/t/t2019-checkout-ambiguous-ref.sh +++ b/t/t2019-checkout-ambiguous-ref.sh @@ -69,7 +69,7 @@ test_expect_success 'wildcard ambiguation, paths win' ' ) ' -test_expect_success 'wildcard ambiguation, refs lose' ' +test_expect_success !MINGW 'wildcard ambiguation, refs lose' ' git init ambi2 && ( cd ambi2 && diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh index ead8aa2a9d..8267411a0e 100755 --- a/t/t2025-worktree-add.sh +++ b/t/t2025-worktree-add.sh @@ -83,6 +83,14 @@ test_expect_success 'die the same branch is already checked out' ' ) ' +test_expect_success SYMLINKS 'die the same branch is already checked out (symlink)' ' + head=$(git -C there rev-parse --git-path HEAD) && + ref=$(git -C there symbolic-ref HEAD) && + rm "$head" && + ln -s "$ref" "$head" && + test_must_fail git -C here checkout newmaster +' + test_expect_success 'not die the same branch is already checked out' ' ( cd here && @@ -145,6 +153,14 @@ test_expect_success '"add -b" with <branch> omitted' ' test_cmp_rev HEAD burble ' +test_expect_success '"add --detach" with <branch> omitted' ' + git worktree add --detach fishhook && + git rev-parse HEAD >expected && + git -C fishhook rev-parse HEAD >actual && + test_cmp expected actual && + test_must_fail git -C fishhook symbolic-ref HEAD +' + test_expect_success '"add" with <branch> omitted' ' git worktree add wiffle/bat && test_cmp_rev HEAD bat @@ -159,4 +175,22 @@ test_expect_success '"add" auto-vivify does not clobber existing branch' ' test_path_is_missing precious ' +test_expect_success '"add" no auto-vivify with --detach and <branch> omitted' ' + git worktree add --detach mish/mash && + test_must_fail git rev-parse mash -- && + test_must_fail git -C mish/mash symbolic-ref HEAD +' + +test_expect_success '"add" -b/-B mutually exclusive' ' + test_must_fail git worktree add -b poodle -B poodle bamboo master +' + +test_expect_success '"add" -b/--detach mutually exclusive' ' + test_must_fail git worktree add -b poodle --detach bamboo master +' + +test_expect_success '"add" -B/--detach mutually exclusive' ' + test_must_fail git worktree add -B poodle --detach bamboo master +' + test_done diff --git a/t/t3020-ls-files-error-unmatch.sh b/t/t3020-ls-files-error-unmatch.sh index ca01053bcc..124e73b8e6 100755 --- a/t/t3020-ls-files-error-unmatch.sh +++ b/t/t3020-ls-files-error-unmatch.sh @@ -22,7 +22,7 @@ test_expect_success \ 'test_must_fail git ls-files --error-unmatch foo bar-does-not-match' test_expect_success \ - 'git ls-files --error-unmatch should succeed eith matched paths.' \ + 'git ls-files --error-unmatch should succeed with matched paths.' \ 'git ls-files --error-unmatch foo bar' test_done diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index ddea49808d..cdaf6f64ec 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -59,7 +59,7 @@ test_expect_success 'git branch -l d/e/f should create a branch and a log' ' test_expect_success 'git branch -d d/e/f should delete a branch and a log' ' git branch -d d/e/f && test_path_is_missing .git/refs/heads/d/e/f && - test_path_is_missing .git/logs/refs/heads/d/e/f + test_must_fail git reflog exists refs/heads/d/e/f ' test_expect_success 'git branch j/k should work after branch j has been deleted' ' @@ -82,13 +82,13 @@ test_expect_success 'git branch -m dumps usage' ' test_expect_success 'git branch -m m m/m should work' ' git branch -l m && git branch -m m m/m && - test_path_is_file .git/logs/refs/heads/m/m + git reflog exists refs/heads/m/m ' test_expect_success 'git branch -m n/n n should work' ' git branch -l n/n && git branch -m n/n n && - test_path_is_file .git/logs/refs/heads/n + git reflog exists refs/heads/n ' test_expect_success 'git branch -m o/o o should fail when o/p exists' ' @@ -267,12 +267,12 @@ git config branch.s/s.dummy Hello test_expect_success 'git branch -m s/s s should work when s/t is deleted' ' git branch -l s/s && - test_path_is_file .git/logs/refs/heads/s/s && + git reflog exists refs/heads/s/s && git branch -l s/t && - test_path_is_file .git/logs/refs/heads/s/t && + git reflog exists refs/heads/s/t && git branch -d s/t && git branch -m s/s s && - test_path_is_file .git/logs/refs/heads/s + git reflog exists refs/heads/s ' test_expect_success 'config information was renamed, too' ' diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh index 8aae98d482..7b5b6d452e 100755 --- a/t/t3210-pack-refs.sh +++ b/t/t3210-pack-refs.sh @@ -169,7 +169,7 @@ test_expect_success 'create packed foo/bar/baz branch' ' git branch foo/bar/baz && git pack-refs --all --prune && test_path_is_missing .git/refs/heads/foo/bar/baz && - test_path_is_missing .git/logs/refs/heads/foo/bar/baz + test_must_fail git reflog exists refs/heads/foo/bar/baz ' test_expect_success 'notice d/f conflict with existing directory' ' diff --git a/t/t3320-notes-merge-worktrees.sh b/t/t3320-notes-merge-worktrees.sh new file mode 100755 index 0000000000..1f71d589f5 --- /dev/null +++ b/t/t3320-notes-merge-worktrees.sh @@ -0,0 +1,72 @@ +#!/bin/sh +# +# Copyright (c) 2015 Twitter, Inc +# + +test_description='Test merging of notes trees in multiple worktrees' + +. ./test-lib.sh + +test_expect_success 'setup commit' ' + test_commit tantrum +' + +commit_tantrum=$(git rev-parse tantrum^{commit}) + +test_expect_success 'setup notes ref (x)' ' + git config core.notesRef refs/notes/x && + git notes add -m "x notes on tantrum" tantrum +' + +test_expect_success 'setup local branch (y)' ' + git update-ref refs/notes/y refs/notes/x && + git config core.notesRef refs/notes/y && + git notes remove tantrum +' + +test_expect_success 'setup remote branch (z)' ' + git update-ref refs/notes/z refs/notes/x && + git config core.notesRef refs/notes/z && + git notes add -f -m "conflicting notes on tantrum" tantrum +' + +test_expect_success 'modify notes ref ourselves (x)' ' + git config core.notesRef refs/notes/x && + git notes add -f -m "more conflicting notes on tantrum" tantrum +' + +test_expect_success 'create some new worktrees' ' + git worktree add -b newbranch worktree master && + git worktree add -b newbranch2 worktree2 master +' + +test_expect_success 'merge z into y fails and sets NOTES_MERGE_REF' ' + git config core.notesRef refs/notes/y && + test_must_fail git notes merge z && + echo "ref: refs/notes/y" >expect && + test_cmp .git/NOTES_MERGE_REF expect +' + +test_expect_success 'merge z into y while mid-merge in another workdir fails' ' + ( + cd worktree && + git config core.notesRef refs/notes/y && + test_must_fail git notes merge z 2>err && + grep "A notes merge into refs/notes/y is already in-progress at" err + ) && + test_path_is_missing .git/worktrees/worktree/NOTES_MERGE_REF +' + +test_expect_success 'merge z into x while mid-merge on y succeeds' ' + ( + cd worktree2 && + git config core.notesRef refs/notes/x && + test_must_fail git notes merge z 2>&1 >out && + grep "Automatic notes merge failed" out && + grep -v "A notes merge into refs/notes/x is already in-progress in" out + ) && + echo "ref: refs/notes/x" >expect && + test_cmp .git/worktrees/worktree2/NOTES_MERGE_REF expect +' + +test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 9d26064aba..d26e3f57dc 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -961,13 +961,13 @@ test_expect_success 'rebase -i produces readable reflog' ' set_fake_editor && git rebase -i --onto I F branch-reflog-test && cat >expect <<-\EOF && - rebase -i (start): checkout I - rebase -i (pick): G - rebase -i (pick): H rebase -i (finish): returning to refs/heads/branch-reflog-test + rebase -i (pick): H + rebase -i (pick): G + rebase -i (start): checkout I EOF - tail -n 4 .git/logs/HEAD | - sed -e "s/.* //" >actual && + git reflog -n4 HEAD | + sed "s/[^:]*: //" >actual && test_cmp expect actual ' diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index f5f18b7d21..2142c1fa92 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -672,7 +672,7 @@ test_expect_success 'store updates stash ref and reflog' ' ! grep quux bazzy && git stash store -m quuxery $STASH_ID && test $(cat .git/refs/stash) = $STASH_ID && - grep $STASH_ID .git/logs/refs/stash && + git reflog --format=%H stash| grep $STASH_ID && git stash pop && grep quux bazzy ' diff --git a/t/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh index 1dbaa3864a..67373dc44e 100755 --- a/t/t4018-diff-funcname.sh +++ b/t/t4018-diff-funcname.sh @@ -31,6 +31,7 @@ diffpatterns=" cpp csharp fortran + fountain html java matlab diff --git a/t/t4018/fountain-scene b/t/t4018/fountain-scene new file mode 100644 index 0000000000..6b3257d680 --- /dev/null +++ b/t/t4018/fountain-scene @@ -0,0 +1,4 @@ +EXT. STREET RIGHT OUTSIDE - DAY + +CHARACTER +You didn't say the magic phrase, "ChangeMe". diff --git a/t/t4151-am-abort.sh b/t/t4151-am-abort.sh index 05bdc3ee49..ea5ace99a1 100755 --- a/t/t4151-am-abort.sh +++ b/t/t4151-am-abort.sh @@ -168,4 +168,28 @@ test_expect_success 'am --abort on unborn branch will keep local commits intact' test_cmp expect actual ' +test_expect_success 'am --skip leaves index stat info alone' ' + git checkout -f --orphan skip-stat-info && + git reset && + test_commit skip-should-be-untouched && + test-chmtime =0 skip-should-be-untouched.t && + git update-index --refresh && + git diff-files --exit-code --quiet && + test_must_fail git am 0001-*.patch && + git am --skip && + git diff-files --exit-code --quiet +' + +test_expect_success 'am --abort leaves index stat info alone' ' + git checkout -f --orphan abort-stat-info && + git reset && + test_commit abort-should-be-untouched && + test-chmtime =0 abort-should-be-untouched.t && + git update-index --refresh && + git diff-files --exit-code --quiet && + test_must_fail git am 0001-*.patch && + git am --abort && + git diff-files --exit-code --quiet +' + test_done diff --git a/t/t5312-prune-corruption.sh b/t/t5312-prune-corruption.sh index 8e98b44083..da9d59940d 100755 --- a/t/t5312-prune-corruption.sh +++ b/t/t5312-prune-corruption.sh @@ -12,7 +12,7 @@ delete objects that cannot be recovered. test_expect_success 'disable reflogs' ' git config core.logallrefupdates false && - rm -rf .git/logs + git reflog expire --expire=all --all ' test_expect_success 'create history reachable only from a bogus-named ref' ' diff --git a/t/t5511-refspec.sh b/t/t5511-refspec.sh index de6db86ccf..f541f30bc2 100755 --- a/t/t5511-refspec.sh +++ b/t/t5511-refspec.sh @@ -71,15 +71,18 @@ test_refspec fetch ':refs/remotes/frotz/HEAD-to-me' test_refspec push ':refs/remotes/frotz/delete me' invalid test_refspec fetch ':refs/remotes/frotz/HEAD to me' invalid -test_refspec fetch 'refs/heads/*/for-linus:refs/remotes/mine/*-blah' invalid -test_refspec push 'refs/heads/*/for-linus:refs/remotes/mine/*-blah' invalid +test_refspec fetch 'refs/heads/*/for-linus:refs/remotes/mine/*-blah' +test_refspec push 'refs/heads/*/for-linus:refs/remotes/mine/*-blah' -test_refspec fetch 'refs/heads*/for-linus:refs/remotes/mine/*' invalid -test_refspec push 'refs/heads*/for-linus:refs/remotes/mine/*' invalid +test_refspec fetch 'refs/heads*/for-linus:refs/remotes/mine/*' +test_refspec push 'refs/heads*/for-linus:refs/remotes/mine/*' test_refspec fetch 'refs/heads/*/*/for-linus:refs/remotes/mine/*' invalid test_refspec push 'refs/heads/*/*/for-linus:refs/remotes/mine/*' invalid +test_refspec fetch 'refs/heads/*g*/for-linus:refs/remotes/mine/*' invalid +test_refspec push 'refs/heads/*g*/for-linus:refs/remotes/mine/*' invalid + test_refspec fetch 'refs/heads/*/for-linus:refs/remotes/mine/*' test_refspec push 'refs/heads/*/for-linus:refs/remotes/mine/*' diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh index 3bd9759e0f..aadaac515e 100755 --- a/t/t5512-ls-remote.sh +++ b/t/t5512-ls-remote.sh @@ -128,6 +128,11 @@ test_expect_success 'Report match with --exit-code' ' test_cmp expect actual ' +test_expect_success 'set up some extra tags for ref hiding' ' + git tag magic/one && + git tag magic/two +' + for configsection in transfer uploadpack do test_expect_success "Hide some refs with $configsection.hiderefs" ' @@ -138,6 +143,24 @@ do sed -e "/ refs\/tags\//d" >expect && test_cmp expect actual ' + + test_expect_success "Override hiding of $configsection.hiderefs" ' + test_when_finished "test_unconfig $configsection.hiderefs" && + git config --add $configsection.hiderefs refs/tags && + git config --add $configsection.hiderefs "!refs/tags/magic" && + git config --add $configsection.hiderefs refs/tags/magic/one && + git ls-remote . >actual && + grep refs/tags/magic/two actual && + ! grep refs/tags/magic/one actual + ' + done +test_expect_success 'overrides work between mixed transfer/upload-pack hideRefs' ' + test_config uploadpack.hiderefs refs/tags && + test_config transfer.hiderefs "!refs/tags/magic" && + git ls-remote . >actual && + grep refs/tags/magic actual +' + test_done diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index f4a7193677..a0013ee32f 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -245,6 +245,17 @@ test_expect_success '--rebase fails with multiple branches' ' test modified = "$(git show HEAD:file)" ' +test_expect_success 'pull --rebase succeeds with dirty working directory and rebase.autostash set' ' + test_config rebase.autostash true && + git reset --hard before-rebase && + echo dirty >new_file && + git add new_file && + git pull --rebase . copy && + test_cmp_rev HEAD^ copy && + test "$(cat new_file)" = dirty && + test "$(cat file)" = "modified again" +' + test_expect_success 'pull.rebase' ' git reset --hard before-rebase && test_config pull.rebase true && diff --git a/t/t5603-clone-dirname.sh b/t/t5603-clone-dirname.sh new file mode 100755 index 0000000000..d5af758129 --- /dev/null +++ b/t/t5603-clone-dirname.sh @@ -0,0 +1,106 @@ +#!/bin/sh + +test_description='check output directory names used by git-clone' +. ./test-lib.sh + +# we use a fake ssh wrapper that ignores the arguments +# entirely; we really only care that we get _some_ repo, +# as the real test is what clone does on the local side +test_expect_success 'setup ssh wrapper' ' + write_script "$TRASH_DIRECTORY/ssh-wrapper" <<-\EOF && + git upload-pack "$TRASH_DIRECTORY" + EOF + GIT_SSH="$TRASH_DIRECTORY/ssh-wrapper" && + export GIT_SSH && + export TRASH_DIRECTORY +' + +# make sure that cloning $1 results in local directory $2 +test_clone_dir () { + url=$1; shift + dir=$1; shift + expect=success + bare=non-bare + clone_opts= + for i in "$@" + do + case "$i" in + fail) + expect=failure + ;; + bare) + bare=bare + clone_opts=--bare + ;; + esac + done + test_expect_$expect "clone of $url goes to $dir ($bare)" " + rm -rf $dir && + git clone $clone_opts $url && + test_path_is_dir $dir + " +} + +# basic syntax with bare and non-bare variants +test_clone_dir host:foo foo +test_clone_dir host:foo foo.git bare +test_clone_dir host:foo.git foo +test_clone_dir host:foo.git foo.git bare +test_clone_dir host:foo/.git foo +test_clone_dir host:foo/.git foo.git bare + +# similar, but using ssh URL rather than host:path syntax +test_clone_dir ssh://host/foo foo +test_clone_dir ssh://host/foo foo.git bare +test_clone_dir ssh://host/foo.git foo +test_clone_dir ssh://host/foo.git foo.git bare +test_clone_dir ssh://host/foo/.git foo +test_clone_dir ssh://host/foo/.git foo.git bare + +# we should remove trailing slashes and .git suffixes +test_clone_dir ssh://host/foo/ foo +test_clone_dir ssh://host/foo/// foo +test_clone_dir ssh://host/foo/.git/ foo +test_clone_dir ssh://host/foo.git/ foo +test_clone_dir ssh://host/foo.git/// foo +test_clone_dir ssh://host/foo///.git/ foo +test_clone_dir ssh://host/foo/.git/// foo + +test_clone_dir host:foo/ foo +test_clone_dir host:foo/// foo +test_clone_dir host:foo.git/ foo +test_clone_dir host:foo/.git/ foo +test_clone_dir host:foo.git/// foo +test_clone_dir host:foo///.git/ foo +test_clone_dir host:foo/.git/// foo + +# omitting the path should default to the hostname +test_clone_dir ssh://host/ host +test_clone_dir ssh://host:1234/ host +test_clone_dir ssh://user@host/ host +test_clone_dir host:/ host + +# auth materials should be redacted +test_clone_dir ssh://user:password@host/ host +test_clone_dir ssh://user:password@host:1234/ host +test_clone_dir ssh://user:passw@rd@host:1234/ host +test_clone_dir user@host:/ host +test_clone_dir user:password@host:/ host +test_clone_dir user:passw@rd@host:/ host + +# auth-like material should not be dropped +test_clone_dir ssh://host/foo@bar foo@bar +test_clone_dir ssh://host/foo@bar.git foo@bar +test_clone_dir ssh://user:password@host/foo@bar foo@bar +test_clone_dir ssh://user:passw@rd@host/foo@bar.git foo@bar + +test_clone_dir host:/foo@bar foo@bar +test_clone_dir host:/foo@bar.git foo@bar +test_clone_dir user:password@host:/foo@bar foo@bar +test_clone_dir user:passw@rd@host:/foo@bar.git foo@bar + +# trailing port-like numbers should not be stripped for paths +test_clone_dir ssh://user:password@host/test:1234 1234 +test_clone_dir ssh://user:password@host/test:1234.git 1234 + +test_done diff --git a/t/t5700-clone-reference.sh b/t/t5700-clone-reference.sh index 3e783fc450..ef1779f5ca 100755 --- a/t/t5700-clone-reference.sh +++ b/t/t5700-clone-reference.sh @@ -10,49 +10,51 @@ base_dir=`pwd` U=$base_dir/UPLOAD_LOG -test_expect_success 'preparing first repository' \ -'test_create_repo A && cd A && -echo first > file1 && -git add file1 && -git commit -m initial' - -cd "$base_dir" - -test_expect_success 'preparing second repository' \ -'git clone A B && cd B && -echo second > file2 && -git add file2 && -git commit -m addition && -git repack -a -d && -git prune' - -cd "$base_dir" - -test_expect_success 'cloning with reference (-l -s)' \ -'git clone -l -s --reference B A C' - -cd "$base_dir" - -test_expect_success 'existence of info/alternates' \ -'test_line_count = 2 C/.git/objects/info/alternates' - -cd "$base_dir" +# create a commit in repo $1 with name $2 +commit_in () { + ( + cd "$1" && + echo "$2" >"$2" && + git add "$2" && + git commit -m "$2" + ) +} + +# check that there are $2 loose objects in repo $1 +test_objcount () { + echo "$2" >expect && + git -C "$1" count-objects >actual.raw && + cut -d' ' -f1 <actual.raw >actual && + test_cmp expect actual +} + +test_expect_success 'preparing first repository' ' + test_create_repo A && + commit_in A file1 +' -test_expect_success 'pulling from reference' \ -'cd C && -git pull ../B master' +test_expect_success 'preparing second repository' ' + git clone A B && + commit_in B file2 && + git -C B repack -ad && + git -C B prune +' -cd "$base_dir" +test_expect_success 'cloning with reference (-l -s)' ' + git clone -l -s --reference B A C +' -test_expect_success 'that reference gets used' \ -'cd C && -echo "0 objects, 0 kilobytes" > expected && -git count-objects > current && -test_cmp expected current' +test_expect_success 'existence of info/alternates' ' + test_line_count = 2 C/.git/objects/info/alternates +' -cd "$base_dir" +test_expect_success 'pulling from reference' ' + git -C C pull ../B master +' -rm -f "$U.D" +test_expect_success 'that reference gets used' ' + test_objcount C 0 +' test_expect_success 'cloning with reference (no -l -s)' ' GIT_TRACE_PACKET=$U.D git clone --reference B "file://$(pwd)/A" D @@ -63,95 +65,69 @@ test_expect_success 'fetched no objects' ' ! grep " want" "$U.D" ' -cd "$base_dir" - -test_expect_success 'existence of info/alternates' \ -'test_line_count = 1 D/.git/objects/info/alternates' - -cd "$base_dir" - -test_expect_success 'pulling from reference' \ -'cd D && git pull ../B master' - -cd "$base_dir" - -test_expect_success 'that reference gets used' \ -'cd D && echo "0 objects, 0 kilobytes" > expected && -git count-objects > current && -test_cmp expected current' - -cd "$base_dir" +test_expect_success 'existence of info/alternates' ' + test_line_count = 1 D/.git/objects/info/alternates +' -test_expect_success 'updating origin' \ -'cd A && -echo third > file3 && -git add file3 && -git commit -m update && -git repack -a -d && -git prune' +test_expect_success 'pulling from reference' ' + git -C D pull ../B master +' -cd "$base_dir" +test_expect_success 'that reference gets used' ' + test_objcount D 0 +' -test_expect_success 'pulling changes from origin' \ -'cd C && -git pull origin' +test_expect_success 'updating origin' ' + commit_in A file3 && + git -C A repack -ad && + git -C A prune +' -cd "$base_dir" +test_expect_success 'pulling changes from origin' ' + git -C C pull origin +' # the 2 local objects are commit and tree from the merge -test_expect_success 'that alternate to origin gets used' \ -'cd C && -echo "2 objects" > expected && -git count-objects | cut -d, -f1 > current && -test_cmp expected current' - -cd "$base_dir" - -test_expect_success 'pulling changes from origin' \ -'cd D && -git pull origin' +test_expect_success 'that alternate to origin gets used' ' + test_objcount C 2 +' -cd "$base_dir" +test_expect_success 'pulling changes from origin' ' + git -C D pull origin +' # the 5 local objects are expected; file3 blob, commit in A to add it # and its tree, and 2 are our tree and the merge commit. -test_expect_success 'check objects expected to exist locally' \ -'cd D && -echo "5 objects" > expected && -git count-objects | cut -d, -f1 > current && -test_cmp expected current' - -cd "$base_dir" - -test_expect_success 'preparing alternate repository #1' \ -'test_create_repo F && cd F && -echo first > file1 && -git add file1 && -git commit -m initial' - -cd "$base_dir" - -test_expect_success 'cloning alternate repo #2 and adding changes to repo #1' \ -'git clone F G && cd F && -echo second > file2 && -git add file2 && -git commit -m addition' +test_expect_success 'check objects expected to exist locally' ' + test_objcount D 5 +' -cd "$base_dir" +test_expect_success 'preparing alternate repository #1' ' + test_create_repo F && + commit_in F file1 +' -test_expect_success 'cloning alternate repo #1, using #2 as reference' \ -'git clone --reference G F H' +test_expect_success 'cloning alternate repo #2 and adding changes to repo #1' ' + git clone F G && + commit_in F file2 +' -cd "$base_dir" +test_expect_success 'cloning alternate repo #1, using #2 as reference' ' + git clone --reference G F H +' -test_expect_success 'cloning with reference being subset of source (-l -s)' \ -'git clone -l -s --reference A B E' +test_expect_success 'cloning with reference being subset of source (-l -s)' ' + git clone -l -s --reference A B E +' -cd "$base_dir" +test_expect_success 'cloning with multiple references drops duplicates' ' + git clone -s --reference B --reference A --reference B A dups && + test_line_count = 2 dups/.git/objects/info/alternates +' test_expect_success 'clone with reference from a tagged repository' ' ( - cd A && git tag -a -m 'tagged' HEAD + cd A && git tag -a -m tagged HEAD ) && git clone --reference=A A I ' @@ -168,8 +144,6 @@ test_expect_success 'prepare branched repository' ' ) ' -rm -f "$U.K" - test_expect_success 'fetch with incomplete alternates' ' git init K && echo "$base_dir/A/.git/objects" >K/.git/objects/info/alternates && diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 06b4868109..9e2c203747 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -362,7 +362,7 @@ test_expect_success 'bisect starting with a detached HEAD' ' test_expect_success 'bisect errors out if bad and good are mistaken' ' git bisect reset && test_must_fail git bisect start $HASH2 $HASH4 2> rev_list_error && - grep "mistake good and bad" rev_list_error && + grep "mistook good and bad" rev_list_error && git bisect reset ' diff --git a/t/t6501-freshen-objects.sh b/t/t6501-freshen-objects.sh index 2adf825076..cf076dcd94 100755 --- a/t/t6501-freshen-objects.sh +++ b/t/t6501-freshen-objects.sh @@ -56,7 +56,7 @@ for repack in '' true; do test_expect_success "disable reflogs ($title)" ' git config core.logallrefupdates false && - rm -rf .git/logs + git reflog expire --expire=all --all ' test_expect_success "setup basic history ($title)" ' diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index d1ff5c94f2..d31788cc6c 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -51,7 +51,19 @@ test_expect_success 'creating a tag using default HEAD should succeed' ' echo foo >foo && git add foo && git commit -m Foo && - git tag mytag + git tag mytag && + test_must_fail git reflog exists refs/tags/mytag +' + +test_expect_success 'creating a tag with --create-reflog should create reflog' ' + test_when_finished "git tag -d tag_with_reflog" && + git tag --create-reflog tag_with_reflog && + git reflog exists refs/tags/tag_with_reflog +' + +test_expect_success '--create-reflog does not create reflog on failure' ' + test_must_fail git tag --create-reflog mytag && + test_must_fail git reflog exists refs/tags/mytag ' test_expect_success 'listing all tags if one exists should succeed' ' diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh index 947b690fd7..6ea7ac4c41 100755 --- a/t/t7006-pager.sh +++ b/t/t7006-pager.sh @@ -447,4 +447,13 @@ test_expect_success TTY 'external command pagers override sub-commands' ' test_cmp expect actual ' +test_expect_success 'command with underscores does not complain' ' + write_script git-under_score <<-\EOF && + echo ok + EOF + git --exec-path=. under_score >actual 2>&1 && + echo ok >expect && + test_cmp expect actual +' + test_done diff --git a/t/t7063-status-untracked-cache.sh b/t/t7063-status-untracked-cache.sh index bd4806c12a..37a24c1312 100755 --- a/t/t7063-status-untracked-cache.sh +++ b/t/t7063-status-untracked-cache.sh @@ -354,4 +354,219 @@ EOF test_cmp ../expect ../actual ' +test_expect_success 'set up for sparse checkout testing' ' + echo two >done/.gitignore && + echo three >>done/.gitignore && + echo two >done/two && + git add -f done/two done/.gitignore && + git commit -m "first commit" +' + +test_expect_success 'status after commit' ' + : >../trace && + GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \ + git status --porcelain >../actual && + cat >../status.expect <<EOF && +?? .gitignore +?? dtwo/ +EOF + test_cmp ../status.expect ../actual && + cat >../trace.expect <<EOF && +node creation: 0 +gitignore invalidation: 0 +directory invalidation: 0 +opendir: 2 +EOF + test_cmp ../trace.expect ../trace +' + +test_expect_success 'untracked cache correct after commit' ' + test-dump-untracked-cache >../actual && + cat >../expect <<EOF && +info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0 +core.excludesfile 0000000000000000000000000000000000000000 +exclude_per_dir .gitignore +flags 00000006 +/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid +.gitignore +dtwo/ +/done/ 0000000000000000000000000000000000000000 recurse valid +/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid +/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid +two +EOF + test_cmp ../expect ../actual +' + +test_expect_success 'set up sparse checkout' ' + echo "done/[a-z]*" >.git/info/sparse-checkout && + test_config core.sparsecheckout true && + git checkout master && + git update-index --force-untracked-cache && + git status --porcelain >/dev/null && # prime the cache + test_path_is_missing done/.gitignore && + test_path_is_file done/one +' + +test_expect_success 'create/modify files, some of which are gitignored' ' + echo two bis >done/two && + echo three >done/three && # three is gitignored + echo four >done/four && # four is gitignored at a higher level + echo five >done/five # five is not gitignored +' + +test_expect_success 'test sparse status with untracked cache' ' + : >../trace && + avoid_racy && + GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \ + git status --porcelain >../status.actual && + cat >../status.expect <<EOF && + M done/two +?? .gitignore +?? done/five +?? dtwo/ +EOF + test_cmp ../status.expect ../status.actual && + cat >../trace.expect <<EOF && +node creation: 0 +gitignore invalidation: 1 +directory invalidation: 2 +opendir: 2 +EOF + test_cmp ../trace.expect ../trace +' + +test_expect_success 'untracked cache correct after status' ' + test-dump-untracked-cache >../actual && + cat >../expect <<EOF && +info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0 +core.excludesfile 0000000000000000000000000000000000000000 +exclude_per_dir .gitignore +flags 00000006 +/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid +.gitignore +dtwo/ +/done/ 1946f0437f90c5005533cbe1736a6451ca301714 recurse valid +five +/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid +/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid +two +EOF + test_cmp ../expect ../actual +' + +test_expect_success 'test sparse status again with untracked cache' ' + avoid_racy && + : >../trace && + GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \ + git status --porcelain >../status.actual && + cat >../status.expect <<EOF && + M done/two +?? .gitignore +?? done/five +?? dtwo/ +EOF + test_cmp ../status.expect ../status.actual && + cat >../trace.expect <<EOF && +node creation: 0 +gitignore invalidation: 0 +directory invalidation: 0 +opendir: 0 +EOF + test_cmp ../trace.expect ../trace +' + +test_expect_success 'set up for test of subdir and sparse checkouts' ' + mkdir done/sub && + mkdir done/sub/sub && + echo "sub" > done/sub/sub/file +' + +test_expect_success 'test sparse status with untracked cache and subdir' ' + avoid_racy && + : >../trace && + GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \ + git status --porcelain >../status.actual && + cat >../status.expect <<EOF && + M done/two +?? .gitignore +?? done/five +?? done/sub/ +?? dtwo/ +EOF + test_cmp ../status.expect ../status.actual && + cat >../trace.expect <<EOF && +node creation: 2 +gitignore invalidation: 0 +directory invalidation: 1 +opendir: 3 +EOF + test_cmp ../trace.expect ../trace +' + +test_expect_success 'verify untracked cache dump (sparse/subdirs)' ' + test-dump-untracked-cache >../actual && + cat >../expect <<EOF && +info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0 +core.excludesfile 0000000000000000000000000000000000000000 +exclude_per_dir .gitignore +flags 00000006 +/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid +.gitignore +dtwo/ +/done/ 1946f0437f90c5005533cbe1736a6451ca301714 recurse valid +five +sub/ +/done/sub/ 0000000000000000000000000000000000000000 recurse check_only valid +sub/ +/done/sub/sub/ 0000000000000000000000000000000000000000 recurse check_only valid +file +/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid +/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid +two +EOF + test_cmp ../expect ../actual +' + +test_expect_success 'test sparse status again with untracked cache and subdir' ' + avoid_racy && + : >../trace && + GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \ + git status --porcelain >../status.actual && + test_cmp ../status.expect ../status.actual && + cat >../trace.expect <<EOF && +node creation: 0 +gitignore invalidation: 0 +directory invalidation: 0 +opendir: 0 +EOF + test_cmp ../trace.expect ../trace +' + +test_expect_success 'move entry in subdir from untracked to cached' ' + git add dtwo/two && + git status --porcelain >../status.actual && + cat >../status.expect <<EOF && + M done/two +A dtwo/two +?? .gitignore +?? done/five +?? done/sub/ +EOF + test_cmp ../status.expect ../status.actual +' + +test_expect_success 'move entry in subdir from cached to untracked' ' + git rm --cached dtwo/two && + git status --porcelain >../status.actual && + cat >../status.expect <<EOF && + M done/two +?? .gitignore +?? done/five +?? done/sub/ +?? dtwo/ +EOF + test_cmp ../status.expect ../status.actual +' + test_done diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh index 32e96da7e3..27557d64f3 100755 --- a/t/t7300-clean.sh +++ b/t/t7300-clean.sh @@ -499,7 +499,7 @@ test_expect_success 'should not clean submodules' ' test_path_is_missing to_clean ' -test_expect_success 'should avoid cleaning possible submodules' ' +test_expect_success POSIXPERM 'should avoid cleaning possible submodules' ' rm -fr to_clean possible_sub1 && mkdir to_clean possible_sub1 && test_when_finished "rm -rf possible_sub*" && diff --git a/t/t7411-submodule-config.sh b/t/t7411-submodule-config.sh new file mode 100755 index 0000000000..fc97c3314e --- /dev/null +++ b/t/t7411-submodule-config.sh @@ -0,0 +1,153 @@ +#!/bin/sh +# +# Copyright (c) 2014 Heiko Voigt +# + +test_description='Test submodules config cache infrastructure + +This test verifies that parsing .gitmodules configurations directly +from the database and from the worktree works. +' + +TEST_NO_CREATE_REPO=1 +. ./test-lib.sh + +test_expect_success 'submodule config cache setup' ' + mkdir submodule && + (cd submodule && + git init && + echo a >a && + git add . && + git commit -ma + ) && + mkdir super && + (cd super && + git init && + git submodule add ../submodule && + git submodule add ../submodule a && + git commit -m "add as submodule and as a" && + git mv a b && + git commit -m "move a to b" + ) +' + +cat >super/expect <<EOF +Submodule name: 'a' for path 'a' +Submodule name: 'a' for path 'b' +Submodule name: 'submodule' for path 'submodule' +Submodule name: 'submodule' for path 'submodule' +EOF + +test_expect_success 'test parsing and lookup of submodule config by path' ' + (cd super && + test-submodule-config \ + HEAD^ a \ + HEAD b \ + HEAD^ submodule \ + HEAD submodule \ + >actual && + test_cmp expect actual + ) +' + +test_expect_success 'test parsing and lookup of submodule config by name' ' + (cd super && + test-submodule-config --name \ + HEAD^ a \ + HEAD a \ + HEAD^ submodule \ + HEAD submodule \ + >actual && + test_cmp expect actual + ) +' + +cat >super/expect_error <<EOF +Submodule name: 'a' for path 'b' +Submodule name: 'submodule' for path 'submodule' +EOF + +test_expect_success 'error in one submodule config lets continue' ' + (cd super && + cp .gitmodules .gitmodules.bak && + echo " value = \"" >>.gitmodules && + git add .gitmodules && + mv .gitmodules.bak .gitmodules && + git commit -m "add error" && + test-submodule-config \ + HEAD b \ + HEAD submodule \ + >actual && + test_cmp expect_error actual + ) +' + +cat >super/expect_url <<EOF +Submodule url: 'git@somewhere.else.net:a.git' for path 'b' +Submodule url: 'git@somewhere.else.net:submodule.git' for path 'submodule' +EOF + +cat >super/expect_local_path <<EOF +Submodule name: 'a' for path 'c' +Submodule name: 'submodule' for path 'submodule' +EOF + +test_expect_success 'reading of local configuration' ' + (cd super && + old_a=$(git config submodule.a.url) && + old_submodule=$(git config submodule.submodule.url) && + git config submodule.a.url git@somewhere.else.net:a.git && + git config submodule.submodule.url git@somewhere.else.net:submodule.git && + test-submodule-config --url \ + "" b \ + "" submodule \ + >actual && + test_cmp expect_url actual && + git config submodule.a.path c && + test-submodule-config \ + "" c \ + "" submodule \ + >actual && + test_cmp expect_local_path actual && + git config submodule.a.url $old_a && + git config submodule.submodule.url $old_submodule && + git config --unset submodule.a.path c + ) +' + +cat >super/expect_fetchrecurse_die.err <<EOF +fatal: bad submodule.submodule.fetchrecursesubmodules argument: blabla +EOF + +test_expect_success 'local error in fetchrecursesubmodule dies early' ' + (cd super && + git config submodule.submodule.fetchrecursesubmodules blabla && + test_must_fail test-submodule-config \ + "" b \ + "" submodule \ + >actual.out 2>actual.err && + touch expect_fetchrecurse_die.out && + test_cmp expect_fetchrecurse_die.out actual.out && + test_cmp expect_fetchrecurse_die.err actual.err && + git config --unset submodule.submodule.fetchrecursesubmodules + ) +' + +test_expect_success 'error in history in fetchrecursesubmodule lets continue' ' + (cd super && + git config -f .gitmodules \ + submodule.submodule.fetchrecursesubmodules blabla && + git add .gitmodules && + git config --unset -f .gitmodules \ + submodule.submodule.fetchrecursesubmodules && + git commit -m "add error in fetchrecursesubmodules" && + test-submodule-config \ + HEAD b \ + HEAD submodule \ + >actual && + test_cmp expect_error actual && + git reset --hard HEAD^ + ) +' + +test_done diff --git a/t/t7509-commit.sh b/t/t7509-commit.sh index 9ac794052d..db9774e345 100755 --- a/t/t7509-commit.sh +++ b/t/t7509-commit.sh @@ -90,22 +90,10 @@ sha1_file() { remove_object() { rm -f $(sha1_file "$*") } -no_reflog() { - cp .git/config .git/config.saved && - echo "[core] logallrefupdates = false" >>.git/config && - test_when_finished "mv -f .git/config.saved .git/config" && - - if test -e .git/logs - then - mv .git/logs . && - test_when_finished "mv logs .git/" - fi -} test_expect_success '--amend option with empty author' ' git cat-file commit Initial >tmp && sed "s/author [^<]* </author </" tmp >empty-author && - no_reflog && sha=$(git hash-object -t commit -w empty-author) && test_when_finished "remove_object $sha" && git checkout $sha && @@ -119,7 +107,6 @@ test_expect_success '--amend option with empty author' ' test_expect_success '--amend option with missing author' ' git cat-file commit Initial >tmp && sed "s/author [^<]* </author </" tmp >malformed && - no_reflog && sha=$(git hash-object -t commit -w malformed) && test_when_finished "remove_object $sha" && git checkout $sha && diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh index bd0ab46750..9577b4effb 100755 --- a/t/t7513-interpret-trailers.sh +++ b/t/t7513-interpret-trailers.sh @@ -93,12 +93,25 @@ test_expect_success 'with config option on the command line' ' Acked-by: Johan Reviewed-by: Peff EOF - echo "Acked-by: Johan" | + { echo; echo "Acked-by: Johan"; } | git -c "trailer.Acked-by.ifexists=addifdifferent" interpret-trailers \ --trailer "Reviewed-by: Peff" --trailer "Acked-by: Johan" >actual && test_cmp expected actual ' +test_expect_success 'with only a title in the message' ' + cat >expected <<-\EOF && + area: change + + Reviewed-by: Peff + Acked-by: Johan + EOF + echo "area: change" | + git interpret-trailers --trailer "Reviewed-by: Peff" \ + --trailer "Acked-by: Johan" >actual && + test_cmp expected actual +' + test_expect_success 'with config setup' ' git config trailer.ack.key "Acked-by: " && cat >expected <<-\EOF && diff --git a/t/test-lib.sh b/t/test-lib.sh index cea6cda604..16c4d7b516 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -531,6 +531,10 @@ maybe_setup_valgrind () { fi } +want_trace () { + test "$trace" = t && test "$verbose" = t +} + # This is a separate function because some tests use # "return" to end a test_expect_success block early # (and we want to make sure we run any cleanup like @@ -538,7 +542,7 @@ maybe_setup_valgrind () { test_eval_inner_ () { # Do not add anything extra (including LF) after '$*' eval " - test \"$trace\" = t && set -x + want_trace && set -x $*" } @@ -554,7 +558,7 @@ test_eval_ () { { test_eval_inner_ "$@" </dev/null >&3 2>&4 test_eval_ret_=$? - if test "$trace" = t + if want_trace then set +x if test "$test_eval_ret_" != 0 @@ -571,12 +575,17 @@ test_run_ () { expecting_failure=$2 if test "${GIT_TEST_CHAIN_LINT:-1}" != 0; then + # turn off tracing for this test-eval, as it simply creates + # confusing noise in the "-x" output + trace_tmp=$trace + trace= # 117 is magic because it is unlikely to match the exit # code of other programs test_eval_ "(exit 117) && $1" if test "$?" != 117; then error "bug in the test script: broken &&-chain: $1" fi + trace=$trace_tmp fi setup_malloc_check diff --git a/tempfile.c b/tempfile.c new file mode 100644 index 0000000000..0af7ebf016 --- /dev/null +++ b/tempfile.c @@ -0,0 +1,305 @@ +/* + * State diagram and cleanup + * ------------------------- + * + * If the program exits while a temporary file is active, we want to + * make sure that we remove it. This is done by remembering the active + * temporary files in a linked list, `tempfile_list`. An `atexit(3)` + * handler and a signal handler are registered, to clean up any active + * temporary files. + * + * Because the signal handler can run at any time, `tempfile_list` and + * the `tempfile` objects that comprise it must be kept in + * self-consistent states at all times. + * + * The possible states of a `tempfile` object are as follows: + * + * - Uninitialized. In this state the object's `on_list` field must be + * zero but the rest of its contents need not be initialized. As + * soon as the object is used in any way, it is irrevocably + * registered in `tempfile_list`, and `on_list` is set. + * + * - Active, file open (after `create_tempfile()` or + * `reopen_tempfile()`). In this state: + * + * - the temporary file exists + * - `active` is set + * - `filename` holds the filename of the temporary file + * - `fd` holds a file descriptor open for writing to it + * - `fp` holds a pointer to an open `FILE` object if and only if + * `fdopen_tempfile()` has been called on the object + * - `owner` holds the PID of the process that created the file + * + * - Active, file closed (after successful `close_tempfile()`). Same + * as the previous state, except that the temporary file is closed, + * `fd` is -1, and `fp` is `NULL`. + * + * - Inactive (after `delete_tempfile()`, `rename_tempfile()`, a + * failed attempt to create a temporary file, or a failed + * `close_tempfile()`). In this state: + * + * - `active` is unset + * - `filename` is empty (usually, though there are transitory + * states in which this condition doesn't hold). Client code should + * *not* rely on the filename being empty in this state. + * - `fd` is -1 and `fp` is `NULL` + * - the object is left registered in the `tempfile_list`, and + * `on_list` is set. + * + * A temporary file is owned by the process that created it. The + * `tempfile` has an `owner` field that records the owner's PID. This + * field is used to prevent a forked process from deleting a temporary + * file created by its parent. + */ + +#include "cache.h" +#include "tempfile.h" +#include "sigchain.h" + +static struct tempfile *volatile tempfile_list; + +static void remove_tempfiles(int skip_fclose) +{ + pid_t me = getpid(); + + while (tempfile_list) { + if (tempfile_list->owner == me) { + /* fclose() is not safe to call in a signal handler */ + if (skip_fclose) + tempfile_list->fp = NULL; + delete_tempfile(tempfile_list); + } + tempfile_list = tempfile_list->next; + } +} + +static void remove_tempfiles_on_exit(void) +{ + remove_tempfiles(0); +} + +static void remove_tempfiles_on_signal(int signo) +{ + remove_tempfiles(1); + sigchain_pop(signo); + raise(signo); +} + +/* + * Initialize *tempfile if necessary and add it to tempfile_list. + */ +static void prepare_tempfile_object(struct tempfile *tempfile) +{ + if (!tempfile_list) { + /* One-time initialization */ + sigchain_push_common(remove_tempfiles_on_signal); + atexit(remove_tempfiles_on_exit); + } + + if (tempfile->active) + die("BUG: prepare_tempfile_object called for active object"); + if (!tempfile->on_list) { + /* Initialize *tempfile and add it to tempfile_list: */ + tempfile->fd = -1; + tempfile->fp = NULL; + tempfile->active = 0; + tempfile->owner = 0; + strbuf_init(&tempfile->filename, 0); + tempfile->next = tempfile_list; + tempfile_list = tempfile; + tempfile->on_list = 1; + } else if (tempfile->filename.len) { + /* This shouldn't happen, but better safe than sorry. */ + die("BUG: prepare_tempfile_object called for improperly-reset object"); + } +} + +/* Make sure errno contains a meaningful value on error */ +int create_tempfile(struct tempfile *tempfile, const char *path) +{ + prepare_tempfile_object(tempfile); + + strbuf_add_absolute_path(&tempfile->filename, path); + tempfile->fd = open(tempfile->filename.buf, O_RDWR | O_CREAT | O_EXCL, 0666); + if (tempfile->fd < 0) { + strbuf_reset(&tempfile->filename); + return -1; + } + tempfile->owner = getpid(); + tempfile->active = 1; + if (adjust_shared_perm(tempfile->filename.buf)) { + int save_errno = errno; + error("cannot fix permission bits on %s", tempfile->filename.buf); + delete_tempfile(tempfile); + errno = save_errno; + return -1; + } + return tempfile->fd; +} + +void register_tempfile(struct tempfile *tempfile, const char *path) +{ + prepare_tempfile_object(tempfile); + strbuf_add_absolute_path(&tempfile->filename, path); + tempfile->owner = getpid(); + tempfile->active = 1; +} + +int mks_tempfile_sm(struct tempfile *tempfile, + const char *template, int suffixlen, int mode) +{ + prepare_tempfile_object(tempfile); + + strbuf_add_absolute_path(&tempfile->filename, template); + tempfile->fd = git_mkstemps_mode(tempfile->filename.buf, suffixlen, mode); + if (tempfile->fd < 0) { + strbuf_reset(&tempfile->filename); + return -1; + } + tempfile->owner = getpid(); + tempfile->active = 1; + return tempfile->fd; +} + +int mks_tempfile_tsm(struct tempfile *tempfile, + const char *template, int suffixlen, int mode) +{ + const char *tmpdir; + + prepare_tempfile_object(tempfile); + + tmpdir = getenv("TMPDIR"); + if (!tmpdir) + tmpdir = "/tmp"; + + strbuf_addf(&tempfile->filename, "%s/%s", tmpdir, template); + tempfile->fd = git_mkstemps_mode(tempfile->filename.buf, suffixlen, mode); + if (tempfile->fd < 0) { + strbuf_reset(&tempfile->filename); + return -1; + } + tempfile->owner = getpid(); + tempfile->active = 1; + return tempfile->fd; +} + +int xmks_tempfile_m(struct tempfile *tempfile, const char *template, int mode) +{ + int fd; + struct strbuf full_template = STRBUF_INIT; + + strbuf_add_absolute_path(&full_template, template); + fd = mks_tempfile_m(tempfile, full_template.buf, mode); + if (fd < 0) + die_errno("Unable to create temporary file '%s'", + full_template.buf); + + strbuf_release(&full_template); + return fd; +} + +FILE *fdopen_tempfile(struct tempfile *tempfile, const char *mode) +{ + if (!tempfile->active) + die("BUG: fdopen_tempfile() called for inactive object"); + if (tempfile->fp) + die("BUG: fdopen_tempfile() called for open object"); + + tempfile->fp = fdopen(tempfile->fd, mode); + return tempfile->fp; +} + +const char *get_tempfile_path(struct tempfile *tempfile) +{ + if (!tempfile->active) + die("BUG: get_tempfile_path() called for inactive object"); + return tempfile->filename.buf; +} + +int get_tempfile_fd(struct tempfile *tempfile) +{ + if (!tempfile->active) + die("BUG: get_tempfile_fd() called for inactive object"); + return tempfile->fd; +} + +FILE *get_tempfile_fp(struct tempfile *tempfile) +{ + if (!tempfile->active) + die("BUG: get_tempfile_fp() called for inactive object"); + return tempfile->fp; +} + +int close_tempfile(struct tempfile *tempfile) +{ + int fd = tempfile->fd; + FILE *fp = tempfile->fp; + int err; + + if (fd < 0) + return 0; + + tempfile->fd = -1; + if (fp) { + tempfile->fp = NULL; + + /* + * Note: no short-circuiting here; we want to fclose() + * in any case! + */ + err = ferror(fp) | fclose(fp); + } else { + err = close(fd); + } + + if (err) { + int save_errno = errno; + delete_tempfile(tempfile); + errno = save_errno; + return -1; + } + + return 0; +} + +int reopen_tempfile(struct tempfile *tempfile) +{ + if (0 <= tempfile->fd) + die("BUG: reopen_tempfile called for an open object"); + if (!tempfile->active) + die("BUG: reopen_tempfile called for an inactive object"); + tempfile->fd = open(tempfile->filename.buf, O_WRONLY); + return tempfile->fd; +} + +int rename_tempfile(struct tempfile *tempfile, const char *path) +{ + if (!tempfile->active) + die("BUG: rename_tempfile called for inactive object"); + + if (close_tempfile(tempfile)) + return -1; + + if (rename(tempfile->filename.buf, path)) { + int save_errno = errno; + delete_tempfile(tempfile); + errno = save_errno; + return -1; + } + + tempfile->active = 0; + strbuf_reset(&tempfile->filename); + return 0; +} + +void delete_tempfile(struct tempfile *tempfile) +{ + if (!tempfile->active) + return; + + if (!close_tempfile(tempfile)) { + unlink_or_warn(tempfile->filename.buf); + tempfile->active = 0; + strbuf_reset(&tempfile->filename); + } +} diff --git a/tempfile.h b/tempfile.h new file mode 100644 index 0000000000..4219fe41bd --- /dev/null +++ b/tempfile.h @@ -0,0 +1,271 @@ +#ifndef TEMPFILE_H +#define TEMPFILE_H + +/* + * Handle temporary files. + * + * The tempfile API allows temporary files to be created, deleted, and + * atomically renamed. Temporary files that are still active when the + * program ends are cleaned up automatically. Lockfiles (see + * "lockfile.h") are built on top of this API. + * + * + * Calling sequence + * ---------------- + * + * The caller: + * + * * Allocates a `struct tempfile` either as a static variable or on + * the heap, initialized to zeros. Once you use the structure to + * call `create_tempfile()`, it belongs to the tempfile subsystem + * and its storage must remain valid throughout the life of the + * program (i.e. you cannot use an on-stack variable to hold this + * structure). + * + * * Attempts to create a temporary file by calling + * `create_tempfile()`. + * + * * Writes new content to the file by either: + * + * * writing to the file descriptor returned by `create_tempfile()` + * (also available via `tempfile->fd`). + * + * * calling `fdopen_tempfile()` to get a `FILE` pointer for the + * open file and writing to the file using stdio. + * + * When finished writing, the caller can: + * + * * Close the file descriptor and remove the temporary file by + * calling `delete_tempfile()`. + * + * * Close the temporary file and rename it atomically to a specified + * filename by calling `rename_tempfile()`. This relinquishes + * control of the file. + * + * * Close the file descriptor without removing or renaming the + * temporary file by calling `close_tempfile()`, and later call + * `delete_tempfile()` or `rename_tempfile()`. + * + * Even after the temporary file is renamed or deleted, the `tempfile` + * object must not be freed or altered by the caller. However, it may + * be reused; just pass it to another call of `create_tempfile()`. + * + * If the program exits before `rename_tempfile()` or + * `delete_tempfile()` is called, an `atexit(3)` handler will close + * and remove the temporary file. + * + * If you need to close the file descriptor yourself, do so by calling + * `close_tempfile()`. You should never call `close(2)` or `fclose(3)` + * yourself, otherwise the `struct tempfile` structure would still + * think that the file descriptor needs to be closed, and a later + * cleanup would result in duplicate calls to `close(2)`. Worse yet, + * if you close and then later open another file descriptor for a + * completely different purpose, then the unrelated file descriptor + * might get closed. + * + * + * Error handling + * -------------- + * + * `create_tempfile()` returns a file descriptor on success or -1 on + * failure. On errors, `errno` describes the reason for failure. + * + * `delete_tempfile()`, `rename_tempfile()`, and `close_tempfile()` + * return 0 on success. On failure they set `errno` appropriately, do + * their best to delete the temporary file, and return -1. + */ + +struct tempfile { + struct tempfile *volatile next; + volatile sig_atomic_t active; + volatile int fd; + FILE *volatile fp; + volatile pid_t owner; + char on_list; + struct strbuf filename; +}; + +/* + * Attempt to create a temporary file at the specified `path`. Return + * a file descriptor for writing to it, or -1 on error. It is an error + * if a file already exists at that path. + */ +extern int create_tempfile(struct tempfile *tempfile, const char *path); + +/* + * Register an existing file as a tempfile, meaning that it will be + * deleted when the program exits. The tempfile is considered closed, + * but it can be worked with like any other closed tempfile (for + * example, it can be opened using reopen_tempfile()). + */ +extern void register_tempfile(struct tempfile *tempfile, const char *path); + + +/* + * mks_tempfile functions + * + * The following functions attempt to create and open temporary files + * with names derived automatically from a template, in the manner of + * mkstemps(), and arrange for them to be deleted if the program ends + * before they are deleted explicitly. There is a whole family of such + * functions, named according to the following pattern: + * + * x?mks_tempfile_t?s?m?() + * + * The optional letters have the following meanings: + * + * x - die if the temporary file cannot be created. + * + * t - create the temporary file under $TMPDIR (as opposed to + * relative to the current directory). When these variants are + * used, template should be the pattern for the filename alone, + * without a path. + * + * s - template includes a suffix that is suffixlen characters long. + * + * m - the temporary file should be created with the specified mode + * (otherwise, the mode is set to 0600). + * + * None of these functions modify template. If the caller wants to + * know the (absolute) path of the file that was created, it can be + * read from tempfile->filename. + * + * On success, the functions return a file descriptor that is open for + * writing the temporary file. On errors, they return -1 and set errno + * appropriately (except for the "x" variants, which die() on errors). + */ + +/* See "mks_tempfile functions" above. */ +extern int mks_tempfile_sm(struct tempfile *tempfile, + const char *template, int suffixlen, int mode); + +/* See "mks_tempfile functions" above. */ +static inline int mks_tempfile_s(struct tempfile *tempfile, + const char *template, int suffixlen) +{ + return mks_tempfile_sm(tempfile, template, suffixlen, 0600); +} + +/* See "mks_tempfile functions" above. */ +static inline int mks_tempfile_m(struct tempfile *tempfile, + const char *template, int mode) +{ + return mks_tempfile_sm(tempfile, template, 0, mode); +} + +/* See "mks_tempfile functions" above. */ +static inline int mks_tempfile(struct tempfile *tempfile, + const char *template) +{ + return mks_tempfile_sm(tempfile, template, 0, 0600); +} + +/* See "mks_tempfile functions" above. */ +extern int mks_tempfile_tsm(struct tempfile *tempfile, + const char *template, int suffixlen, int mode); + +/* See "mks_tempfile functions" above. */ +static inline int mks_tempfile_ts(struct tempfile *tempfile, + const char *template, int suffixlen) +{ + return mks_tempfile_tsm(tempfile, template, suffixlen, 0600); +} + +/* See "mks_tempfile functions" above. */ +static inline int mks_tempfile_tm(struct tempfile *tempfile, + const char *template, int mode) +{ + return mks_tempfile_tsm(tempfile, template, 0, mode); +} + +/* See "mks_tempfile functions" above. */ +static inline int mks_tempfile_t(struct tempfile *tempfile, + const char *template) +{ + return mks_tempfile_tsm(tempfile, template, 0, 0600); +} + +/* See "mks_tempfile functions" above. */ +extern int xmks_tempfile_m(struct tempfile *tempfile, + const char *template, int mode); + +/* See "mks_tempfile functions" above. */ +static inline int xmks_tempfile(struct tempfile *tempfile, + const char *template) +{ + return xmks_tempfile_m(tempfile, template, 0600); +} + +/* + * Associate a stdio stream with the temporary file (which must still + * be open). Return `NULL` (*without* deleting the file) on error. The + * stream is closed automatically when `close_tempfile()` is called or + * when the file is deleted or renamed. + */ +extern FILE *fdopen_tempfile(struct tempfile *tempfile, const char *mode); + +static inline int is_tempfile_active(struct tempfile *tempfile) +{ + return tempfile->active; +} + +/* + * Return the path of the lockfile. The return value is a pointer to a + * field within the lock_file object and should not be freed. + */ +extern const char *get_tempfile_path(struct tempfile *tempfile); + +extern int get_tempfile_fd(struct tempfile *tempfile); +extern FILE *get_tempfile_fp(struct tempfile *tempfile); + +/* + * If the temporary file is still open, close it (and the file pointer + * too, if it has been opened using `fdopen_tempfile()`) without + * deleting the file. Return 0 upon success. On failure to `close(2)`, + * return a negative value and delete the file. Usually + * `delete_tempfile()` or `rename_tempfile()` should eventually be + * called if `close_tempfile()` succeeds. + */ +extern int close_tempfile(struct tempfile *tempfile); + +/* + * Re-open a temporary file that has been closed using + * `close_tempfile()` but not yet deleted or renamed. This can be used + * to implement a sequence of operations like the following: + * + * * Create temporary file. + * + * * Write new contents to file, then `close_tempfile()` to cause the + * contents to be written to disk. + * + * * Pass the name of the temporary file to another program to allow + * it (and nobody else) to inspect or even modify the file's + * contents. + * + * * `reopen_tempfile()` to reopen the temporary file. Make further + * updates to the contents. + * + * * `rename_tempfile()` to move the file to its permanent location. + */ +extern int reopen_tempfile(struct tempfile *tempfile); + +/* + * Close the file descriptor and/or file pointer and remove the + * temporary file associated with `tempfile`. It is a NOOP to call + * `delete_tempfile()` for a `tempfile` object that has already been + * deleted or renamed. + */ +extern void delete_tempfile(struct tempfile *tempfile); + +/* + * Close the file descriptor and/or file pointer if they are still + * open, and atomically rename the temporary file to `path`. `path` + * must be on the same filesystem as the lock file. Return 0 on + * success. On failure, delete the temporary file and return -1, with + * `errno` set to the value from the failing call to `close(2)` or + * `rename(2)`. It is a bug to call `rename_tempfile()` for a + * `tempfile` object that is not currently active. + */ +extern int rename_tempfile(struct tempfile *tempfile, const char *path); + +#endif /* TEMPFILE_H */ diff --git a/test-submodule-config.c b/test-submodule-config.c new file mode 100644 index 0000000000..dab8c27768 --- /dev/null +++ b/test-submodule-config.c @@ -0,0 +1,76 @@ +#include "cache.h" +#include "submodule-config.h" +#include "submodule.h" + +static void die_usage(int argc, char **argv, const char *msg) +{ + fprintf(stderr, "%s\n", msg); + fprintf(stderr, "Usage: %s [<commit> <submodulepath>] ...\n", argv[0]); + exit(1); +} + +static int git_test_config(const char *var, const char *value, void *cb) +{ + return parse_submodule_config_option(var, value); +} + +int main(int argc, char **argv) +{ + char **arg = argv; + int my_argc = argc; + int output_url = 0; + int lookup_name = 0; + + arg++; + my_argc--; + while (starts_with(arg[0], "--")) { + if (!strcmp(arg[0], "--url")) + output_url = 1; + if (!strcmp(arg[0], "--name")) + lookup_name = 1; + arg++; + my_argc--; + } + + if (my_argc % 2 != 0) + die_usage(argc, argv, "Wrong number of arguments."); + + setup_git_directory(); + gitmodules_config(); + git_config(git_test_config, NULL); + + while (*arg) { + unsigned char commit_sha1[20]; + const struct submodule *submodule; + const char *commit; + const char *path_or_name; + + commit = arg[0]; + path_or_name = arg[1]; + + if (commit[0] == '\0') + hashcpy(commit_sha1, null_sha1); + else if (get_sha1(commit, commit_sha1) < 0) + die_usage(argc, argv, "Commit not found."); + + if (lookup_name) { + submodule = submodule_from_name(commit_sha1, path_or_name); + } else + submodule = submodule_from_path(commit_sha1, path_or_name); + if (!submodule) + die_usage(argc, argv, "Submodule not found."); + + if (output_url) + printf("Submodule url: '%s' for path '%s'\n", + submodule->url, submodule->path); + else + printf("Submodule name: '%s' for path '%s'\n", + submodule->name, submodule->path); + + arg += 2; + } + + submodule_free(); + + return 0; +} @@ -740,8 +740,10 @@ static int find_trailer_start(struct strbuf **lines, int count) /* * Get the start of the trailers by looking starting from the end * for a line with only spaces before lines with one separator. + * The first line must not be analyzed as the others as it + * should be either the message title or a blank line. */ - for (start = count - 1; start >= 0; start--) { + for (start = count - 1; start >= 1; start--) { if (lines[start]->buf[0] == comment_line_char) continue; if (contains_only_spaces(lines[start]->buf)) { diff --git a/transport-helper.c b/transport-helper.c index 5d99a6bc2e..68e498eebd 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -490,7 +490,8 @@ static int fetch_with_import(struct transport *transport, else private = xstrdup(name); if (private) { - read_ref(private, posn->old_sha1); + if (read_ref(private, posn->old_sha1) < 0) + die("Could not read ref %s", private); free(private); } } @@ -1019,7 +1020,10 @@ static struct ref *get_refs_list(struct transport *transport, int for_push) if (eon) { if (has_attribute(eon + 1, "unchanged")) { (*tail)->status |= REF_STATUS_UPTODATE; - read_ref((*tail)->name, (*tail)->old_sha1); + if (read_ref((*tail)->name, + (*tail)->old_sha1) < 0) + die(N_("Could not read ref %s"), + (*tail)->name); } } tail = &((*tail)->next); diff --git a/unpack-trees.c b/unpack-trees.c index 2927660d92..bb09b1ed27 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -224,6 +224,9 @@ static int check_updates(struct unpack_trees_options *o) struct cache_entry *ce = index->cache[i]; if (ce->ce_flags & CE_UPDATE) { + if (ce->ce_flags & CE_WT_REMOVE) + die("BUG: both update and delete flags are set on %s", + ce->name); display_progress(progress, ++cnt); ce->ce_flags &= ~CE_UPDATE; if (o->update && !o->dry_run) { @@ -293,6 +296,7 @@ static int apply_sparse_checkout(struct index_state *istate, if (!(ce->ce_flags & CE_UPDATE) && verify_uptodate_sparse(ce, o)) return -1; ce->ce_flags |= CE_WT_REMOVE; + ce->ce_flags &= ~CE_UPDATE; } if (was_skip_worktree && !ce_skip_worktree(ce)) { if (verify_absent_sparse(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o)) @@ -1025,10 +1029,12 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options if (!core_apply_sparse_checkout || !o->update) o->skip_sparse_checkout = 1; if (!o->skip_sparse_checkout) { - if (add_excludes_from_file_to_list(git_path("info/sparse-checkout"), "", 0, &el, 0) < 0) + char *sparse = git_pathdup("info/sparse-checkout"); + if (add_excludes_from_file_to_list(sparse, "", 0, &el, 0) < 0) o->skip_sparse_checkout = 1; else o->el = ⪙ + free(sparse); } memset(&o->result, 0, sizeof(o->result)); @@ -1156,6 +1162,14 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options o->src_index = NULL; ret = check_updates(o) ? (-2) : 0; if (o->dst_index) { + if (!ret) { + if (!o->result.cache_tree) + o->result.cache_tree = cache_tree(); + if (!cache_tree_fully_valid(o->result.cache_tree)) + cache_tree_update(&o->result, + WRITE_TREE_SILENT | + WRITE_TREE_REPAIR); + } discard_index(o->dst_index); *o->dst_index = o->result; } else { @@ -6,23 +6,22 @@ #include "git-compat-util.h" #include "cache.h" +static FILE *error_handle; +static int tweaked_error_buffering; + void vreportf(const char *prefix, const char *err, va_list params) { - char msg[4096]; - vsnprintf(msg, sizeof(msg), err, params); - fprintf(stderr, "%s%s\n", prefix, msg); -} + FILE *fh = error_handle ? error_handle : stderr; -void vwritef(int fd, const char *prefix, const char *err, va_list params) -{ - char msg[4096]; - int len = vsnprintf(msg, sizeof(msg), err, params); - if (len > sizeof(msg)) - len = sizeof(msg); + fflush(fh); + if (!tweaked_error_buffering) { + setvbuf(fh, NULL, _IOLBF, 0); + tweaked_error_buffering = 1; + } - write_in_full(fd, prefix, strlen(prefix)); - write_in_full(fd, msg, len); - write_in_full(fd, "\n", 1); + fputs(prefix, fh); + vfprintf(fh, err, params); + fputc('\n', fh); } static NORETURN void usage_builtin(const char *err, va_list params) @@ -76,6 +75,12 @@ void set_die_is_recursing_routine(int (*routine)(void)) die_is_recursing = routine; } +void set_error_handle(FILE *fh) +{ + error_handle = fh; + tweaked_error_buffering = 0; +} + void NORETURN usagef(const char *err, ...) { va_list params; diff --git a/userdiff.c b/userdiff.c index 2ccbee50cb..6bf2505994 100644 --- a/userdiff.c +++ b/userdiff.c @@ -35,6 +35,8 @@ IPATTERN("fortran", * they would have been matched above as a variable anyway. */ "|[-+]?[0-9.]+([AaIiDdEeFfLlTtXx][Ss]?[-+]?[0-9.]*)?(_[a-zA-Z0-9][a-zA-Z0-9_]*)?" "|//|\\*\\*|::|[/<>=]="), +IPATTERN("fountain", "^((\\.[^.]|(int|ext|est|int\\.?/ext|i/e)[. ]).*)$", + "[^ \t-]+"), PATTERNS("html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$", "[^<>= \t]+"), PATTERNS("java", diff --git a/wt-status.c b/wt-status.c index 04e01e4238..c327fe8128 100644 --- a/wt-status.c +++ b/wt-status.c @@ -1,5 +1,4 @@ #include "cache.h" -#include "pathspec.h" #include "wt-status.h" #include "object.h" #include "dir.h" @@ -1171,7 +1170,7 @@ static void show_rebase_in_progress(struct wt_status *s, status_printf_ln(s, color, _(" (use \"git rebase --abort\" to check out the original branch)")); } - } else if (state->rebase_in_progress || !stat(git_path("MERGE_MSG"), &st)) { + } else if (state->rebase_in_progress || !stat(git_path_merge_msg(), &st)) { print_rebase_state(s, state, color); if (s->hints) status_printf_ln(s, color, @@ -1368,7 +1367,7 @@ void wt_status_get_state(struct wt_status_state *state, struct stat st; unsigned char sha1[20]; - if (!stat(git_path("MERGE_HEAD"), &st)) { + if (!stat(git_path_merge_head(), &st)) { state->merge_in_progress = 1; } else if (!stat(git_path("rebase-apply"), &st)) { if (!stat(git_path("rebase-apply/applying"), &st)) { @@ -1387,7 +1386,7 @@ void wt_status_get_state(struct wt_status_state *state, state->rebase_in_progress = 1; state->branch = read_and_strip_branch("rebase-merge/head-name"); state->onto = read_and_strip_branch("rebase-merge/onto"); - } else if (!stat(git_path("CHERRY_PICK_HEAD"), &st) && + } else if (!stat(git_path_cherry_pick_head(), &st) && !get_sha1("CHERRY_PICK_HEAD", sha1)) { state->cherry_pick_in_progress = 1; hashcpy(state->cherry_pick_head_sha1, sha1); @@ -1396,7 +1395,7 @@ void wt_status_get_state(struct wt_status_state *state, state->bisect_in_progress = 1; state->branch = read_and_strip_branch("BISECT_START"); } - if (!stat(git_path("REVERT_HEAD"), &st) && + if (!stat(git_path_revert_head(), &st) && !get_sha1("REVERT_HEAD", sha1)) { state->revert_in_progress = 1; hashcpy(state->revert_head_sha1, sha1); diff --git a/wt-status.h b/wt-status.h index e0a99f75c7..c9b3b744e9 100644 --- a/wt-status.h +++ b/wt-status.h @@ -4,6 +4,7 @@ #include <stdio.h> #include "string-list.h" #include "color.h" +#include "pathspec.h" enum color_wt_status { WT_STATUS_HEADER = 0, |