Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.kernel.org/pub/scm/git/git.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/RelNotes/2.6.0.txt36
-rw-r--r--Documentation/config.txt73
-rw-r--r--Documentation/git-am.txt7
-rw-r--r--Documentation/git-bisect.txt133
-rw-r--r--Documentation/git-worktree.txt48
-rw-r--r--Documentation/gitrepository-layout.txt22
-rw-r--r--Documentation/technical/index-format.txt2
-rw-r--r--Makefile2
-rw-r--r--bisect.c83
-rw-r--r--bisect.h2
-rw-r--r--branch.c67
-rw-r--r--branch.h7
-rw-r--r--builtin.h1
-rw-r--r--builtin/add.c2
-rw-r--r--builtin/am.c2321
-rw-r--r--builtin/checkout.c82
-rw-r--r--builtin/clone.c65
-rw-r--r--builtin/fetch.c14
-rw-r--r--builtin/gc.c2
-rw-r--r--builtin/index-pack.c4
-rw-r--r--builtin/pull.c6
-rw-r--r--builtin/worktree.c123
-rw-r--r--cache-tree.c29
-rw-r--r--cache-tree.h1
-rw-r--r--cache.h2
-rwxr-xr-xcontrib/examples/git-am.sh (renamed from git-am.sh)0
-rwxr-xr-xcontrib/examples/git-pull.sh2
-rw-r--r--dir.c17
-rw-r--r--fast-import.c4
-rwxr-xr-xgit-bisect.sh144
-rw-r--r--git-compat-util.h2
-rw-r--r--git.c1
-rw-r--r--http.c10
-rw-r--r--parse-options.h2
-rw-r--r--refs.c18
-rw-r--r--revision.c18
-rw-r--r--sha1_file.c7
-rwxr-xr-xt/t0090-cache-tree.sh24
-rwxr-xr-xt/t1400-update-ref.sh5
-rwxr-xr-xt/t1410-reflog.sh24
-rwxr-xr-xt/t1411-reflog-show.sh2
-rwxr-xr-xt/t1503-rev-parse-verify.sh9
-rwxr-xr-xt/t1509-root-worktree.sh8
-rwxr-xr-xt/t2025-worktree-add.sh34
-rwxr-xr-xt/t3200-branch.sh12
-rwxr-xr-xt/t3210-pack-refs.sh2
-rwxr-xr-xt/t3404-rebase-interactive.sh10
-rwxr-xr-xt/t3903-stash.sh2
-rwxr-xr-xt/t4150-am.sh19
-rwxr-xr-xt/t5312-prune-corruption.sh2
-rwxr-xr-xt/t5512-ls-remote.sh23
-rwxr-xr-xt/t5520-pull.sh11
-rwxr-xr-xt/t5603-clone-dirname.sh106
-rwxr-xr-xt/t6030-bisect-porcelain.sh2
-rwxr-xr-xt/t6501-freshen-objects.sh2
-rwxr-xr-xt/t7063-status-untracked-cache.sh119
-rwxr-xr-xt/t7509-commit.sh13
-rw-r--r--t/test-lib.sh13
-rw-r--r--transport-helper.c8
-rw-r--r--unpack-trees.c8
-rw-r--r--wrapper.c56
61 files changed, 3449 insertions, 424 deletions
diff --git a/Documentation/RelNotes/2.6.0.txt b/Documentation/RelNotes/2.6.0.txt
index b62c7dde34..4a0a589613 100644
--- a/Documentation/RelNotes/2.6.0.txt
+++ b/Documentation/RelNotes/2.6.0.txt
@@ -45,6 +45,12 @@ UI, Workflows & Features
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.
+
Performance, Internal Implementation, Development Support etc.
@@ -82,6 +88,19 @@ Performance, Internal Implementation, Development Support etc.
* 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".
+
Also contains various documentation updates and code clean-ups.
@@ -148,7 +167,24 @@ notes for details).
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).
+
* 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).
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 315f2710af..75ec02e8e9 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -769,6 +769,14 @@ am.keepcr::
by giving '--no-keep-cr' from the command line.
See linkgit:git-am[1], linkgit:git-mailsplit[1].
+am.threeWay::
+ By default, `git am` will fail if the patch does not apply cleanly. When
+ set to true, this setting tells `git am` to fall back on 3-way merge if
+ the patch records the identity of blobs it is supposed to apply to and
+ we have those blobs available locally (equivalent to giving the `--3way`
+ option from the command line). Defaults to `false`.
+ See linkgit:git-am[1].
+
apply.ignoreWhitespace::
When set to 'change', tells 'git apply' to ignore changes in
whitespace, in the same way as the '--ignore-space-change'
@@ -1299,20 +1307,24 @@ 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>.
@@ -1320,7 +1332,9 @@ gc.reflogExpireUnreachable::
gc.<ref>.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>.
@@ -2299,13 +2313,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
@@ -2593,9 +2604,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
@@ -2610,13 +2630,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-am.txt b/Documentation/git-am.txt
index 0d8ba48f79..dbea6e7ae9 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -10,7 +10,7 @@ SYNOPSIS
--------
[verse]
'git am' [--signoff] [--keep] [--[no-]keep-cr] [--[no-]utf8]
- [--3way] [--interactive] [--committer-date-is-author-date]
+ [--[no-]3way] [--interactive] [--committer-date-is-author-date]
[--ignore-date] [--ignore-space-change | --ignore-whitespace]
[--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>]
[--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
@@ -90,10 +90,13 @@ default. You can use `--no-utf8` to override this.
-3::
--3way::
+--no-3way::
When the patch does not apply cleanly, fall back on
3-way merge if the patch records the identity of blobs
it is supposed to apply to and we have those blobs
- available locally.
+ available locally. `--no-3way` can be used to override
+ am.threeWay configuration variable. For more information,
+ see am.threeWay in linkgit:git-config[1].
--ignore-space-change::
--ignore-whitespace::
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-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/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/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/Makefile b/Makefile
index 7efedbe834..e39ca6ca64 100644
--- a/Makefile
+++ b/Makefile
@@ -467,7 +467,6 @@ TEST_PROGRAMS_NEED_X =
# interactive shell sessions without exporting it.
unexport CDPATH
-SCRIPT_SH += git-am.sh
SCRIPT_SH += git-bisect.sh
SCRIPT_SH += git-difftool--helper.sh
SCRIPT_SH += git-filter-branch.sh
@@ -813,6 +812,7 @@ LIB_OBJS += xdiff-interface.o
LIB_OBJS += zlib.o
BUILTIN_OBJS += builtin/add.o
+BUILTIN_OBJS += builtin/am.o
BUILTIN_OBJS += builtin/annotate.o
BUILTIN_OBJS += builtin/apply.o
BUILTIN_OBJS += builtin/archive.o
diff --git a/bisect.c b/bisect.c
index 7a6498c8e9..f9da9d1346 100644
--- a/bisect.c
+++ b/bisect.c
@@ -21,6 +21,9 @@ 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 +406,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;
}
@@ -637,7 +646,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));
@@ -735,18 +744,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);
}
@@ -758,10 +773,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);
}
@@ -842,7 +857,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))
@@ -893,6 +908,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.
@@ -908,6 +953,7 @@ int bisect_next_all(const char *prefix, int no_checkout)
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");
@@ -929,8 +975,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);
}
@@ -945,7 +993,8 @@ int bisect_next_all(const char *prefix, int no_checkout)
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", bisect_rev_hex,
+ term_bad);
show_diff_tree(prefix, revs.commits->item);
/* This means the bisection process succeeded. */
exit(10);
diff --git a/bisect.h b/bisect.h
index 2a6c831f3e..acd12ef802 100644
--- a/bisect.h
+++ b/bisect.h
@@ -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
diff --git a/branch.c b/branch.c
index e283683d9d..3364adf182 100644
--- a/branch.c
+++ b/branch.c
@@ -310,3 +310,70 @@ void remove_branch_state(void)
unlink(git_path_merge_mode());
unlink(git_path_squash_msg());
}
+
+static void check_linked_checkout(const char *branch, const char *id)
+{
+ struct strbuf sb = STRBUF_INIT;
+ struct strbuf path = STRBUF_INIT;
+ struct strbuf gitdir = STRBUF_INIT;
+
+ /*
+ * $GIT_COMMON_DIR/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/HEAD", get_git_common_dir(), id);
+ else
+ strbuf_addf(&path, "%s/HEAD", get_git_common_dir());
+
+ 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());
+ skip_prefix(branch, "refs/heads/", &branch);
+ strbuf_strip_suffix(&gitdir, ".git");
+ die(_("'%s' is already checked out at '%s'"), branch, gitdir.buf);
+done:
+ strbuf_release(&path);
+ strbuf_release(&sb);
+ strbuf_release(&gitdir);
+}
+
+void die_if_checked_out(const char *branch)
+{
+ struct strbuf path = STRBUF_INIT;
+ DIR *dir;
+ struct dirent *d;
+
+ check_linked_checkout(branch, NULL);
+
+ strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
+ dir = opendir(path.buf);
+ strbuf_release(&path);
+ if (!dir)
+ return;
+
+ while ((d = readdir(dir)) != NULL) {
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+ check_linked_checkout(branch, d->d_name);
+ }
+ closedir(dir);
+}
diff --git a/branch.h b/branch.h
index 64173abf4d..58aa45fe72 100644
--- a/branch.h
+++ b/branch.h
@@ -52,4 +52,11 @@ 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);
+
#endif
diff --git a/builtin.h b/builtin.h
index 839483de6e..79aaf0afe8 100644
--- a/builtin.h
+++ b/builtin.h
@@ -30,6 +30,7 @@ extern int textconv_object(const char *path, unsigned mode, const unsigned char
extern int is_builtin(const char *s);
extern int cmd_add(int argc, const char **argv, const char *prefix);
+extern int cmd_am(int argc, const char **argv, const char *prefix);
extern int cmd_annotate(int argc, const char **argv, const char *prefix);
extern int cmd_apply(int argc, const char **argv, const char *prefix);
extern int cmd_archive(int argc, const char **argv, const char *prefix);
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
new file mode 100644
index 0000000000..1399c8dd88
--- /dev/null
+++ b/builtin/am.c
@@ -0,0 +1,2321 @@
+/*
+ * Builtin "git am"
+ *
+ * Based on git-am.sh by Junio C Hamano.
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "exec_cmd.h"
+#include "parse-options.h"
+#include "dir.h"
+#include "run-command.h"
+#include "quote.h"
+#include "lockfile.h"
+#include "cache-tree.h"
+#include "refs.h"
+#include "commit.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "unpack-trees.h"
+#include "branch.h"
+#include "sequencer.h"
+#include "revision.h"
+#include "merge-recursive.h"
+#include "revision.h"
+#include "log-tree.h"
+#include "notes-utils.h"
+#include "rerere.h"
+#include "prompt.h"
+
+/**
+ * Returns 1 if the file is empty or does not exist, 0 otherwise.
+ */
+static int is_empty_file(const char *filename)
+{
+ struct stat st;
+
+ if (stat(filename, &st) < 0) {
+ if (errno == ENOENT)
+ return 1;
+ die_errno(_("could not stat %s"), filename);
+ }
+
+ return !st.st_size;
+}
+
+/**
+ * Like strbuf_getline(), but treats both '\n' and "\r\n" as line terminators.
+ */
+static int strbuf_getline_crlf(struct strbuf *sb, FILE *fp)
+{
+ if (strbuf_getwholeline(sb, fp, '\n'))
+ return EOF;
+ if (sb->buf[sb->len - 1] == '\n') {
+ strbuf_setlen(sb, sb->len - 1);
+ if (sb->len > 0 && sb->buf[sb->len - 1] == '\r')
+ strbuf_setlen(sb, sb->len - 1);
+ }
+ return 0;
+}
+
+/**
+ * Returns the length of the first line of msg.
+ */
+static int linelen(const char *msg)
+{
+ return strchrnul(msg, '\n') - msg;
+}
+
+/**
+ * Returns true if `str` consists of only whitespace, false otherwise.
+ */
+static int str_isspace(const char *str)
+{
+ for (; *str; str++)
+ if (!isspace(*str))
+ return 0;
+
+ return 1;
+}
+
+enum patch_format {
+ PATCH_FORMAT_UNKNOWN = 0,
+ PATCH_FORMAT_MBOX,
+ PATCH_FORMAT_STGIT,
+ PATCH_FORMAT_STGIT_SERIES,
+ PATCH_FORMAT_HG
+};
+
+enum keep_type {
+ KEEP_FALSE = 0,
+ KEEP_TRUE, /* pass -k flag to git-mailinfo */
+ KEEP_NON_PATCH /* pass -b flag to git-mailinfo */
+};
+
+enum scissors_type {
+ SCISSORS_UNSET = -1,
+ SCISSORS_FALSE = 0, /* pass --no-scissors to git-mailinfo */
+ SCISSORS_TRUE /* pass --scissors to git-mailinfo */
+};
+
+struct am_state {
+ /* state directory path */
+ char *dir;
+
+ /* current and last patch numbers, 1-indexed */
+ int cur;
+ int last;
+
+ /* commit metadata and message */
+ char *author_name;
+ char *author_email;
+ char *author_date;
+ char *msg;
+ size_t msg_len;
+
+ /* when --rebasing, records the original commit the patch came from */
+ unsigned char orig_commit[GIT_SHA1_RAWSZ];
+
+ /* number of digits in patch filename */
+ int prec;
+
+ /* various operating modes and command line options */
+ int interactive;
+ int threeway;
+ int quiet;
+ int signoff;
+ int utf8;
+ int keep; /* enum keep_type */
+ int message_id;
+ int scissors; /* enum scissors_type */
+ struct argv_array git_apply_opts;
+ const char *resolvemsg;
+ int committer_date_is_author_date;
+ int ignore_date;
+ int allow_rerere_autoupdate;
+ const char *sign_commit;
+ int rebasing;
+};
+
+/**
+ * Initializes am_state with the default values. The state directory is set to
+ * dir.
+ */
+static void am_state_init(struct am_state *state, const char *dir)
+{
+ int gpgsign;
+
+ memset(state, 0, sizeof(*state));
+
+ assert(dir);
+ state->dir = xstrdup(dir);
+
+ state->prec = 4;
+
+ git_config_get_bool("am.threeway", &state->threeway);
+
+ state->utf8 = 1;
+
+ git_config_get_bool("am.messageid", &state->message_id);
+
+ state->scissors = SCISSORS_UNSET;
+
+ argv_array_init(&state->git_apply_opts);
+
+ if (!git_config_get_bool("commit.gpgsign", &gpgsign))
+ state->sign_commit = gpgsign ? "" : NULL;
+}
+
+/**
+ * Releases memory allocated by an am_state.
+ */
+static void am_state_release(struct am_state *state)
+{
+ free(state->dir);
+ free(state->author_name);
+ free(state->author_email);
+ free(state->author_date);
+ free(state->msg);
+ argv_array_clear(&state->git_apply_opts);
+}
+
+/**
+ * Returns path relative to the am_state directory.
+ */
+static inline const char *am_path(const struct am_state *state, const char *path)
+{
+ return mkpath("%s/%s", state->dir, path);
+}
+
+/**
+ * If state->quiet is false, calls fprintf(fp, fmt, ...), and appends a newline
+ * at the end.
+ */
+static void say(const struct am_state *state, FILE *fp, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (!state->quiet) {
+ vfprintf(fp, fmt, ap);
+ putc('\n', fp);
+ }
+ va_end(ap);
+}
+
+/**
+ * Returns 1 if there is an am session in progress, 0 otherwise.
+ */
+static int am_in_progress(const struct am_state *state)
+{
+ struct stat st;
+
+ if (lstat(state->dir, &st) < 0 || !S_ISDIR(st.st_mode))
+ return 0;
+ if (lstat(am_path(state, "last"), &st) || !S_ISREG(st.st_mode))
+ return 0;
+ if (lstat(am_path(state, "next"), &st) || !S_ISREG(st.st_mode))
+ return 0;
+ return 1;
+}
+
+/**
+ * Reads the contents of `file` in the `state` directory into `sb`. Returns the
+ * number of bytes read on success, -1 if the file does not exist. If `trim` is
+ * set, trailing whitespace will be removed.
+ */
+static int read_state_file(struct strbuf *sb, const struct am_state *state,
+ const char *file, int trim)
+{
+ strbuf_reset(sb);
+
+ if (strbuf_read_file(sb, am_path(state, file), 0) >= 0) {
+ if (trim)
+ strbuf_trim(sb);
+
+ return sb->len;
+ }
+
+ if (errno == ENOENT)
+ return -1;
+
+ die_errno(_("could not read '%s'"), am_path(state, file));
+}
+
+/**
+ * Reads a KEY=VALUE shell variable assignment from `fp`, returning the VALUE
+ * as a newly-allocated string. VALUE must be a quoted string, and the KEY must
+ * match `key`. Returns NULL on failure.
+ *
+ * This is used by read_author_script() to read the GIT_AUTHOR_* variables from
+ * the author-script.
+ */
+static char *read_shell_var(FILE *fp, const char *key)
+{
+ struct strbuf sb = STRBUF_INIT;
+ const char *str;
+
+ if (strbuf_getline(&sb, fp, '\n'))
+ goto fail;
+
+ if (!skip_prefix(sb.buf, key, &str))
+ goto fail;
+
+ if (!skip_prefix(str, "=", &str))
+ goto fail;
+
+ strbuf_remove(&sb, 0, str - sb.buf);
+
+ str = sq_dequote(sb.buf);
+ if (!str)
+ goto fail;
+
+ return strbuf_detach(&sb, NULL);
+
+fail:
+ strbuf_release(&sb);
+ return NULL;
+}
+
+/**
+ * Reads and parses the state directory's "author-script" file, and sets
+ * state->author_name, state->author_email and state->author_date accordingly.
+ * Returns 0 on success, -1 if the file could not be parsed.
+ *
+ * The author script is of the format:
+ *
+ * GIT_AUTHOR_NAME='$author_name'
+ * GIT_AUTHOR_EMAIL='$author_email'
+ * GIT_AUTHOR_DATE='$author_date'
+ *
+ * where $author_name, $author_email and $author_date are quoted. We are strict
+ * with our parsing, as the file was meant to be eval'd in the old git-am.sh
+ * script, and thus if the file differs from what this function expects, it is
+ * better to bail out than to do something that the user does not expect.
+ */
+static int read_author_script(struct am_state *state)
+{
+ const char *filename = am_path(state, "author-script");
+ FILE *fp;
+
+ assert(!state->author_name);
+ assert(!state->author_email);
+ assert(!state->author_date);
+
+ fp = fopen(filename, "r");
+ if (!fp) {
+ if (errno == ENOENT)
+ return 0;
+ die_errno(_("could not open '%s' for reading"), filename);
+ }
+
+ state->author_name = read_shell_var(fp, "GIT_AUTHOR_NAME");
+ if (!state->author_name) {
+ fclose(fp);
+ return -1;
+ }
+
+ state->author_email = read_shell_var(fp, "GIT_AUTHOR_EMAIL");
+ if (!state->author_email) {
+ fclose(fp);
+ return -1;
+ }
+
+ state->author_date = read_shell_var(fp, "GIT_AUTHOR_DATE");
+ if (!state->author_date) {
+ fclose(fp);
+ return -1;
+ }
+
+ if (fgetc(fp) != EOF) {
+ fclose(fp);
+ return -1;
+ }
+
+ fclose(fp);
+ return 0;
+}
+
+/**
+ * Saves state->author_name, state->author_email and state->author_date in the
+ * state directory's "author-script" file.
+ */
+static void write_author_script(const struct am_state *state)
+{
+ struct strbuf sb = STRBUF_INIT;
+
+ strbuf_addstr(&sb, "GIT_AUTHOR_NAME=");
+ sq_quote_buf(&sb, state->author_name);
+ strbuf_addch(&sb, '\n');
+
+ strbuf_addstr(&sb, "GIT_AUTHOR_EMAIL=");
+ sq_quote_buf(&sb, state->author_email);
+ strbuf_addch(&sb, '\n');
+
+ strbuf_addstr(&sb, "GIT_AUTHOR_DATE=");
+ sq_quote_buf(&sb, state->author_date);
+ strbuf_addch(&sb, '\n');
+
+ write_file(am_path(state, "author-script"), 1, "%s", sb.buf);
+
+ strbuf_release(&sb);
+}
+
+/**
+ * Reads the commit message from the state directory's "final-commit" file,
+ * setting state->msg to its contents and state->msg_len to the length of its
+ * contents in bytes.
+ *
+ * Returns 0 on success, -1 if the file does not exist.
+ */
+static int read_commit_msg(struct am_state *state)
+{
+ struct strbuf sb = STRBUF_INIT;
+
+ assert(!state->msg);
+
+ if (read_state_file(&sb, state, "final-commit", 0) < 0) {
+ strbuf_release(&sb);
+ return -1;
+ }
+
+ state->msg = strbuf_detach(&sb, &state->msg_len);
+ return 0;
+}
+
+/**
+ * Saves state->msg in the state directory's "final-commit" file.
+ */
+static void write_commit_msg(const struct am_state *state)
+{
+ int fd;
+ const char *filename = am_path(state, "final-commit");
+
+ fd = xopen(filename, O_WRONLY | O_CREAT, 0666);
+ if (write_in_full(fd, state->msg, state->msg_len) < 0)
+ die_errno(_("could not write to %s"), filename);
+ close(fd);
+}
+
+/**
+ * Loads state from disk.
+ */
+static void am_load(struct am_state *state)
+{
+ struct strbuf sb = STRBUF_INIT;
+
+ if (read_state_file(&sb, state, "next", 1) < 0)
+ die("BUG: state file 'next' does not exist");
+ state->cur = strtol(sb.buf, NULL, 10);
+
+ if (read_state_file(&sb, state, "last", 1) < 0)
+ die("BUG: state file 'last' does not exist");
+ state->last = strtol(sb.buf, NULL, 10);
+
+ if (read_author_script(state) < 0)
+ die(_("could not parse author script"));
+
+ read_commit_msg(state);
+
+ if (read_state_file(&sb, state, "original-commit", 1) < 0)
+ hashclr(state->orig_commit);
+ else if (get_sha1_hex(sb.buf, state->orig_commit) < 0)
+ die(_("could not parse %s"), am_path(state, "original-commit"));
+
+ read_state_file(&sb, state, "threeway", 1);
+ state->threeway = !strcmp(sb.buf, "t");
+
+ read_state_file(&sb, state, "quiet", 1);
+ state->quiet = !strcmp(sb.buf, "t");
+
+ read_state_file(&sb, state, "sign", 1);
+ state->signoff = !strcmp(sb.buf, "t");
+
+ read_state_file(&sb, state, "utf8", 1);
+ state->utf8 = !strcmp(sb.buf, "t");
+
+ read_state_file(&sb, state, "keep", 1);
+ if (!strcmp(sb.buf, "t"))
+ state->keep = KEEP_TRUE;
+ else if (!strcmp(sb.buf, "b"))
+ state->keep = KEEP_NON_PATCH;
+ else
+ state->keep = KEEP_FALSE;
+
+ read_state_file(&sb, state, "messageid", 1);
+ state->message_id = !strcmp(sb.buf, "t");
+
+ read_state_file(&sb, state, "scissors", 1);
+ if (!strcmp(sb.buf, "t"))
+ state->scissors = SCISSORS_TRUE;
+ else if (!strcmp(sb.buf, "f"))
+ state->scissors = SCISSORS_FALSE;
+ else
+ state->scissors = SCISSORS_UNSET;
+
+ read_state_file(&sb, state, "apply-opt", 1);
+ argv_array_clear(&state->git_apply_opts);
+ if (sq_dequote_to_argv_array(sb.buf, &state->git_apply_opts) < 0)
+ die(_("could not parse %s"), am_path(state, "apply-opt"));
+
+ state->rebasing = !!file_exists(am_path(state, "rebasing"));
+
+ strbuf_release(&sb);
+}
+
+/**
+ * Removes the am_state directory, forcefully terminating the current am
+ * session.
+ */
+static void am_destroy(const struct am_state *state)
+{
+ struct strbuf sb = STRBUF_INIT;
+
+ strbuf_addstr(&sb, state->dir);
+ remove_dir_recursively(&sb, 0);
+ strbuf_release(&sb);
+}
+
+/**
+ * Runs applypatch-msg hook. Returns its exit code.
+ */
+static int run_applypatch_msg_hook(struct am_state *state)
+{
+ int ret;
+
+ assert(state->msg);
+ ret = run_hook_le(NULL, "applypatch-msg", am_path(state, "final-commit"), NULL);
+
+ if (!ret) {
+ free(state->msg);
+ state->msg = NULL;
+ if (read_commit_msg(state) < 0)
+ die(_("'%s' was deleted by the applypatch-msg hook"),
+ am_path(state, "final-commit"));
+ }
+
+ return ret;
+}
+
+/**
+ * Runs post-rewrite hook. Returns it exit code.
+ */
+static int run_post_rewrite_hook(const struct am_state *state)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+ const char *hook = find_hook("post-rewrite");
+ int ret;
+
+ if (!hook)
+ return 0;
+
+ argv_array_push(&cp.args, hook);
+ argv_array_push(&cp.args, "rebase");
+
+ cp.in = xopen(am_path(state, "rewritten"), O_RDONLY);
+ cp.stdout_to_stderr = 1;
+
+ ret = run_command(&cp);
+
+ close(cp.in);
+ return ret;
+}
+
+/**
+ * Reads the state directory's "rewritten" file, and copies notes from the old
+ * commits listed in the file to their rewritten commits.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+static int copy_notes_for_rebase(const struct am_state *state)
+{
+ struct notes_rewrite_cfg *c;
+ struct strbuf sb = STRBUF_INIT;
+ const char *invalid_line = _("Malformed input line: '%s'.");
+ const char *msg = "Notes added by 'git rebase'";
+ FILE *fp;
+ int ret = 0;
+
+ assert(state->rebasing);
+
+ c = init_copy_notes_for_rewrite("rebase");
+ if (!c)
+ return 0;
+
+ fp = xfopen(am_path(state, "rewritten"), "r");
+
+ while (!strbuf_getline(&sb, fp, '\n')) {
+ unsigned char from_obj[GIT_SHA1_RAWSZ], to_obj[GIT_SHA1_RAWSZ];
+
+ if (sb.len != GIT_SHA1_HEXSZ * 2 + 1) {
+ ret = error(invalid_line, sb.buf);
+ goto finish;
+ }
+
+ if (get_sha1_hex(sb.buf, from_obj)) {
+ ret = error(invalid_line, sb.buf);
+ goto finish;
+ }
+
+ if (sb.buf[GIT_SHA1_HEXSZ] != ' ') {
+ ret = error(invalid_line, sb.buf);
+ goto finish;
+ }
+
+ if (get_sha1_hex(sb.buf + GIT_SHA1_HEXSZ + 1, to_obj)) {
+ ret = error(invalid_line, sb.buf);
+ goto finish;
+ }
+
+ if (copy_note_for_rewrite(c, from_obj, to_obj))
+ ret = error(_("Failed to copy notes from '%s' to '%s'"),
+ sha1_to_hex(from_obj), sha1_to_hex(to_obj));
+ }
+
+finish:
+ finish_copy_notes_for_rewrite(c, msg);
+ fclose(fp);
+ strbuf_release(&sb);
+ return ret;
+}
+
+/**
+ * Determines if the file looks like a piece of RFC2822 mail by grabbing all
+ * non-indented lines and checking if they look like they begin with valid
+ * header field names.
+ *
+ * Returns 1 if the file looks like a piece of mail, 0 otherwise.
+ */
+static int is_mail(FILE *fp)
+{
+ const char *header_regex = "^[!-9;-~]+:";
+ struct strbuf sb = STRBUF_INIT;
+ regex_t regex;
+ int ret = 1;
+
+ if (fseek(fp, 0L, SEEK_SET))
+ die_errno(_("fseek failed"));
+
+ if (regcomp(&regex, header_regex, REG_NOSUB | REG_EXTENDED))
+ die("invalid pattern: %s", header_regex);
+
+ while (!strbuf_getline_crlf(&sb, fp)) {
+ if (!sb.len)
+ break; /* End of header */
+
+ /* Ignore indented folded lines */
+ if (*sb.buf == '\t' || *sb.buf == ' ')
+ continue;
+
+ /* It's a header if it matches header_regex */
+ if (regexec(&regex, sb.buf, 0, NULL, 0)) {
+ ret = 0;
+ goto done;
+ }
+ }
+
+done:
+ regfree(&regex);
+ strbuf_release(&sb);
+ return ret;
+}
+
+/**
+ * Attempts to detect the patch_format of the patches contained in `paths`,
+ * returning the PATCH_FORMAT_* enum value. Returns PATCH_FORMAT_UNKNOWN if
+ * detection fails.
+ */
+static int detect_patch_format(const char **paths)
+{
+ enum patch_format ret = PATCH_FORMAT_UNKNOWN;
+ struct strbuf l1 = STRBUF_INIT;
+ struct strbuf l2 = STRBUF_INIT;
+ struct strbuf l3 = STRBUF_INIT;
+ FILE *fp;
+
+ /*
+ * We default to mbox format if input is from stdin and for directories
+ */
+ if (!*paths || !strcmp(*paths, "-") || is_directory(*paths))
+ return PATCH_FORMAT_MBOX;
+
+ /*
+ * Otherwise, check the first few lines of the first patch, starting
+ * from the first non-blank line, to try to detect its format.
+ */
+
+ fp = xfopen(*paths, "r");
+
+ while (!strbuf_getline_crlf(&l1, fp)) {
+ if (l1.len)
+ break;
+ }
+
+ if (starts_with(l1.buf, "From ") || starts_with(l1.buf, "From: ")) {
+ ret = PATCH_FORMAT_MBOX;
+ goto done;
+ }
+
+ if (starts_with(l1.buf, "# This series applies on GIT commit")) {
+ ret = PATCH_FORMAT_STGIT_SERIES;
+ goto done;
+ }
+
+ if (!strcmp(l1.buf, "# HG changeset patch")) {
+ ret = PATCH_FORMAT_HG;
+ goto done;
+ }
+
+ strbuf_reset(&l2);
+ strbuf_getline_crlf(&l2, fp);
+ strbuf_reset(&l3);
+ strbuf_getline_crlf(&l3, fp);
+
+ /*
+ * If the second line is empty and the third is a From, Author or Date
+ * entry, this is likely an StGit patch.
+ */
+ if (l1.len && !l2.len &&
+ (starts_with(l3.buf, "From:") ||
+ starts_with(l3.buf, "Author:") ||
+ starts_with(l3.buf, "Date:"))) {
+ ret = PATCH_FORMAT_STGIT;
+ goto done;
+ }
+
+ if (l1.len && is_mail(fp)) {
+ ret = PATCH_FORMAT_MBOX;
+ goto done;
+ }
+
+done:
+ fclose(fp);
+ strbuf_release(&l1);
+ return ret;
+}
+
+/**
+ * Splits out individual email patches from `paths`, where each path is either
+ * a mbox file or a Maildir. Returns 0 on success, -1 on failure.
+ */
+static int split_mail_mbox(struct am_state *state, const char **paths, int keep_cr)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+ struct strbuf last = STRBUF_INIT;
+
+ cp.git_cmd = 1;
+ argv_array_push(&cp.args, "mailsplit");
+ argv_array_pushf(&cp.args, "-d%d", state->prec);
+ argv_array_pushf(&cp.args, "-o%s", state->dir);
+ argv_array_push(&cp.args, "-b");
+ if (keep_cr)
+ argv_array_push(&cp.args, "--keep-cr");
+ argv_array_push(&cp.args, "--");
+ argv_array_pushv(&cp.args, paths);
+
+ if (capture_command(&cp, &last, 8))
+ return -1;
+
+ state->cur = 1;
+ state->last = strtol(last.buf, NULL, 10);
+
+ return 0;
+}
+
+/**
+ * Callback signature for split_mail_conv(). The foreign patch should be
+ * read from `in`, and the converted patch (in RFC2822 mail format) should be
+ * written to `out`. Return 0 on success, or -1 on failure.
+ */
+typedef int (*mail_conv_fn)(FILE *out, FILE *in, int keep_cr);
+
+/**
+ * Calls `fn` for each file in `paths` to convert the foreign patch to the
+ * RFC2822 mail format suitable for parsing with git-mailinfo.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+static int split_mail_conv(mail_conv_fn fn, struct am_state *state,
+ const char **paths, int keep_cr)
+{
+ static const char *stdin_only[] = {"-", NULL};
+ int i;
+
+ if (!*paths)
+ paths = stdin_only;
+
+ for (i = 0; *paths; paths++, i++) {
+ FILE *in, *out;
+ const char *mail;
+ int ret;
+
+ if (!strcmp(*paths, "-"))
+ in = stdin;
+ else
+ in = fopen(*paths, "r");
+
+ if (!in)
+ return error(_("could not open '%s' for reading: %s"),
+ *paths, strerror(errno));
+
+ mail = mkpath("%s/%0*d", state->dir, state->prec, i + 1);
+
+ out = fopen(mail, "w");
+ if (!out)
+ return error(_("could not open '%s' for writing: %s"),
+ mail, strerror(errno));
+
+ ret = fn(out, in, keep_cr);
+
+ fclose(out);
+ fclose(in);
+
+ if (ret)
+ return error(_("could not parse patch '%s'"), *paths);
+ }
+
+ state->cur = 1;
+ state->last = i;
+ return 0;
+}
+
+/**
+ * A split_mail_conv() callback that converts an StGit patch to an RFC2822
+ * message suitable for parsing with git-mailinfo.
+ */
+static int stgit_patch_to_mail(FILE *out, FILE *in, int keep_cr)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int subject_printed = 0;
+
+ while (!strbuf_getline(&sb, in, '\n')) {
+ const char *str;
+
+ if (str_isspace(sb.buf))
+ continue;
+ else if (skip_prefix(sb.buf, "Author:", &str))
+ fprintf(out, "From:%s\n", str);
+ else if (starts_with(sb.buf, "From") || starts_with(sb.buf, "Date"))
+ fprintf(out, "%s\n", sb.buf);
+ else if (!subject_printed) {
+ fprintf(out, "Subject: %s\n", sb.buf);
+ subject_printed = 1;
+ } else {
+ fprintf(out, "\n%s\n", sb.buf);
+ break;
+ }
+ }
+
+ strbuf_reset(&sb);
+ while (strbuf_fread(&sb, 8192, in) > 0) {
+ fwrite(sb.buf, 1, sb.len, out);
+ strbuf_reset(&sb);
+ }
+
+ strbuf_release(&sb);
+ return 0;
+}
+
+/**
+ * This function only supports a single StGit series file in `paths`.
+ *
+ * Given an StGit series file, converts the StGit patches in the series into
+ * RFC2822 messages suitable for parsing with git-mailinfo, and queues them in
+ * the state directory.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+static int split_mail_stgit_series(struct am_state *state, const char **paths,
+ int keep_cr)
+{
+ const char *series_dir;
+ char *series_dir_buf;
+ FILE *fp;
+ struct argv_array patches = ARGV_ARRAY_INIT;
+ struct strbuf sb = STRBUF_INIT;
+ int ret;
+
+ if (!paths[0] || paths[1])
+ return error(_("Only one StGIT patch series can be applied at once"));
+
+ series_dir_buf = xstrdup(*paths);
+ series_dir = dirname(series_dir_buf);
+
+ fp = fopen(*paths, "r");
+ if (!fp)
+ return error(_("could not open '%s' for reading: %s"), *paths,
+ strerror(errno));
+
+ while (!strbuf_getline(&sb, fp, '\n')) {
+ if (*sb.buf == '#')
+ continue; /* skip comment lines */
+
+ argv_array_push(&patches, mkpath("%s/%s", series_dir, sb.buf));
+ }
+
+ fclose(fp);
+ strbuf_release(&sb);
+ free(series_dir_buf);
+
+ ret = split_mail_conv(stgit_patch_to_mail, state, patches.argv, keep_cr);
+
+ argv_array_clear(&patches);
+ return ret;
+}
+
+/**
+ * A split_patches_conv() callback that converts a mercurial patch to a RFC2822
+ * message suitable for parsing with git-mailinfo.
+ */
+static int hg_patch_to_mail(FILE *out, FILE *in, int keep_cr)
+{
+ struct strbuf sb = STRBUF_INIT;
+
+ while (!strbuf_getline(&sb, in, '\n')) {
+ const char *str;
+
+ if (skip_prefix(sb.buf, "# User ", &str))
+ fprintf(out, "From: %s\n", str);
+ else if (skip_prefix(sb.buf, "# Date ", &str)) {
+ unsigned long timestamp;
+ long tz, tz2;
+ char *end;
+
+ errno = 0;
+ timestamp = strtoul(str, &end, 10);
+ if (errno)
+ return error(_("invalid timestamp"));
+
+ if (!skip_prefix(end, " ", &str))
+ return error(_("invalid Date line"));
+
+ errno = 0;
+ tz = strtol(str, &end, 10);
+ if (errno)
+ return error(_("invalid timezone offset"));
+
+ if (*end)
+ return error(_("invalid Date line"));
+
+ /*
+ * mercurial's timezone is in seconds west of UTC,
+ * however git's timezone is in hours + minutes east of
+ * UTC. Convert it.
+ */
+ tz2 = labs(tz) / 3600 * 100 + labs(tz) % 3600 / 60;
+ if (tz > 0)
+ tz2 = -tz2;
+
+ fprintf(out, "Date: %s\n", show_date(timestamp, tz2, DATE_MODE(RFC2822)));
+ } else if (starts_with(sb.buf, "# ")) {
+ continue;
+ } else {
+ fprintf(out, "\n%s\n", sb.buf);
+ break;
+ }
+ }
+
+ strbuf_reset(&sb);
+ while (strbuf_fread(&sb, 8192, in) > 0) {
+ fwrite(sb.buf, 1, sb.len, out);
+ strbuf_reset(&sb);
+ }
+
+ strbuf_release(&sb);
+ return 0;
+}
+
+/**
+ * Splits a list of files/directories into individual email patches. Each path
+ * in `paths` must be a file/directory that is formatted according to
+ * `patch_format`.
+ *
+ * Once split out, the individual email patches will be stored in the state
+ * directory, with each patch's filename being its index, padded to state->prec
+ * digits.
+ *
+ * state->cur will be set to the index of the first mail, and state->last will
+ * be set to the index of the last mail.
+ *
+ * Set keep_cr to 0 to convert all lines ending with \r\n to end with \n, 1
+ * to disable this behavior, -1 to use the default configured setting.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+static int split_mail(struct am_state *state, enum patch_format patch_format,
+ const char **paths, int keep_cr)
+{
+ if (keep_cr < 0) {
+ keep_cr = 0;
+ git_config_get_bool("am.keepcr", &keep_cr);
+ }
+
+ switch (patch_format) {
+ case PATCH_FORMAT_MBOX:
+ return split_mail_mbox(state, paths, keep_cr);
+ case PATCH_FORMAT_STGIT:
+ return split_mail_conv(stgit_patch_to_mail, state, paths, keep_cr);
+ case PATCH_FORMAT_STGIT_SERIES:
+ return split_mail_stgit_series(state, paths, keep_cr);
+ case PATCH_FORMAT_HG:
+ return split_mail_conv(hg_patch_to_mail, state, paths, keep_cr);
+ default:
+ die("BUG: invalid patch_format");
+ }
+ return -1;
+}
+
+/**
+ * Setup a new am session for applying patches
+ */
+static void am_setup(struct am_state *state, enum patch_format patch_format,
+ const char **paths, int keep_cr)
+{
+ unsigned char curr_head[GIT_SHA1_RAWSZ];
+ const char *str;
+ struct strbuf sb = STRBUF_INIT;
+
+ if (!patch_format)
+ patch_format = detect_patch_format(paths);
+
+ if (!patch_format) {
+ fprintf_ln(stderr, _("Patch format detection failed."));
+ exit(128);
+ }
+
+ if (mkdir(state->dir, 0777) < 0 && errno != EEXIST)
+ die_errno(_("failed to create directory '%s'"), state->dir);
+
+ if (split_mail(state, patch_format, paths, keep_cr) < 0) {
+ am_destroy(state);
+ die(_("Failed to split patches."));
+ }
+
+ if (state->rebasing)
+ state->threeway = 1;
+
+ write_file(am_path(state, "threeway"), 1, state->threeway ? "t" : "f");
+
+ write_file(am_path(state, "quiet"), 1, state->quiet ? "t" : "f");
+
+ write_file(am_path(state, "sign"), 1, state->signoff ? "t" : "f");
+
+ write_file(am_path(state, "utf8"), 1, state->utf8 ? "t" : "f");
+
+ switch (state->keep) {
+ case KEEP_FALSE:
+ str = "f";
+ break;
+ case KEEP_TRUE:
+ str = "t";
+ break;
+ case KEEP_NON_PATCH:
+ str = "b";
+ break;
+ default:
+ die("BUG: invalid value for state->keep");
+ }
+
+ write_file(am_path(state, "keep"), 1, "%s", str);
+
+ write_file(am_path(state, "messageid"), 1, state->message_id ? "t" : "f");
+
+ switch (state->scissors) {
+ case SCISSORS_UNSET:
+ str = "";
+ break;
+ case SCISSORS_FALSE:
+ str = "f";
+ break;
+ case SCISSORS_TRUE:
+ str = "t";
+ break;
+ default:
+ die("BUG: invalid value for state->scissors");
+ }
+
+ write_file(am_path(state, "scissors"), 1, "%s", str);
+
+ sq_quote_argv(&sb, state->git_apply_opts.argv, 0);
+ write_file(am_path(state, "apply-opt"), 1, "%s", sb.buf);
+
+ if (state->rebasing)
+ write_file(am_path(state, "rebasing"), 1, "%s", "");
+ else
+ write_file(am_path(state, "applying"), 1, "%s", "");
+
+ if (!get_sha1("HEAD", curr_head)) {
+ write_file(am_path(state, "abort-safety"), 1, "%s", sha1_to_hex(curr_head));
+ if (!state->rebasing)
+ update_ref("am", "ORIG_HEAD", curr_head, NULL, 0,
+ UPDATE_REFS_DIE_ON_ERR);
+ } else {
+ write_file(am_path(state, "abort-safety"), 1, "%s", "");
+ if (!state->rebasing)
+ delete_ref("ORIG_HEAD", NULL, 0);
+ }
+
+ /*
+ * NOTE: Since the "next" and "last" files determine if an am_state
+ * session is in progress, they should be written last.
+ */
+
+ write_file(am_path(state, "next"), 1, "%d", state->cur);
+
+ write_file(am_path(state, "last"), 1, "%d", state->last);
+
+ strbuf_release(&sb);
+}
+
+/**
+ * Increments the patch pointer, and cleans am_state for the application of the
+ * next patch.
+ */
+static void am_next(struct am_state *state)
+{
+ unsigned char head[GIT_SHA1_RAWSZ];
+
+ free(state->author_name);
+ state->author_name = NULL;
+
+ free(state->author_email);
+ state->author_email = NULL;
+
+ free(state->author_date);
+ state->author_date = NULL;
+
+ free(state->msg);
+ state->msg = NULL;
+ state->msg_len = 0;
+
+ unlink(am_path(state, "author-script"));
+ unlink(am_path(state, "final-commit"));
+
+ hashclr(state->orig_commit);
+ unlink(am_path(state, "original-commit"));
+
+ if (!get_sha1("HEAD", head))
+ write_file(am_path(state, "abort-safety"), 1, "%s", sha1_to_hex(head));
+ else
+ write_file(am_path(state, "abort-safety"), 1, "%s", "");
+
+ state->cur++;
+ write_file(am_path(state, "next"), 1, "%d", state->cur);
+}
+
+/**
+ * Returns the filename of the current patch email.
+ */
+static const char *msgnum(const struct am_state *state)
+{
+ static struct strbuf sb = STRBUF_INIT;
+
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%0*d", state->prec, state->cur);
+
+ return sb.buf;
+}
+
+/**
+ * Refresh and write index.
+ */
+static void refresh_and_write_cache(void)
+{
+ struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+ hold_locked_index(lock_file, 1);
+ refresh_cache(REFRESH_QUIET);
+ if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
+ die(_("unable to write index file"));
+}
+
+/**
+ * Returns 1 if the index differs from HEAD, 0 otherwise. When on an unborn
+ * branch, returns 1 if there are entries in the index, 0 otherwise. If an
+ * strbuf is provided, the space-separated list of files that differ will be
+ * appended to it.
+ */
+static int index_has_changes(struct strbuf *sb)
+{
+ unsigned char head[GIT_SHA1_RAWSZ];
+ int i;
+
+ if (!get_sha1_tree("HEAD", head)) {
+ struct diff_options opt;
+
+ diff_setup(&opt);
+ DIFF_OPT_SET(&opt, EXIT_WITH_STATUS);
+ if (!sb)
+ DIFF_OPT_SET(&opt, QUICK);
+ do_diff_cache(head, &opt);
+ diffcore_std(&opt);
+ for (i = 0; sb && i < diff_queued_diff.nr; i++) {
+ if (i)
+ strbuf_addch(sb, ' ');
+ strbuf_addstr(sb, diff_queued_diff.queue[i]->two->path);
+ }
+ diff_flush(&opt);
+ return DIFF_OPT_TST(&opt, HAS_CHANGES) != 0;
+ } else {
+ for (i = 0; sb && i < active_nr; i++) {
+ if (i)
+ strbuf_addch(sb, ' ');
+ strbuf_addstr(sb, active_cache[i]->name);
+ }
+ return !!active_nr;
+ }
+}
+
+/**
+ * Dies with a user-friendly message on how to proceed after resolving the
+ * problem. This message can be overridden with state->resolvemsg.
+ */
+static void NORETURN die_user_resolve(const struct am_state *state)
+{
+ if (state->resolvemsg) {
+ printf_ln("%s", state->resolvemsg);
+ } else {
+ const char *cmdline = state->interactive ? "git am -i" : "git am";
+
+ printf_ln(_("When you have resolved this problem, run \"%s --continue\"."), cmdline);
+ printf_ln(_("If you prefer to skip this patch, run \"%s --skip\" instead."), cmdline);
+ printf_ln(_("To restore the original branch and stop patching, run \"%s --abort\"."), cmdline);
+ }
+
+ exit(128);
+}
+
+/**
+ * Parses `mail` using git-mailinfo, extracting its patch and authorship info.
+ * state->msg will be set to the patch message. state->author_name,
+ * state->author_email and state->author_date will be set to the patch author's
+ * name, email and date respectively. The patch body will be written to the
+ * state directory's "patch" file.
+ *
+ * Returns 1 if the patch should be skipped, 0 otherwise.
+ */
+static int parse_mail(struct am_state *state, const char *mail)
+{
+ FILE *fp;
+ struct child_process cp = CHILD_PROCESS_INIT;
+ struct strbuf sb = STRBUF_INIT;
+ struct strbuf msg = STRBUF_INIT;
+ struct strbuf author_name = STRBUF_INIT;
+ struct strbuf author_date = STRBUF_INIT;
+ struct strbuf author_email = STRBUF_INIT;
+ int ret = 0;
+
+ cp.git_cmd = 1;
+ cp.in = xopen(mail, O_RDONLY, 0);
+ cp.out = xopen(am_path(state, "info"), O_WRONLY | O_CREAT, 0777);
+
+ argv_array_push(&cp.args, "mailinfo");
+ argv_array_push(&cp.args, state->utf8 ? "-u" : "-n");
+
+ switch (state->keep) {
+ case KEEP_FALSE:
+ break;
+ case KEEP_TRUE:
+ argv_array_push(&cp.args, "-k");
+ break;
+ case KEEP_NON_PATCH:
+ argv_array_push(&cp.args, "-b");
+ break;
+ default:
+ die("BUG: invalid value for state->keep");
+ }
+
+ if (state->message_id)
+ argv_array_push(&cp.args, "-m");
+
+ switch (state->scissors) {
+ case SCISSORS_UNSET:
+ break;
+ case SCISSORS_FALSE:
+ argv_array_push(&cp.args, "--no-scissors");
+ break;
+ case SCISSORS_TRUE:
+ argv_array_push(&cp.args, "--scissors");
+ break;
+ default:
+ die("BUG: invalid value for state->scissors");
+ }
+
+ argv_array_push(&cp.args, am_path(state, "msg"));
+ argv_array_push(&cp.args, am_path(state, "patch"));
+
+ if (run_command(&cp) < 0)
+ die("could not parse patch");
+
+ close(cp.in);
+ close(cp.out);
+
+ /* Extract message and author information */
+ fp = xfopen(am_path(state, "info"), "r");
+ while (!strbuf_getline(&sb, fp, '\n')) {
+ const char *x;
+
+ if (skip_prefix(sb.buf, "Subject: ", &x)) {
+ if (msg.len)
+ strbuf_addch(&msg, '\n');
+ strbuf_addstr(&msg, x);
+ } else if (skip_prefix(sb.buf, "Author: ", &x))
+ strbuf_addstr(&author_name, x);
+ else if (skip_prefix(sb.buf, "Email: ", &x))
+ strbuf_addstr(&author_email, x);
+ else if (skip_prefix(sb.buf, "Date: ", &x))
+ strbuf_addstr(&author_date, x);
+ }
+ fclose(fp);
+
+ /* Skip pine's internal folder data */
+ if (!strcmp(author_name.buf, "Mail System Internal Data")) {
+ ret = 1;
+ goto finish;
+ }
+
+ if (is_empty_file(am_path(state, "patch"))) {
+ printf_ln(_("Patch is empty. Was it split wrong?"));
+ die_user_resolve(state);
+ }
+
+ strbuf_addstr(&msg, "\n\n");
+ if (strbuf_read_file(&msg, am_path(state, "msg"), 0) < 0)
+ die_errno(_("could not read '%s'"), am_path(state, "msg"));
+ stripspace(&msg, 0);
+
+ if (state->signoff)
+ append_signoff(&msg, 0, 0);
+
+ assert(!state->author_name);
+ state->author_name = strbuf_detach(&author_name, NULL);
+
+ assert(!state->author_email);
+ state->author_email = strbuf_detach(&author_email, NULL);
+
+ assert(!state->author_date);
+ state->author_date = strbuf_detach(&author_date, NULL);
+
+ assert(!state->msg);
+ state->msg = strbuf_detach(&msg, &state->msg_len);
+
+finish:
+ strbuf_release(&msg);
+ strbuf_release(&author_date);
+ strbuf_release(&author_email);
+ strbuf_release(&author_name);
+ strbuf_release(&sb);
+ return ret;
+}
+
+/**
+ * Sets commit_id to the commit hash where the mail was generated from.
+ * Returns 0 on success, -1 on failure.
+ */
+static int get_mail_commit_sha1(unsigned char *commit_id, const char *mail)
+{
+ struct strbuf sb = STRBUF_INIT;
+ FILE *fp = xfopen(mail, "r");
+ const char *x;
+
+ if (strbuf_getline(&sb, fp, '\n'))
+ return -1;
+
+ if (!skip_prefix(sb.buf, "From ", &x))
+ return -1;
+
+ if (get_sha1_hex(x, commit_id) < 0)
+ return -1;
+
+ strbuf_release(&sb);
+ fclose(fp);
+ return 0;
+}
+
+/**
+ * Sets state->msg, state->author_name, state->author_email, state->author_date
+ * to the commit's respective info.
+ */
+static void get_commit_info(struct am_state *state, struct commit *commit)
+{
+ const char *buffer, *ident_line, *author_date, *msg;
+ size_t ident_len;
+ struct ident_split ident_split;
+ struct strbuf sb = STRBUF_INIT;
+
+ buffer = logmsg_reencode(commit, NULL, get_commit_output_encoding());
+
+ ident_line = find_commit_header(buffer, "author", &ident_len);
+
+ if (split_ident_line(&ident_split, ident_line, ident_len) < 0) {
+ strbuf_add(&sb, ident_line, ident_len);
+ die(_("invalid ident line: %s"), sb.buf);
+ }
+
+ assert(!state->author_name);
+ if (ident_split.name_begin) {
+ strbuf_add(&sb, ident_split.name_begin,
+ ident_split.name_end - ident_split.name_begin);
+ state->author_name = strbuf_detach(&sb, NULL);
+ } else
+ state->author_name = xstrdup("");
+
+ assert(!state->author_email);
+ if (ident_split.mail_begin) {
+ strbuf_add(&sb, ident_split.mail_begin,
+ ident_split.mail_end - ident_split.mail_begin);
+ state->author_email = strbuf_detach(&sb, NULL);
+ } else
+ state->author_email = xstrdup("");
+
+ author_date = show_ident_date(&ident_split, DATE_MODE(NORMAL));
+ strbuf_addstr(&sb, author_date);
+ assert(!state->author_date);
+ state->author_date = strbuf_detach(&sb, NULL);
+
+ assert(!state->msg);
+ msg = strstr(buffer, "\n\n");
+ if (!msg)
+ die(_("unable to parse commit %s"), sha1_to_hex(commit->object.sha1));
+ state->msg = xstrdup(msg + 2);
+ state->msg_len = strlen(state->msg);
+}
+
+/**
+ * Writes `commit` as a patch to the state directory's "patch" file.
+ */
+static void write_commit_patch(const struct am_state *state, struct commit *commit)
+{
+ struct rev_info rev_info;
+ FILE *fp;
+
+ fp = xfopen(am_path(state, "patch"), "w");
+ init_revisions(&rev_info, NULL);
+ rev_info.diff = 1;
+ rev_info.abbrev = 0;
+ rev_info.disable_stdin = 1;
+ rev_info.show_root_diff = 1;
+ rev_info.diffopt.output_format = DIFF_FORMAT_PATCH;
+ rev_info.no_commit_id = 1;
+ DIFF_OPT_SET(&rev_info.diffopt, BINARY);
+ DIFF_OPT_SET(&rev_info.diffopt, FULL_INDEX);
+ rev_info.diffopt.use_color = 0;
+ rev_info.diffopt.file = fp;
+ rev_info.diffopt.close_file = 1;
+ add_pending_object(&rev_info, &commit->object, "");
+ diff_setup_done(&rev_info.diffopt);
+ log_tree_commit(&rev_info, commit);
+}
+
+/**
+ * Writes the diff of the index against HEAD as a patch to the state
+ * directory's "patch" file.
+ */
+static void write_index_patch(const struct am_state *state)
+{
+ struct tree *tree;
+ unsigned char head[GIT_SHA1_RAWSZ];
+ struct rev_info rev_info;
+ FILE *fp;
+
+ if (!get_sha1_tree("HEAD", head))
+ tree = lookup_tree(head);
+ else
+ tree = lookup_tree(EMPTY_TREE_SHA1_BIN);
+
+ fp = xfopen(am_path(state, "patch"), "w");
+ init_revisions(&rev_info, NULL);
+ rev_info.diff = 1;
+ rev_info.disable_stdin = 1;
+ rev_info.no_commit_id = 1;
+ rev_info.diffopt.output_format = DIFF_FORMAT_PATCH;
+ rev_info.diffopt.use_color = 0;
+ rev_info.diffopt.file = fp;
+ rev_info.diffopt.close_file = 1;
+ add_pending_object(&rev_info, &tree->object, "");
+ diff_setup_done(&rev_info.diffopt);
+ run_diff_index(&rev_info, 1);
+}
+
+/**
+ * Like parse_mail(), but parses the mail by looking up its commit ID
+ * directly. This is used in --rebasing mode to bypass git-mailinfo's munging
+ * of patches.
+ *
+ * state->orig_commit will be set to the original commit ID.
+ *
+ * Will always return 0 as the patch should never be skipped.
+ */
+static int parse_mail_rebase(struct am_state *state, const char *mail)
+{
+ struct commit *commit;
+ unsigned char commit_sha1[GIT_SHA1_RAWSZ];
+
+ if (get_mail_commit_sha1(commit_sha1, mail) < 0)
+ die(_("could not parse %s"), mail);
+
+ commit = lookup_commit_or_die(commit_sha1, mail);
+
+ get_commit_info(state, commit);
+
+ write_commit_patch(state, commit);
+
+ hashcpy(state->orig_commit, commit_sha1);
+ write_file(am_path(state, "original-commit"), 1, "%s",
+ sha1_to_hex(commit_sha1));
+
+ return 0;
+}
+
+/**
+ * Applies current patch with git-apply. Returns 0 on success, -1 otherwise. If
+ * `index_file` is not NULL, the patch will be applied to that index.
+ */
+static int run_apply(const struct am_state *state, const char *index_file)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ cp.git_cmd = 1;
+
+ if (index_file)
+ argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", index_file);
+
+ /*
+ * If we are allowed to fall back on 3-way merge, don't give false
+ * errors during the initial attempt.
+ */
+ if (state->threeway && !index_file) {
+ cp.no_stdout = 1;
+ cp.no_stderr = 1;
+ }
+
+ argv_array_push(&cp.args, "apply");
+
+ argv_array_pushv(&cp.args, state->git_apply_opts.argv);
+
+ if (index_file)
+ argv_array_push(&cp.args, "--cached");
+ else
+ argv_array_push(&cp.args, "--index");
+
+ argv_array_push(&cp.args, am_path(state, "patch"));
+
+ if (run_command(&cp))
+ return -1;
+
+ /* Reload index as git-apply will have modified it. */
+ discard_cache();
+ read_cache_from(index_file ? index_file : get_index_file());
+
+ return 0;
+}
+
+/**
+ * Builds an index that contains just the blobs needed for a 3way merge.
+ */
+static int build_fake_ancestor(const struct am_state *state, const char *index_file)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ cp.git_cmd = 1;
+ argv_array_push(&cp.args, "apply");
+ argv_array_pushv(&cp.args, state->git_apply_opts.argv);
+ argv_array_pushf(&cp.args, "--build-fake-ancestor=%s", index_file);
+ argv_array_push(&cp.args, am_path(state, "patch"));
+
+ if (run_command(&cp))
+ return -1;
+
+ return 0;
+}
+
+/**
+ * Attempt a threeway merge, using index_path as the temporary index.
+ */
+static int fall_back_threeway(const struct am_state *state, const char *index_path)
+{
+ unsigned char orig_tree[GIT_SHA1_RAWSZ], his_tree[GIT_SHA1_RAWSZ],
+ our_tree[GIT_SHA1_RAWSZ];
+ const unsigned char *bases[1] = {orig_tree};
+ struct merge_options o;
+ struct commit *result;
+ char *his_tree_name;
+
+ if (get_sha1("HEAD", our_tree) < 0)
+ hashcpy(our_tree, EMPTY_TREE_SHA1_BIN);
+
+ if (build_fake_ancestor(state, index_path))
+ return error("could not build fake ancestor");
+
+ discard_cache();
+ read_cache_from(index_path);
+
+ if (write_index_as_tree(orig_tree, &the_index, index_path, 0, NULL))
+ return error(_("Repository lacks necessary blobs to fall back on 3-way merge."));
+
+ say(state, stdout, _("Using index info to reconstruct a base tree..."));
+
+ if (!state->quiet) {
+ /*
+ * List paths that needed 3-way fallback, so that the user can
+ * review them with extra care to spot mismerges.
+ */
+ struct rev_info rev_info;
+ const char *diff_filter_str = "--diff-filter=AM";
+
+ init_revisions(&rev_info, NULL);
+ rev_info.diffopt.output_format = DIFF_FORMAT_NAME_STATUS;
+ diff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1);
+ add_pending_sha1(&rev_info, "HEAD", our_tree, 0);
+ diff_setup_done(&rev_info.diffopt);
+ run_diff_index(&rev_info, 1);
+ }
+
+ if (run_apply(state, index_path))
+ return error(_("Did you hand edit your patch?\n"
+ "It does not apply to blobs recorded in its index."));
+
+ if (write_index_as_tree(his_tree, &the_index, index_path, 0, NULL))
+ return error("could not write tree");
+
+ say(state, stdout, _("Falling back to patching base and 3-way merge..."));
+
+ discard_cache();
+ read_cache();
+
+ /*
+ * This is not so wrong. Depending on which base we picked, orig_tree
+ * may be wildly different from ours, but his_tree has the same set of
+ * wildly different changes in parts the patch did not touch, so
+ * recursive ends up canceling them, saying that we reverted all those
+ * changes.
+ */
+
+ init_merge_options(&o);
+
+ o.branch1 = "HEAD";
+ his_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
+ o.branch2 = his_tree_name;
+
+ if (state->quiet)
+ o.verbosity = 0;
+
+ if (merge_recursive_generic(&o, our_tree, his_tree, 1, bases, &result)) {
+ rerere(state->allow_rerere_autoupdate);
+ free(his_tree_name);
+ return error(_("Failed to merge in the changes."));
+ }
+
+ free(his_tree_name);
+ return 0;
+}
+
+/**
+ * Commits the current index with state->msg as the commit message and
+ * state->author_name, state->author_email and state->author_date as the author
+ * information.
+ */
+static void do_commit(const struct am_state *state)
+{
+ unsigned char tree[GIT_SHA1_RAWSZ], parent[GIT_SHA1_RAWSZ],
+ commit[GIT_SHA1_RAWSZ];
+ unsigned char *ptr;
+ struct commit_list *parents = NULL;
+ const char *reflog_msg, *author;
+ struct strbuf sb = STRBUF_INIT;
+
+ if (run_hook_le(NULL, "pre-applypatch", NULL))
+ exit(1);
+
+ if (write_cache_as_tree(tree, 0, NULL))
+ die(_("git write-tree failed to write a tree"));
+
+ if (!get_sha1_commit("HEAD", parent)) {
+ ptr = parent;
+ commit_list_insert(lookup_commit(parent), &parents);
+ } else {
+ ptr = NULL;
+ say(state, stderr, _("applying to an empty history"));
+ }
+
+ author = fmt_ident(state->author_name, state->author_email,
+ state->ignore_date ? NULL : state->author_date,
+ IDENT_STRICT);
+
+ if (state->committer_date_is_author_date)
+ setenv("GIT_COMMITTER_DATE",
+ state->ignore_date ? "" : state->author_date, 1);
+
+ if (commit_tree(state->msg, state->msg_len, tree, parents, commit,
+ author, state->sign_commit))
+ die(_("failed to write commit object"));
+
+ reflog_msg = getenv("GIT_REFLOG_ACTION");
+ if (!reflog_msg)
+ reflog_msg = "am";
+
+ strbuf_addf(&sb, "%s: %.*s", reflog_msg, linelen(state->msg),
+ state->msg);
+
+ update_ref(sb.buf, "HEAD", commit, ptr, 0, UPDATE_REFS_DIE_ON_ERR);
+
+ if (state->rebasing) {
+ FILE *fp = xfopen(am_path(state, "rewritten"), "a");
+
+ assert(!is_null_sha1(state->orig_commit));
+ fprintf(fp, "%s ", sha1_to_hex(state->orig_commit));
+ fprintf(fp, "%s\n", sha1_to_hex(commit));
+ fclose(fp);
+ }
+
+ run_hook_le(NULL, "post-applypatch", NULL);
+
+ strbuf_release(&sb);
+}
+
+/**
+ * Validates the am_state for resuming -- the "msg" and authorship fields must
+ * be filled up.
+ */
+static void validate_resume_state(const struct am_state *state)
+{
+ if (!state->msg)
+ die(_("cannot resume: %s does not exist."),
+ am_path(state, "final-commit"));
+
+ if (!state->author_name || !state->author_email || !state->author_date)
+ die(_("cannot resume: %s does not exist."),
+ am_path(state, "author-script"));
+}
+
+/**
+ * Interactively prompt the user on whether the current patch should be
+ * applied.
+ *
+ * Returns 0 if the user chooses to apply the patch, 1 if the user chooses to
+ * skip it.
+ */
+static int do_interactive(struct am_state *state)
+{
+ assert(state->msg);
+
+ if (!isatty(0))
+ die(_("cannot be interactive without stdin connected to a terminal."));
+
+ for (;;) {
+ const char *reply;
+
+ puts(_("Commit Body is:"));
+ puts("--------------------------");
+ printf("%s", state->msg);
+ puts("--------------------------");
+
+ /*
+ * TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
+ * in your translation. The program will only accept English
+ * input at this point.
+ */
+ reply = git_prompt(_("Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all: "), PROMPT_ECHO);
+
+ if (!reply) {
+ continue;
+ } else if (*reply == 'y' || *reply == 'Y') {
+ return 0;
+ } else if (*reply == 'a' || *reply == 'A') {
+ state->interactive = 0;
+ return 0;
+ } else if (*reply == 'n' || *reply == 'N') {
+ return 1;
+ } else if (*reply == 'e' || *reply == 'E') {
+ struct strbuf msg = STRBUF_INIT;
+
+ if (!launch_editor(am_path(state, "final-commit"), &msg, NULL)) {
+ free(state->msg);
+ state->msg = strbuf_detach(&msg, &state->msg_len);
+ }
+ strbuf_release(&msg);
+ } else if (*reply == 'v' || *reply == 'V') {
+ const char *pager = git_pager(1);
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ if (!pager)
+ pager = "cat";
+ argv_array_push(&cp.args, pager);
+ argv_array_push(&cp.args, am_path(state, "patch"));
+ run_command(&cp);
+ }
+ }
+}
+
+/**
+ * Applies all queued mail.
+ *
+ * If `resume` is true, we are "resuming". The "msg" and authorship fields, as
+ * well as the state directory's "patch" file is used as-is for applying the
+ * patch and committing it.
+ */
+static void am_run(struct am_state *state, int resume)
+{
+ const char *argv_gc_auto[] = {"gc", "--auto", NULL};
+ struct strbuf sb = STRBUF_INIT;
+
+ unlink(am_path(state, "dirtyindex"));
+
+ refresh_and_write_cache();
+
+ if (index_has_changes(&sb)) {
+ write_file(am_path(state, "dirtyindex"), 1, "t");
+ die(_("Dirty index: cannot apply patches (dirty: %s)"), sb.buf);
+ }
+
+ strbuf_release(&sb);
+
+ while (state->cur <= state->last) {
+ const char *mail = am_path(state, msgnum(state));
+ int apply_status;
+
+ if (!file_exists(mail))
+ goto next;
+
+ if (resume) {
+ validate_resume_state(state);
+ resume = 0;
+ } else {
+ int skip;
+
+ if (state->rebasing)
+ skip = parse_mail_rebase(state, mail);
+ else
+ skip = parse_mail(state, mail);
+
+ if (skip)
+ goto next; /* mail should be skipped */
+
+ write_author_script(state);
+ write_commit_msg(state);
+ }
+
+ if (state->interactive && do_interactive(state))
+ goto next;
+
+ if (run_applypatch_msg_hook(state))
+ exit(1);
+
+ say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
+
+ apply_status = run_apply(state, NULL);
+
+ if (apply_status && state->threeway) {
+ struct strbuf sb = STRBUF_INIT;
+
+ strbuf_addstr(&sb, am_path(state, "patch-merge-index"));
+ apply_status = fall_back_threeway(state, sb.buf);
+ strbuf_release(&sb);
+
+ /*
+ * Applying the patch to an earlier tree and merging
+ * the result may have produced the same tree as ours.
+ */
+ if (!apply_status && !index_has_changes(NULL)) {
+ say(state, stdout, _("No changes -- Patch already applied."));
+ goto next;
+ }
+ }
+
+ if (apply_status) {
+ int advice_amworkdir = 1;
+
+ printf_ln(_("Patch failed at %s %.*s"), msgnum(state),
+ linelen(state->msg), state->msg);
+
+ git_config_get_bool("advice.amworkdir", &advice_amworkdir);
+
+ if (advice_amworkdir)
+ printf_ln(_("The copy of the patch that failed is found in: %s"),
+ am_path(state, "patch"));
+
+ die_user_resolve(state);
+ }
+
+ do_commit(state);
+
+next:
+ am_next(state);
+ }
+
+ if (!is_empty_file(am_path(state, "rewritten"))) {
+ assert(state->rebasing);
+ copy_notes_for_rebase(state);
+ run_post_rewrite_hook(state);
+ }
+
+ /*
+ * In rebasing mode, it's up to the caller to take care of
+ * housekeeping.
+ */
+ if (!state->rebasing) {
+ am_destroy(state);
+ run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+ }
+}
+
+/**
+ * Resume the current am session after patch application failure. The user did
+ * all the hard work, and we do not have to do any patch application. Just
+ * trust and commit what the user has in the index and working tree.
+ */
+static void am_resolve(struct am_state *state)
+{
+ validate_resume_state(state);
+
+ say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
+
+ if (!index_has_changes(NULL)) {
+ printf_ln(_("No changes - did you forget to use 'git add'?\n"
+ "If there is nothing left to stage, chances are that something else\n"
+ "already introduced the same changes; you might want to skip this patch."));
+ die_user_resolve(state);
+ }
+
+ if (unmerged_cache()) {
+ printf_ln(_("You still have unmerged paths in your index.\n"
+ "Did you forget to use 'git add'?"));
+ die_user_resolve(state);
+ }
+
+ if (state->interactive) {
+ write_index_patch(state);
+ if (do_interactive(state))
+ goto next;
+ }
+
+ rerere(0);
+
+ do_commit(state);
+
+next:
+ am_next(state);
+ am_run(state, 0);
+}
+
+/**
+ * Performs a checkout fast-forward from `head` to `remote`. If `reset` is
+ * true, any unmerged entries will be discarded. Returns 0 on success, -1 on
+ * failure.
+ */
+static int fast_forward_to(struct tree *head, struct tree *remote, int reset)
+{
+ struct lock_file *lock_file;
+ struct unpack_trees_options opts;
+ struct tree_desc t[2];
+
+ if (parse_tree(head) || parse_tree(remote))
+ return -1;
+
+ lock_file = xcalloc(1, sizeof(struct lock_file));
+ hold_locked_index(lock_file, 1);
+
+ refresh_cache(REFRESH_QUIET);
+
+ memset(&opts, 0, sizeof(opts));
+ opts.head_idx = 1;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+ opts.update = 1;
+ opts.merge = 1;
+ opts.reset = reset;
+ opts.fn = twoway_merge;
+ init_tree_desc(&t[0], head->buffer, head->size);
+ init_tree_desc(&t[1], remote->buffer, remote->size);
+
+ if (unpack_trees(2, 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)
+ return error(_("Could not parse object '%s'."), sha1_to_hex(head));
+
+ remote_tree = parse_tree_indirect(remote);
+ if (!remote_tree)
+ return error(_("Could not parse object '%s'."), sha1_to_hex(remote));
+
+ read_cache_unmerged();
+
+ if (fast_forward_to(head_tree, head_tree, 1))
+ return -1;
+
+ if (write_cache_as_tree(index, 0, NULL))
+ return -1;
+
+ index_tree = parse_tree_indirect(index);
+ if (!index_tree)
+ return error(_("Could not parse object '%s'."), sha1_to_hex(index));
+
+ 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);
+ return -1;
+ }
+
+ if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
+ die(_("unable to write new index file"));
+
+ remove_branch_state();
+
+ return 0;
+}
+
+/**
+ * Resets rerere's merge resolution metadata.
+ */
+static void am_rerere_clear(void)
+{
+ struct string_list merge_rr = STRING_LIST_INIT_DUP;
+ int fd = setup_rerere(&merge_rr, 0);
+
+ if (fd < 0)
+ return;
+
+ rerere_clear(&merge_rr);
+ string_list_clear(&merge_rr, 1);
+}
+
+/**
+ * Resume the current am session by skipping the current patch.
+ */
+static void am_skip(struct am_state *state)
+{
+ unsigned char head[GIT_SHA1_RAWSZ];
+
+ am_rerere_clear();
+
+ if (get_sha1("HEAD", head))
+ hashcpy(head, EMPTY_TREE_SHA1_BIN);
+
+ if (clean_index(head, head))
+ die(_("failed to clean index"));
+
+ am_next(state);
+ am_run(state, 0);
+}
+
+/**
+ * Returns true if it is safe to reset HEAD to the ORIG_HEAD, false otherwise.
+ *
+ * It is not safe to reset HEAD when:
+ * 1. git-am previously failed because the index was dirty.
+ * 2. HEAD has moved since git-am previously failed.
+ */
+static int safe_to_abort(const struct am_state *state)
+{
+ struct strbuf sb = STRBUF_INIT;
+ unsigned char abort_safety[GIT_SHA1_RAWSZ], head[GIT_SHA1_RAWSZ];
+
+ if (file_exists(am_path(state, "dirtyindex")))
+ return 0;
+
+ if (read_state_file(&sb, state, "abort-safety", 1) > 0) {
+ if (get_sha1_hex(sb.buf, abort_safety))
+ die(_("could not parse %s"), am_path(state, "abort_safety"));
+ } else
+ hashclr(abort_safety);
+
+ if (get_sha1("HEAD", head))
+ hashclr(head);
+
+ if (!hashcmp(head, abort_safety))
+ return 1;
+
+ error(_("You seem to have moved HEAD since the last 'am' failure.\n"
+ "Not rewinding to ORIG_HEAD"));
+
+ return 0;
+}
+
+/**
+ * Aborts the current am session if it is safe to do so.
+ */
+static void am_abort(struct am_state *state)
+{
+ unsigned char curr_head[GIT_SHA1_RAWSZ], orig_head[GIT_SHA1_RAWSZ];
+ int has_curr_head, has_orig_head;
+ char *curr_branch;
+
+ if (!safe_to_abort(state)) {
+ am_destroy(state);
+ return;
+ }
+
+ am_rerere_clear();
+
+ curr_branch = resolve_refdup("HEAD", 0, curr_head, NULL);
+ has_curr_head = !is_null_sha1(curr_head);
+ if (!has_curr_head)
+ hashcpy(curr_head, EMPTY_TREE_SHA1_BIN);
+
+ has_orig_head = !get_sha1("ORIG_HEAD", orig_head);
+ if (!has_orig_head)
+ hashcpy(orig_head, EMPTY_TREE_SHA1_BIN);
+
+ clean_index(curr_head, orig_head);
+
+ if (has_orig_head)
+ update_ref("am --abort", "HEAD", orig_head,
+ has_curr_head ? curr_head : NULL, 0,
+ UPDATE_REFS_DIE_ON_ERR);
+ else if (curr_branch)
+ delete_ref(curr_branch, NULL, REF_NODEREF);
+
+ free(curr_branch);
+ am_destroy(state);
+}
+
+/**
+ * parse_options() callback that validates and sets opt->value to the
+ * PATCH_FORMAT_* enum value corresponding to `arg`.
+ */
+static int parse_opt_patchformat(const struct option *opt, const char *arg, int unset)
+{
+ int *opt_value = opt->value;
+
+ if (!strcmp(arg, "mbox"))
+ *opt_value = PATCH_FORMAT_MBOX;
+ else if (!strcmp(arg, "stgit"))
+ *opt_value = PATCH_FORMAT_STGIT;
+ else if (!strcmp(arg, "stgit-series"))
+ *opt_value = PATCH_FORMAT_STGIT_SERIES;
+ else if (!strcmp(arg, "hg"))
+ *opt_value = PATCH_FORMAT_HG;
+ else
+ return error(_("Invalid value for --patch-format: %s"), arg);
+ return 0;
+}
+
+enum resume_mode {
+ RESUME_FALSE = 0,
+ RESUME_APPLY,
+ RESUME_RESOLVED,
+ RESUME_SKIP,
+ RESUME_ABORT
+};
+
+int cmd_am(int argc, const char **argv, const char *prefix)
+{
+ struct am_state state;
+ int binary = -1;
+ int keep_cr = -1;
+ int patch_format = PATCH_FORMAT_UNKNOWN;
+ enum resume_mode resume = RESUME_FALSE;
+
+ const char * const usage[] = {
+ N_("git am [options] [(<mbox>|<Maildir>)...]"),
+ N_("git am [options] (--continue | --skip | --abort)"),
+ NULL
+ };
+
+ struct option options[] = {
+ OPT_BOOL('i', "interactive", &state.interactive,
+ N_("run interactively")),
+ OPT_HIDDEN_BOOL('b', "binary", &binary,
+ 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")),
+ OPT_BOOL('s', "signoff", &state.signoff,
+ N_("add a Signed-off-by line to the commit message")),
+ OPT_BOOL('u', "utf8", &state.utf8,
+ N_("recode into utf8 (default)")),
+ OPT_SET_INT('k', "keep", &state.keep,
+ N_("pass -k flag to git-mailinfo"), KEEP_TRUE),
+ OPT_SET_INT(0, "keep-non-patch", &state.keep,
+ N_("pass -b flag to git-mailinfo"), KEEP_NON_PATCH),
+ OPT_BOOL('m', "message-id", &state.message_id,
+ N_("pass -m flag to git-mailinfo")),
+ { OPTION_SET_INT, 0, "keep-cr", &keep_cr, NULL,
+ N_("pass --keep-cr flag to git-mailsplit for mbox format"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1},
+ { OPTION_SET_INT, 0, "no-keep-cr", &keep_cr, NULL,
+ N_("do not pass --keep-cr flag to git-mailsplit independent of am.keepcr"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},
+ OPT_BOOL('c', "scissors", &state.scissors,
+ N_("strip everything before a scissors line")),
+ OPT_PASSTHRU_ARGV(0, "whitespace", &state.git_apply_opts, N_("action"),
+ N_("pass it through git-apply"),
+ 0),
+ OPT_PASSTHRU_ARGV(0, "ignore-space-change", &state.git_apply_opts, NULL,
+ N_("pass it through git-apply"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU_ARGV(0, "ignore-whitespace", &state.git_apply_opts, NULL,
+ N_("pass it through git-apply"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU_ARGV(0, "directory", &state.git_apply_opts, N_("root"),
+ N_("pass it through git-apply"),
+ 0),
+ OPT_PASSTHRU_ARGV(0, "exclude", &state.git_apply_opts, N_("path"),
+ N_("pass it through git-apply"),
+ 0),
+ OPT_PASSTHRU_ARGV(0, "include", &state.git_apply_opts, N_("path"),
+ N_("pass it through git-apply"),
+ 0),
+ OPT_PASSTHRU_ARGV('C', NULL, &state.git_apply_opts, N_("n"),
+ N_("pass it through git-apply"),
+ 0),
+ OPT_PASSTHRU_ARGV('p', NULL, &state.git_apply_opts, N_("num"),
+ N_("pass it through git-apply"),
+ 0),
+ OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
+ N_("format the patch(es) are in"),
+ parse_opt_patchformat),
+ OPT_PASSTHRU_ARGV(0, "reject", &state.git_apply_opts, NULL,
+ N_("pass it through git-apply"),
+ PARSE_OPT_NOARG),
+ OPT_STRING(0, "resolvemsg", &state.resolvemsg, NULL,
+ N_("override error message when patch failure occurs")),
+ OPT_CMDMODE(0, "continue", &resume,
+ N_("continue applying patches after resolving a conflict"),
+ RESUME_RESOLVED),
+ OPT_CMDMODE('r', "resolved", &resume,
+ N_("synonyms for --continue"),
+ RESUME_RESOLVED),
+ OPT_CMDMODE(0, "skip", &resume,
+ N_("skip the current patch"),
+ RESUME_SKIP),
+ OPT_CMDMODE(0, "abort", &resume,
+ N_("restore the original branch and abort the patching operation."),
+ RESUME_ABORT),
+ OPT_BOOL(0, "committer-date-is-author-date",
+ &state.committer_date_is_author_date,
+ N_("lie about committer date")),
+ OPT_BOOL(0, "ignore-date", &state.ignore_date,
+ N_("use current timestamp for author date")),
+ OPT_RERERE_AUTOUPDATE(&state.allow_rerere_autoupdate),
+ { OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
+ N_("GPG-sign commits"),
+ PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+ OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
+ N_("(internal use for git-rebase)")),
+ OPT_END()
+ };
+
+ git_config(git_default_config, NULL);
+
+ am_state_init(&state, git_path("rebase-apply"));
+
+ argc = parse_options(argc, argv, prefix, options, usage, 0);
+
+ if (binary >= 0)
+ fprintf_ln(stderr, _("The -b/--binary option has been a no-op for long time, and\n"
+ "it will be removed. Please do not use it anymore."));
+
+ /* Ensure a valid committer ident can be constructed */
+ git_committer_info(IDENT_STRICT);
+
+ if (read_index_preload(&the_index, NULL) < 0)
+ die(_("failed to read the index"));
+
+ if (am_in_progress(&state)) {
+ /*
+ * Catch user error to feed us patches when there is a session
+ * in progress:
+ *
+ * 1. mbox path(s) are provided on the command-line.
+ * 2. stdin is not a tty: the user is trying to feed us a patch
+ * from standard input. This is somewhat unreliable -- stdin
+ * could be /dev/null for example and the caller did not
+ * intend to feed us a patch but wanted to continue
+ * unattended.
+ */
+ if (argc || (resume == RESUME_FALSE && !isatty(0)))
+ die(_("previous rebase directory %s still exists but mbox given."),
+ state.dir);
+
+ if (resume == RESUME_FALSE)
+ resume = RESUME_APPLY;
+
+ am_load(&state);
+ } else {
+ struct argv_array paths = ARGV_ARRAY_INIT;
+ int i;
+
+ /*
+ * Handle stray state directory in the independent-run case. In
+ * the --rebasing case, it is up to the caller to take care of
+ * stray directories.
+ */
+ if (file_exists(state.dir) && !state.rebasing) {
+ if (resume == RESUME_ABORT) {
+ am_destroy(&state);
+ am_state_release(&state);
+ return 0;
+ }
+
+ die(_("Stray %s directory found.\n"
+ "Use \"git am --abort\" to remove it."),
+ state.dir);
+ }
+
+ if (resume)
+ die(_("Resolve operation not in progress, we are not resuming."));
+
+ for (i = 0; i < argc; i++) {
+ if (is_absolute_path(argv[i]) || !prefix)
+ argv_array_push(&paths, argv[i]);
+ else
+ argv_array_push(&paths, mkpath("%s/%s", prefix, argv[i]));
+ }
+
+ am_setup(&state, patch_format, paths.argv, keep_cr);
+
+ argv_array_clear(&paths);
+ }
+
+ switch (resume) {
+ case RESUME_FALSE:
+ am_run(&state, 0);
+ break;
+ case RESUME_APPLY:
+ am_run(&state, 1);
+ break;
+ case RESUME_RESOLVED:
+ am_resolve(&state);
+ break;
+ case RESUME_SKIP:
+ am_skip(&state);
+ break;
+ case RESUME_ABORT:
+ am_abort(&state);
+ break;
+ default:
+ die("BUG: invalid resume value");
+ }
+
+ am_state_release(&state);
+
+ return 0;
+}
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 9ff5ca4cbe..e1403bec27 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -48,8 +48,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,
@@ -509,7 +507,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);
@@ -830,8 +828,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);
@@ -896,71 +893,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,
@@ -1168,14 +1100,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);
}
@@ -1239,8 +1171,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 5864ad15e1..578da85254 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -161,35 +161,78 @@ static char *get_repo_path(const char *repo, int *is_bundle)
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.
+ */
+ 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.
*/
- start = end;
- while (repo < start && !is_dir_sep(start[-1]) && start[-1] != ':')
- start--;
+ 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/fetch.c b/builtin/fetch.c
index df16d0a56e..d3a08545ad 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -988,17 +988,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/gc.c b/builtin/gc.c
index 4957c39032..bcc75d9bfc 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -85,7 +85,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);
}
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/pull.c b/builtin/pull.c
index 722a83c51b..b7bc1fff5e 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -823,10 +823,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/worktree.c b/builtin/worktree.c
index 6a264ee749..430b51e7a7 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, 1, "%s\n", sha1_to_hex(rev));
+ write_file(sb.buf, 1, "0000000000000000000000000000000000000000\n");
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
write_file(sb.buf, 1, "../..\n");
- 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)
diff --git a/cache-tree.c b/cache-tree.c
index 32772b9564..feace8bd90 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -592,7 +592,7 @@ static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *pat
return it;
}
-int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix)
+int write_index_as_tree(unsigned char *sha1, struct index_state *index_state, const char *index_path, int flags, const char *prefix)
{
int entries, was_valid, newfd;
struct lock_file *lock_file;
@@ -603,23 +603,23 @@ int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix)
*/
lock_file = xcalloc(1, sizeof(struct lock_file));
- newfd = hold_locked_index(lock_file, 1);
+ newfd = hold_lock_file_for_update(lock_file, index_path, LOCK_DIE_ON_ERROR);
- entries = read_cache();
+ entries = read_index_from(index_state, index_path);
if (entries < 0)
return WRITE_TREE_UNREADABLE_INDEX;
if (flags & WRITE_TREE_IGNORE_CACHE_TREE)
- cache_tree_free(&(active_cache_tree));
+ cache_tree_free(&index_state->cache_tree);
- if (!active_cache_tree)
- active_cache_tree = cache_tree();
+ if (!index_state->cache_tree)
+ index_state->cache_tree = cache_tree();
- was_valid = cache_tree_fully_valid(active_cache_tree);
+ was_valid = cache_tree_fully_valid(index_state->cache_tree);
if (!was_valid) {
- if (cache_tree_update(&the_index, flags) < 0)
+ if (cache_tree_update(index_state, flags) < 0)
return WRITE_TREE_UNMERGED_INDEX;
if (0 <= newfd) {
- if (!write_locked_index(&the_index, lock_file, COMMIT_LOCK))
+ if (!write_locked_index(index_state, lock_file, COMMIT_LOCK))
newfd = -1;
}
/* Not being able to write is fine -- we are only interested
@@ -631,14 +631,14 @@ int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix)
}
if (prefix) {
- struct cache_tree *subtree =
- cache_tree_find(active_cache_tree, prefix);
+ struct cache_tree *subtree;
+ subtree = cache_tree_find(index_state->cache_tree, prefix);
if (!subtree)
return WRITE_TREE_PREFIX_ERROR;
hashcpy(sha1, subtree->sha1);
}
else
- hashcpy(sha1, active_cache_tree->sha1);
+ hashcpy(sha1, index_state->cache_tree->sha1);
if (0 <= newfd)
rollback_lock_file(lock_file);
@@ -646,6 +646,11 @@ int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix)
return 0;
}
+int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix)
+{
+ return write_index_as_tree(sha1, &the_index, get_index_file(), flags, prefix);
+}
+
static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree)
{
struct tree_desc desc;
diff --git a/cache-tree.h b/cache-tree.h
index aa7b3e4a0a..41c574663a 100644
--- a/cache-tree.h
+++ b/cache-tree.h
@@ -46,6 +46,7 @@ int update_main_cache_tree(int);
#define WRITE_TREE_UNMERGED_INDEX (-2)
#define WRITE_TREE_PREFIX_ERROR (-3)
+int write_index_as_tree(unsigned char *sha1, struct index_state *index_state, const char *index_path, int flags, const char *prefix);
int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix);
void prime_cache_tree(struct index_state *, struct tree *);
diff --git a/cache.h b/cache.h
index 402521e34d..4e25271e59 100644
--- a/cache.h
+++ b/cache.h
@@ -982,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);
diff --git a/git-am.sh b/contrib/examples/git-am.sh
index 3b77028123..3b77028123 100755
--- a/git-am.sh
+++ b/contrib/examples/git-am.sh
diff --git a/contrib/examples/git-pull.sh b/contrib/examples/git-pull.sh
index a814bf61aa..26c5e9ff61 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/dir.c b/dir.c
index 1ac8d5dd28..c00c7e2b73 100644
--- a/dir.c
+++ b/dir.c
@@ -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))) {
/*
@@ -1880,7 +1879,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 +1930,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;
diff --git a/fast-import.c b/fast-import.c
index ad8848bef1..6c7c3c9b66 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -925,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/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 c6d391f864..392da79029 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -717,10 +717,12 @@ extern void *xrealloc(void *ptr, size_t size);
extern void *xcalloc(size_t nmemb, size_t size);
extern void *xmmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
extern void *xmmap_gently(void *start, size_t length, int prot, int flags, int fd, off_t offset);
+extern int xopen(const char *path, int flags, ...);
extern ssize_t xread(int fd, void *buf, size_t len);
extern ssize_t xwrite(int fd, const void *buf, size_t len);
extern ssize_t xpread(int fd, void *buf, size_t len, off_t offset);
extern int xdup(int fd);
+extern FILE *xfopen(const char *path, const char *mode);
extern FILE *xfdopen(int fd, const char *mode);
extern int xmkstemp(char *template);
extern int xmkstemp_mode(char *template, int mode);
diff --git a/git.c b/git.c
index 55c327c7b3..5feba410ca 100644
--- a/git.c
+++ b/git.c
@@ -370,6 +370,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
static struct cmd_struct commands[] = {
{ "add", cmd_add, RUN_SETUP | NEED_WORK_TREE },
+ { "am", cmd_am, RUN_SETUP | NEED_WORK_TREE },
{ "annotate", cmd_annotate, RUN_SETUP },
{ "apply", cmd_apply, RUN_SETUP_GENTLY },
{ "archive", cmd_archive },
diff --git a/http.c b/http.c
index e9c6fdd835..8cd59f7f36 100644
--- a/http.c
+++ b/http.c
@@ -1318,7 +1318,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 +1405,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 +1517,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 +1782,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/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), \
diff --git a/refs.c b/refs.c
index 84b1aff842..835801905b 100644
--- a/refs.c
+++ b/refs.c
@@ -4379,17 +4379,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;
}
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/sha1_file.c b/sha1_file.c
index 3400b8be4c..ea78ba7cf0 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -2941,11 +2941,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;
@@ -3118,7 +3115,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)
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/t1400-update-ref.sh b/t/t1400-update-ref.sh
index 9d21c1927e..97406fa4b1 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -174,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)" \
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 3eb4f102e5..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
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/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/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/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/t4150-am.sh b/t/t4150-am.sh
index e9b6f8158a..dd627c42d3 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -551,6 +551,25 @@ test_expect_success 'am -3 -p0 can read --no-prefix patch' '
git diff --exit-code lorem
'
+test_expect_success 'am with config am.threeWay falls back to 3-way merge' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
+ git checkout -b lorem4 base3way &&
+ test_config am.threeWay 1 &&
+ git am lorem-move.patch &&
+ test_path_is_missing .git/rebase-apply &&
+ git diff --exit-code lorem
+'
+
+test_expect_success 'am with config am.threeWay overridden by --no-3way' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
+ git checkout -b lorem5 base3way &&
+ test_config am.threeWay 1 &&
+ test_must_fail git am --no-3way lorem-move.patch &&
+ test_path_is_dir .git/rebase-apply
+'
+
test_expect_success 'am can rename a file' '
grep "^rename from" rename.patch &&
rm -fr .git/rebase-apply &&
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/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/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/t7063-status-untracked-cache.sh b/t/t7063-status-untracked-cache.sh
index bd4806c12a..ff23f4e94e 100755
--- a/t/t7063-status-untracked-cache.sh
+++ b/t/t7063-status-untracked-cache.sh
@@ -354,4 +354,123 @@ 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: 1
+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 --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 files, some of which are gitignored' '
+ 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 &&
+?? .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 &&
+?? .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_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/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/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 7bb446a4af..bb09b1ed27 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -1162,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 {
diff --git a/wrapper.c b/wrapper.c
index ff49807948..e451463431 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -189,6 +189,41 @@ void *xcalloc(size_t nmemb, size_t size)
# endif
#endif
+/**
+ * xopen() is the same as open(), but it die()s if the open() fails.
+ */
+int xopen(const char *path, int oflag, ...)
+{
+ mode_t mode = 0;
+ va_list ap;
+
+ /*
+ * va_arg() will have undefined behavior if the specified type is not
+ * compatible with the argument type. Since integers are promoted to
+ * ints, we fetch the next argument as an int, and then cast it to a
+ * mode_t to avoid undefined behavior.
+ */
+ va_start(ap, oflag);
+ if (oflag & O_CREAT)
+ mode = va_arg(ap, int);
+ va_end(ap);
+
+ for (;;) {
+ int fd = open(path, oflag, mode);
+ if (fd >= 0)
+ return fd;
+ if (errno == EINTR)
+ continue;
+
+ if ((oflag & O_RDWR) == O_RDWR)
+ die_errno(_("could not open '%s' for reading and writing"), path);
+ else if ((oflag & O_WRONLY) == O_WRONLY)
+ die_errno(_("could not open '%s' for writing"), path);
+ else
+ die_errno(_("could not open '%s' for reading"), path);
+ }
+}
+
/*
* xread() is the same a read(), but it automatically restarts read()
* operations with a recoverable error (EAGAIN and EINTR). xread()
@@ -311,6 +346,27 @@ int xdup(int fd)
return ret;
}
+/**
+ * xfopen() is the same as fopen(), but it die()s if the fopen() fails.
+ */
+FILE *xfopen(const char *path, const char *mode)
+{
+ for (;;) {
+ FILE *fp = fopen(path, mode);
+ if (fp)
+ return fp;
+ if (errno == EINTR)
+ continue;
+
+ if (*mode && mode[1] == '+')
+ die_errno(_("could not open '%s' for reading and writing"), path);
+ else if (*mode == 'w' || *mode == 'a')
+ die_errno(_("could not open '%s' for writing"), path);
+ else
+ die_errno(_("could not open '%s' for reading"), path);
+ }
+}
+
FILE *xfdopen(int fd, const char *mode)
{
FILE *stream = fdopen(fd, mode);