Merge branch 'jk/long-error-messages'
authorJunio C Hamano <gitster@pobox.com>
Tue, 25 Aug 2015 21:57:06 +0000 (14:57 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 25 Aug 2015 21:57:06 +0000 (14:57 -0700)
The codepath to produce error messages had a hard-coded limit to
the size of the message, primarily to avoid memory allocation while
calling die().

* jk/long-error-messages:
vreportf: avoid intermediate buffer
vreportf: report to arbitrary filehandles

208 files changed:
Documentation/RelNotes/2.4.8.txt [new file with mode: 0644]
Documentation/RelNotes/2.5.1.txt [new file with mode: 0644]
Documentation/RelNotes/2.6.0.txt [new file with mode: 0644]
Documentation/config.txt
Documentation/git-am.txt
Documentation/git-bisect.txt
Documentation/git-cat-file.txt
Documentation/git-check-ref-format.txt
Documentation/git-checkout.txt
Documentation/git-fast-import.txt
Documentation/git-fsck.txt
Documentation/git-log.txt
Documentation/git-rebase.txt
Documentation/git-reflog.txt
Documentation/git-rev-parse.txt
Documentation/git-send-email.txt
Documentation/git-tag.txt
Documentation/git-tools.txt
Documentation/git-update-ref.txt
Documentation/git-verify-commit.txt
Documentation/git-verify-tag.txt
Documentation/git-worktree.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/gitrepository-layout.txt
Documentation/i18n.txt
Documentation/rev-list-options.txt
Documentation/technical/api-argv-array.txt
Documentation/technical/api-parse-options.txt
Documentation/technical/index-format.txt
GIT-VERSION-GEN
Makefile
RelNotes
advice.c
advice.h
archive.c
argv-array.c
argv-array.h
attr.c
bisect.c
bisect.h
branch.c
branch.h
builtin.h
builtin/add.c
builtin/am.c [new file with mode: 0644]
builtin/blame.c
builtin/branch.c
builtin/cat-file.c
builtin/checkout.c
builtin/clean.c
builtin/clone.c
builtin/commit.c
builtin/fast-export.c
builtin/fetch.c
builtin/fmt-merge-msg.c
builtin/for-each-ref.c
builtin/fsck.c
builtin/gc.c
builtin/index-pack.c
builtin/init-db.c
builtin/log.c
builtin/merge.c
builtin/pack-objects.c
builtin/pull.c [new file with mode: 0644]
builtin/receive-pack.c
builtin/reflog.c
builtin/remote.c
builtin/repack.c
builtin/replace.c
builtin/reset.c
builtin/rev-parse.c
builtin/send-pack.c
builtin/shortlog.c
builtin/show-branch.c
builtin/tag.c
builtin/unpack-objects.c
builtin/update-ref.c
builtin/verify-commit.c
builtin/verify-tag.c
builtin/worktree.c
cache-tree.c
cache-tree.h
cache.h
commit-slab.h
commit.c
commit.h
contrib/completion/git-completion.bash
contrib/completion/git-prompt.sh
contrib/examples/builtin-fetch--tool.c
contrib/examples/git-am.sh [new file with mode: 0755]
contrib/examples/git-pull.sh [new file with mode: 0755]
contrib/subtree/git-subtree.sh
contrib/subtree/t/t7900-subtree.sh
date.c
diff.c
diff.h
dir.c
environment.c
fast-import.c
fetch-pack.c
fsck.c
fsck.h
gettext.c
git-am.sh [deleted file]
git-bisect.sh
git-compat-util.h
git-pull.sh [deleted file]
git-rebase--interactive.sh
git-send-email.perl
git-stash.sh
git.c
gpg-interface.c
gpg-interface.h
http-backend.c
http.c
log-tree.c
notes-merge.c
parse-options-cb.c
parse-options.c
parse-options.h
path.c
perl/Git.pm
pkt-line.c
pretty.c
ref-filter.c [new file with mode: 0644]
ref-filter.h [new file with mode: 0644]
reflog-walk.c
reflog-walk.h
refs.c
refs.h
remote-testsvn.c
rerere.c
revision.c
revision.h
run-command.c
run-command.h
sequencer.c
setup.c
sha1_file.c
sha1_name.c
shallow.c
strbuf.c
strbuf.h
submodule.c
t/lib-rebase.sh
t/perf/p7300-clean.sh [new file with mode: 0755]
t/t0002-gitfile.sh
t/t0040-parse-options.sh
t/t0090-cache-tree.sh
t/t1006-cat-file.sh
t/t1090-sparse-checkout-scope.sh [new file with mode: 0755]
t/t1400-update-ref.sh
t/t1402-check-ref-format.sh
t/t1410-reflog.sh
t/t1411-reflog-show.sh
t/t1450-fsck.sh
t/t1502-rev-parse-parseopt.sh
t/t1503-rev-parse-verify.sh
t/t1509-root-worktree.sh
t/t2025-worktree-add.sh
t/t3200-branch.sh
t/t3210-pack-refs.sh
t/t3404-rebase-interactive.sh
t/t3415-rebase-autosquash.sh
t/t3418-rebase-continue.sh
t/t3901-i18n-patch.sh
t/t3903-stash.sh
t/t4018-diff-funcname.sh
t/t4018/fountain-scene [new file with mode: 0644]
t/t4150-am.sh
t/t4151-am-abort.sh
t/t4202-log.sh
t/t5302-pack-index.sh
t/t5312-prune-corruption.sh
t/t5504-fetch-receive-strict.sh
t/t5511-refspec.sh
t/t5512-ls-remote.sh
t/t5520-pull.sh
t/t5601-clone.sh
t/t5603-clone-dirname.sh [new file with mode: 0755]
t/t5700-clone-reference.sh
t/t6030-bisect-porcelain.sh
t/t6300-for-each-ref.sh
t/t6501-freshen-objects.sh
t/t7004-tag.sh
t/t7030-verify-tag.sh [new file with mode: 0755]
t/t7063-status-untracked-cache.sh
t/t7300-clean.sh
t/t7509-commit.sh
t/t7510-signed-commit.sh
t/t7512-status-help.sh
t/t9000-addresses.sh [new file with mode: 0755]
t/t9000/test.pl [new file with mode: 0755]
t/t9001-send-email.sh
t/t9300-fast-import.sh
t/t9903-bash-prompt.sh
t/test-lib.sh
test-date.c
test-parse-options.c
test-revision-walking.c
trace.c
trace.h
transport-helper.c
unpack-trees.c
userdiff.c
wrapper.c
wt-status.c
diff --git a/Documentation/RelNotes/2.4.8.txt b/Documentation/RelNotes/2.4.8.txt
new file mode 100644 (file)
index 0000000..ad946b2
--- /dev/null
@@ -0,0 +1,21 @@
+Git v2.4.8 Release Notes
+========================
+
+Fixes since v2.4.7
+------------------
+
+ * Abandoning an already applied change in "git rebase -i" with
+   "--continue" left CHERRY_PICK_HEAD and confused later steps.
+
+ * Various fixes around "git am" that applies a patch to a history
+   that is not there yet.
+
+ * "git for-each-ref" reported "missing object" for 0{40} when it
+   encounters a broken ref.  The lack of object whose name is 0{40} is
+   not the problem; the ref being broken is.
+
+ * "git commit --cleanup=scissors" was not careful enough to protect
+   against getting fooled by a line that looked like scissors.
+
+Also contains typofixes, documentation updates and trivial code
+clean-ups.
diff --git a/Documentation/RelNotes/2.5.1.txt b/Documentation/RelNotes/2.5.1.txt
new file mode 100644 (file)
index 0000000..3c46851
--- /dev/null
@@ -0,0 +1,49 @@
+Git v2.5.1 Release Notes
+========================
+
+Fixes since v2.5
+----------------
+
+ * Running an aliased command from a subdirectory when the .git thing
+   in the working tree is a gitfile pointing elsewhere did not work.
+
+ * Often a fast-import stream builds a new commit on top of the
+   previous commit it built, and it often unconditionally emits a
+   "from" command to specify the first parent, which can be omitted in
+   such a case.  This caused fast-import to forget the tree of the
+   previous commit and then re-read it from scratch, which was
+   inefficient.  Optimize for this common case.
+
+ * The "rev-parse --parseopt" mode parsed the option specification
+   and the argument hint in a strange way to allow '=' and other
+   special characters in the option name while forbidding them from
+   the argument hint.  This made it impossible to define an option
+   like "--pair <key>=<value>" with "pair=key=value" specification,
+   which instead would have defined a "--pair=key <value>" option.
+
+ * A "rebase" replays changes of the local branch on top of something
+   else, as such they are placed in stage #3 and referred to as
+   "theirs", while the changes in the new base, typically a foreign
+   work, are placed in stage #2 and referred to as "ours".  Clarify
+   the "checkout --ours/--theirs".
+
+ * An experimental "untracked cache" feature used uname(2) in a
+   slightly unportable way.
+
+ * "sparse checkout" misbehaved for a path that is excluded from the
+   checkout when switching between branches that differ at the path.
+
+ * The low-level "git send-pack" did not honor 'user.signingkey'
+   configuration variable when sending a signed-push.
+
+ * An attempt to delete a ref by pushing into a repository whose HEAD
+   symbolic reference points at an unborn branch that cannot be
+   created due to ref D/F conflict (e.g. refs/heads/a/b exists, HEAD
+   points at refs/heads/a) failed.
+
+ * "git subtree" (in contrib/) depended on "git log" output to be
+   stable, which was a no-no.  Apply a workaround to force a
+   particular date format.
+
+Also contains typofixes, documentation updates and trivial code
+clean-ups.
diff --git a/Documentation/RelNotes/2.6.0.txt b/Documentation/RelNotes/2.6.0.txt
new file mode 100644 (file)
index 0000000..f938768
--- /dev/null
@@ -0,0 +1,223 @@
+Git 2.6 Release Notes
+=====================
+
+Updates since v2.5
+------------------
+
+UI, Workflows & Features
+
+ * An asterisk as a substring (as opposed to the entirety) of a path
+   component for both side of a refspec, e.g.
+   "refs/heads/o*:refs/remotes/heads/i*", is now allowed.
+
+ * New userdiff pattern definition for fountain screenwriting markup
+   format has been added.
+
+ * "git log" and friends learned a new "--date=format:..." option to
+   format timestamps using system's strftime(3).
+
+ * "git fast-import" learned to respond to the get-mark command via
+   its cat-blob-fd interface.
+
+ * "git rebase -i" learned "drop commit-object-name subject" command
+   as another way to skip replaying of a commit.
+
+ * A new configuration variable can enable "--follow" automatically
+   when "git log" is run with one pathspec argument.
+
+ * "git status" learned to show a more detailed information regarding
+   the "rebase -i" session in progress.
+
+ * "git cat-file" learned "--batch-all-objects" option to enumerate all
+   available objects in the repository more quickly than "rev-list
+   --all --objects" (the output includes unreachable objects, though).
+
+ * "git fsck" learned to ignore errors on a set of known-to-be-bad
+   objects, and also allows the warning levels of various kinds of
+   non-critical breakages to be tweaked.
+
+ * "git rebase -i"'s list of todo is made configurable.
+
+ * "git send-email" now performs alias-expansion on names that are
+   given via --cccmd, etc.
+
+ * An environment variable GIT_REPLACE_REF_BASE tells Git to look into
+   refs hierarchy other than refs/replace/ for the object replacement
+   data.
+
+ * Allow untracked cache (experimental) to be used when sparse
+   checkout (experimental) is also in use.
+
+ * "git pull --rebase" has been taught to pay attention to
+   rebase.autostash configuration.
+
+ * The command-line completion script (in contrib/) has been updated.
+
+ * A negative !ref entry in multi-value transfer.hideRefs
+   configuration can be used to say "don't hide this one".
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * In preparation for allowing different "backends" to store the refs
+   in a way different from the traditional "one ref per file in
+   $GIT_DIR or in a $GIT_DIR/packed-refs file" filesystem storage,
+   direct filesystem access to ref-like things like CHERRY_PICK_HEAD
+   from scripts and programs has been reduced.
+
+ * Computation of untracked status indicator by bash prompt
+   script (in contrib/) has been optimized.
+
+ * Memory use reduction when commit-slab facility is used to annotate
+   sparsely (which is not recommended in the first place).
+
+ * Clean up refs API and make "git clone" less intimate with the
+   implementation detail.
+
+ * "git pull" was reimplemented in C.
+
+ * The packet tracing machinery allows to capture an incoming pack
+   data to a file for debugging.
+
+ * Move machinery to parse human-readable scaled numbers like 1k, 4M,
+   and 2G as an option parameter's value from pack-objects to
+   parse-options API, to make it available to other codepaths.
+
+ * "git verify-tag" and "git verify-commit" have been taught to share
+   more code, and then learned to optionally show the verification
+   message from the underlying GPG implementation.
+
+ * Various enhancements around "git am" reading patches generated by
+   foreign SCM have been made.
+
+ * Ref listing by "git branch -l" and "git tag -l" commands has
+   started to be rebuilt, based on the for-each-ref machinery.
+
+ * The code to perform multi-tree merges has been taught to repopulate
+   the cache-tree upon a successful merge into the index, so that
+   subsequent "diff-index --cached" (hence "status") and "write-tree"
+   (hence "commit") will go faster.
+
+   The same logic in "git checkout" may now be removed, but that is a
+   separate issue.
+
+ * Tests that assume how reflogs are represented on the filesystem too
+   much have been corrected.
+
+ * "git am" has been rewritten in "C".
+
+ * git_path() and mkpath() are handy helper functions but it is easy
+   to misuse, as the callers need to be careful to keep the number of
+   active results below 4.  Their uses have been reduced.
+
+
+Also contains various documentation updates and code clean-ups.
+
+
+Fixes since v2.5
+----------------
+
+Unless otherwise noted, all the fixes since v2.5 in the maintenance
+track are contained in this release (see the maintenance releases'
+notes for details).
+
+ * "git subtree" (in contrib/) depended on "git log" output to be
+   stable, which was a no-no.  Apply a workaround to force a
+   particular date format.
+   (merge e7aac44 da/subtree-date-confusion later to maint).
+
+ * An attempt to delete a ref by pushing into a repository whose HEAD
+   symbolic reference points at an unborn branch that cannot be
+   created due to ref D/F conflict (e.g. refs/heads/a/b exists, HEAD
+   points at refs/heads/a) failed.
+   (merge b112b14 jx/do-not-crash-receive-pack-wo-head later to maint).
+
+ * The low-level "git send-pack" did not honor 'user.signingkey'
+   configuration variable when sending a signed-push.
+   (merge d830d39 db/send-pack-user-signingkey later to maint).
+
+ * "sparse checkout" misbehaved for a path that is excluded from the
+   checkout when switching between branches that differ at the path.
+   (merge 7d78241 as/sparse-checkout-removal later to maint).
+
+ * An experimental "untracked cache" feature used uname(2) in a
+   slightly unportable way.
+   (merge 100e433 cb/uname-in-untracked later to maint).
+
+ * A "rebase" replays changes of the local branch on top of something
+   else, as such they are placed in stage #3 and referred to as
+   "theirs", while the changes in the new base, typically a foreign
+   work, are placed in stage #2 and referred to as "ours".  Clarify
+   the "checkout --ours/--theirs".
+   (merge f303016 se/doc-checkout-ours-theirs later to maint).
+
+ * The "rev-parse --parseopt" mode parsed the option specification
+   and the argument hint in a strange way to allow '=' and other
+   special characters in the option name while forbidding them from
+   the argument hint.  This made it impossible to define an option
+   like "--pair <key>=<value>" with "pair=key=value" specification,
+   which instead would have defined a "--pair=key <value>" option.
+   (merge 2d893df ib/scripted-parse-opt-better-hint-string later to maint).
+
+ * Often a fast-import stream builds a new commit on top of the
+   previous commit it built, and it often unconditionally emits a
+   "from" command to specify the first parent, which can be omitted in
+   such a case.  This caused fast-import to forget the tree of the
+   previous commit and then re-read it from scratch, which was
+   inefficient.  Optimize for this common case.
+   (merge 0df3245 mh/fast-import-optimize-current-from later to maint).
+
+ * Running an aliased command from a subdirectory when the .git thing
+   in the working tree is a gitfile pointing elsewhere did not work.
+   (merge d95138e nd/export-worktree later to maint).
+
+ * "Is this subdirectory a separate repository that should not be
+   touched?" check "git clean" was inefficient.  This was replaced
+   with a more optimized check.
+   (merge 38ae878 ee/clean-remove-dirs later to maint).
+
+ * The "new-worktree-mode" hack in "checkout" that was added in
+   nd/multiple-work-trees topic has been removed by updating the
+   implementation of new "worktree add".
+   (merge 65f9b75 es/worktree-add-cleanup later to maint).
+
+ * Remove remaining cruft from  "git checkout --to", which
+   transitioned to "git worktree add".
+   (merge 114ff88 es/worktree-add later to maint).
+
+ * An off-by-one error made "git remote" to mishandle a remote with a
+   single letter nickname.
+   (merge bc598c3 mh/get-remote-group-fix later to maint).
+
+ * "git clone $URL", when cloning from a site whose sole purpose is to
+   host a single repository (hence, no path after <scheme>://<site>/),
+   tried to use the site name as the new repository name, but did not
+   remove username or password when <site> part was of the form
+   <user>@<pass>:<host>.  The code is taught to redact these.
+   (merge adef956 ps/guess-repo-name-at-root later to maint).
+
+ * Running tests with the "-x" option to make them verbose had some
+   unpleasant interactions with other features of the test suite.
+   (merge 9b5fe78 jk/test-with-x later to maint).
+
+ * t1509 test that requires a dedicated VM environment had some
+   bitrot, which has been corrected.
+   (merge faacc5a ps/t1509-chroot-test-fixup later to maint).
+
+ * "git pull" in recent releases of Git has a regression in the code
+   that allows custom path to the --upload-pack=<program>.  This has
+   been corrected.
+
+   Note that this is irrelevant for 'master' with "git pull" rewritten
+   in C.
+   (merge 22d6857 mm/pull-upload-pack later to maint).
+
+ * Code cleanups and documentation updates.
+   (merge 1c601af es/doc-clean-outdated-tools later to maint).
+   (merge 3581304 kn/tag-doc-fix later to maint).
+   (merge 3a59e59 kb/i18n-doc later to maint).
+   (merge 45abdee sb/remove-unused-var-from-builtin-add later to maint).
+   (merge 14691e3 sb/parse-options-codeformat later to maint).
+   (merge 4a6ada3 ad/bisect-cleanup later to maint).
+   (merge da4c5ad ta/docfix-index-format-tech later to maint).
+   (merge ae25fd3 sb/check-return-from-read-ref later to maint).
index 43bb53c0477276d81af484cdb64855698b6a635b..75ec02e8e90a57a54a768677a272975487f664a8 100644 (file)
@@ -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'
@@ -1242,6 +1250,25 @@ filter.<driver>.smudge::
        object to a worktree file upon checkout.  See
        linkgit:gitattributes[5] for details.
 
+fsck.<msg-id>::
+       Allows overriding the message type (error, warn or ignore) of a
+       specific message ID such as `missingEmail`.
++
+For convenience, fsck prefixes the error/warning with the message ID,
+e.g.  "missingEmail: invalid author/committer line - missing email" means
+that setting `fsck.missingEmail = ignore` will hide that issue.
++
+This feature is intended to support working with legacy repositories
+which cannot be repaired without disruptive changes.
+
+fsck.skipList::
+       The path to a sorted list of object names (i.e. one SHA-1 per
+       line) that are known to be broken in a non-fatal way and should
+       be ignored. This feature is useful when an established project
+       should be accepted despite early commits containing errors that
+       can be safely ignored such as invalid committer email addresses.
+       Note: corrupt objects cannot be skipped with this setting.
+
 gc.aggressiveDepth::
        The depth parameter used in the delta compression
        algorithm used by 'git gc --aggressive'.  This defaults
@@ -1280,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>.
 
@@ -1301,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>.
 
@@ -2161,6 +2194,22 @@ rebase.autoStash::
        successful rebase might result in non-trivial conflicts.
        Defaults to false.
 
+rebase.missingCommitsCheck::
+       If set to "warn", git rebase -i will print a warning if some
+       commits are removed (e.g. a line was deleted), however the
+       rebase will still proceed. If set to "error", it will print
+       the previous warning and stop the rebase, 'git rebase
+       --edit-todo' can then be used to correct the error. If set to
+       "ignore", no checking is done.
+       To drop a commit without warning or error, use the `drop`
+       command in the todo-list.
+       Defaults to "ignore".
+
+rebase.instructionFormat
+       A format string, as specified in linkgit:git-log[1], to be used for
+       the instruction list during an interactive rebase.  The format will automatically
+       have the long commit hash prepended to the format.
+
 receive.advertiseAtomic::
        By default, git-receive-pack will advertise the atomic push
        capability to its clients. If you don't want to this capability
@@ -2197,6 +2246,28 @@ receive.fsckObjects::
        Defaults to false. If not set, the value of `transfer.fsckObjects`
        is used instead.
 
+receive.fsck.<msg-id>::
+       When `receive.fsckObjects` is set to true, errors can be switched
+       to warnings and vice versa by configuring the `receive.fsck.<msg-id>`
+       setting where the `<msg-id>` is the fsck message ID and the value
+       is one of `error`, `warn` or `ignore`. For convenience, fsck prefixes
+       the error/warning with the message ID, e.g. "missingEmail: invalid
+       author/committer line - missing email" means that setting
+       `receive.fsck.missingEmail = ignore` will hide that issue.
++
+This feature is intended to support working with legacy repositories
+which would not pass pushing when `receive.fsckObjects = true`, allowing
+the host to accept repositories with certain known issues but still catch
+other issues.
+
+receive.fsck.skipList::
+       The path to a sorted list of object names (i.e. one SHA-1 per
+       line) that are known to be broken in a non-fatal way and should
+       be ignored. This feature is useful when an established project
+       should be accepted despite early commits containing errors that
+       can be safely ignored such as invalid committer email addresses.
+       Note: corrupt objects cannot be skipped with this setting.
+
 receive.unpackLimit::
        If the number of objects received in a push is below this
        limit then the objects will be unpacked into loose object
@@ -2242,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
@@ -2536,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
@@ -2553,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`
index 0d8ba48f792ae82237a56aa0dd2aca03f4e16020..dbea6e7ae9131a1e5c51dec460d58cc6deda701b 100644 (file)
@@ -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::
index 4cb52a7302077e17d5a05983a5e6c924b4694194..e97f2de21bdc58cc2de35731f7b339353b121bd0 100644 (file)
@@ -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
 --------
index 319ab4cb086874da5e2f153d69d8f9af1f18461e..3105fc07205ac19805a753603940f9add612d4ae 100644 (file)
@@ -69,6 +69,20 @@ OPTIONS
        not be combined with any other options or arguments.  See the
        section `BATCH OUTPUT` below for details.
 
+--batch-all-objects::
+       Instead of reading a list of objects on stdin, perform the
+       requested batch operation on all objects in the repository and
+       any alternate object stores (not just reachable objects).
+       Requires `--batch` or `--batch-check` be specified. Note that
+       the objects are visited in order sorted by their hashes.
+
+--buffer::
+       Normally batch output is flushed after each object is output, so
+       that a process can interactively read and write from
+       `cat-file`. With this option, the output uses normal stdio
+       buffering; this is much more efficient when invoking
+       `--batch-check` on a large number of objects.
+
 --allow-unknown-type::
        Allow -s or -t to query broken/corrupt objects of unknown type.
 
index fc02959ba4ab1ae6acc3dc5f707e870e42144f1c..9044dfaadae10047281dfdfd06a75e634e1309fa 100644 (file)
@@ -94,8 +94,8 @@ OPTIONS
        Interpret <refname> as a reference name pattern for a refspec
        (as used with remote repositories).  If this option is
        enabled, <refname> is allowed to contain a single `*`
-       in place of a one full pathname component (e.g.,
-       `foo/*/bar` but not `foo/bar*`).
+       in the refspec (e.g., `foo/bar*/baz` or `foo/bar*baz/`
+       but not `foo/bar*/baz*`).
 
 --normalize::
        Normalize 'refname' by removing any leading slash (`/`)
index 63b739c550dda6fddb73fad776d48e0b71f71bfa..e269fb110835305633ccafa6aa87cf1f425f7b8c 100644 (file)
@@ -120,6 +120,21 @@ entries; instead, unmerged entries are ignored.
 --theirs::
        When checking out paths from the index, check out stage #2
        ('ours') or #3 ('theirs') for unmerged paths.
++
+Note that during `git rebase` and `git pull --rebase`, 'ours' and
+'theirs' may appear swapped; `--ours` gives the version from the
+branch the changes are rebased onto, while `--theirs` gives the
+version from the branch that holds your work that is being rebased.
++
+This is because `rebase` is used in a workflow that treats the
+history at the remote as the shared canonical one, and treats the
+work done on the branch you are rebasing as the third-party work to
+be integrated, and you are temporarily assuming the role of the
+keeper of the canonical history during the rebase.  As the keeper of
+the canonical history, you need to view the history from the remote
+as `ours` (i.e. "our shared canonical history"), while what you did
+on your side branch as `theirs` (i.e. "one contributor's work on top
+of it").
 
 -b <new_branch>::
        Create a new branch named <new_branch> and start it at
index fd328952556fc8a28db72e6537595067aadbd946..66910aa2faff1d78b71c5c552550a4f8e9d51832 100644 (file)
@@ -54,7 +54,7 @@ Options for Frontends
 ~~~~~~~~~~~~~~~~~~~~~
 
 --cat-blob-fd=<fd>::
-       Write responses to `cat-blob` and `ls` queries to the
+       Write responses to `get-mark`, `cat-blob`, and `ls` queries to the
        file descriptor <fd> instead of `stdout`.  Allows `progress`
        output intended for the end-user to be separated from other
        output.
@@ -350,6 +350,11 @@ and control the current import process.  More detailed discussion
        unless the `done` feature was requested using the
        `--done` command-line option or `feature done` command.
 
+`get-mark`::
+       Causes fast-import to print the SHA-1 corresponding to a mark
+       to the file descriptor set with `--cat-blob-fd`, or `stdout` if
+       unspecified.
+
 `cat-blob`::
        Causes fast-import to print a blob in 'cat-file --batch'
        format to the file descriptor set with `--cat-blob-fd` or
@@ -930,6 +935,25 @@ Placing a `progress` command immediately after a `checkpoint` will
 inform the reader when the `checkpoint` has been completed and it
 can safely access the refs that fast-import updated.
 
+`get-mark`
+~~~~~~~~~~
+Causes fast-import to print the SHA-1 corresponding to a mark to
+stdout or to the file descriptor previously arranged with the
+`--cat-blob-fd` argument. The command otherwise has no impact on the
+current import; its purpose is to retrieve SHA-1s that later commits
+might want to refer to in their commit messages.
+
+....
+       'get-mark' SP ':' <idnum> LF
+....
+
+This command can be used anywhere in the stream that comments are
+accepted.  In particular, the `get-mark` command can be used in the
+middle of a commit but not in the middle of a `data` command.
+
+See ``Responses To Commands'' below for details about how to read
+this output safely.
+
 `cat-blob`
 ~~~~~~~~~~
 Causes fast-import to print a blob to a file descriptor previously
@@ -1000,7 +1024,8 @@ Output uses the same format as `git ls-tree <tree> -- <path>`:
 ====
 
 The <dataref> represents the blob, tree, or commit object at <path>
-and can be used in later 'cat-blob', 'filemodify', or 'ls' commands.
+and can be used in later 'get-mark', 'cat-blob', 'filemodify', or
+'ls' commands.
 
 If there is no file or subtree at that path, 'git fast-import' will
 instead report
@@ -1042,9 +1067,11 @@ import-marks-if-exists::
        "feature import-marks-if-exists" like a corresponding
        command-line option silently skips a nonexistent file.
 
+get-mark::
 cat-blob::
 ls::
-       Require that the backend support the 'cat-blob' or 'ls' command.
+       Require that the backend support the 'get-mark', 'cat-blob',
+       or 'ls' command respectively.
        Versions of fast-import not supporting the specified command
        will exit with a message indicating so.
        This lets the import error out early with a clear message,
@@ -1124,11 +1151,11 @@ bidirectional pipes:
        git fast-import >fast-import-output
 ====
 
-A frontend set up this way can use `progress`, `ls`, and `cat-blob`
-commands to read information from the import in progress.
+A frontend set up this way can use `progress`, `get-mark`, `ls`, and
+`cat-blob` commands to read information from the import in progress.
 
 To avoid deadlock, such frontends must completely consume any
-pending output from `progress`, `ls`, and `cat-blob` before
+pending output from `progress`, `ls`, `get-mark`, and `cat-blob` before
 performing writes to fast-import that might block.
 
 Crash Reports
index 25c431d3c552d523c79473cf10867a3cab00c259..84ee92e15844588425111c1230b37ac3f0f03038 100644 (file)
@@ -11,7 +11,7 @@ SYNOPSIS
 [verse]
 'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]
         [--[no-]full] [--strict] [--verbose] [--lost-found]
-        [--[no-]dangling] [--[no-]progress] [<object>*]
+        [--[no-]dangling] [--[no-]progress] [--connectivity-only] [<object>*]
 
 DESCRIPTION
 -----------
@@ -60,6 +60,11 @@ index file, all SHA-1 references in `refs` namespace, and all reflogs
        object pools.  This is now default; you can turn it off
        with --no-full.
 
+--connectivity-only::
+       Check only the connectivity of tags, commits and tree objects. By
+       avoiding to unpack blobs, this speeds up the operation, at the
+       expense of missing corrupt objects or other problematic issues.
+
 --strict::
        Enable more strict checking, namely to catch a file mode
        recorded with g+w bit set, which was created by older
index 5692945a0b38c0b9e8e79e73a9a755c292224c98..97b9993ee8c2aeef504e96a041424e1d396a75d8 100644 (file)
@@ -184,6 +184,12 @@ log.date::
        `--date` option.)  Defaults to "default", which means to write
        dates like `Sat May 8 19:35:34 2010 -0500`.
 
+log.follow::
+       If a single <path> is given to git log, it will act as
+       if the `--follow` option was also used.  This has the same
+       limitations as `--follow`, i.e. it cannot be used to follow
+       multiple files and does not work well on non-linear history.
+
 log.showRoot::
        If `false`, `git log` and related commands will not treat the
        initial commit as a big creation event.  Any root commits in
index 1d01baa5fcfd03370953e8311b9c7bf3b49e2f4e..ca039546a463bee511ca3a1c7d301689fb56a412 100644 (file)
@@ -213,6 +213,15 @@ rebase.autoSquash::
 rebase.autoStash::
        If set to true enable '--autostash' option by default.
 
+rebase.missingCommitsCheck::
+       If set to "warn", print warnings about removed commits in
+       interactive mode. If set to "error", print the warnings and
+       stop the rebase. If set to "ignore", no checking is
+       done. "ignore" by default.
+
+rebase.instructionFormat::
+       Custom commit list format to use during an '--interactive' rebase.
+
 OPTIONS
 -------
 --onto <newbase>::
@@ -359,6 +368,10 @@ default is `--no-fork-point`, otherwise the default is `--fork-point`.
        Make a list of the commits which are about to be rebased.  Let the
        user edit that list before rebasing.  This mode can also be used to
        split commits (see SPLITTING COMMITS below).
++
+The commit list format can be changed by setting the configuration option
+rebase.instructionFormat.  A customized instruction format will automatically
+have the long commit hash prepended to the format.
 
 -p::
 --preserve-merges::
@@ -514,6 +527,9 @@ rebasing.
 If you just want to edit the commit message for a commit, replace the
 command "pick" with the command "reword".
 
+To drop a commit, replace the command "pick" with "drop", or just
+delete the matching line.
+
 If you want to fold two or more commits into one, replace the command
 "pick" for the second and subsequent commits with "squash" or "fixup".
 If the commits had different authors, the folded commit will be
index 5e7908e4f705d858dca8483069fd3b89c8f7e31c..44c736f1a8e581bc072d5de3b719284f7376ec3b 100644 (file)
@@ -23,6 +23,7 @@ depending on the subcommand:
        [--dry-run] [--verbose] [--all | <refs>...]
 'git reflog delete' [--rewrite] [--updateref]
        [--dry-run] [--verbose] ref@\{specifier\}...
+'git reflog exists' <ref>
 
 Reference logs, or "reflogs", record when the tips of branches and
 other references were updated in the local repository. Reflogs are
@@ -52,6 +53,9 @@ argument must be an _exact_ entry (e.g. "`git reflog delete
 master@{2}`"). This subcommand is also typically not used directly by
 end users.
 
+The "exists" subcommand checks whether a ref has a reflog.  It exits
+with zero status if the reflog exists, and non-zero status if it does
+not.
 
 OPTIONS
 -------
index c483100e75886e7326cecabcd66f1449e640365a..b6c6326cdc7bb42993cf16fcae0d492944720e51 100644 (file)
@@ -311,8 +311,8 @@ Each line of options has this format:
 `<opt-spec>`::
        its format is the short option character, then the long option name
        separated by a comma. Both parts are not required, though at least one
-       is necessary. `h,help`, `dry-run` and `f` are all three correct
-       `<opt-spec>`.
+       is necessary. May not contain any of the `<flags>` characters.
+       `h,help`, `dry-run` and `f` are examples of correct `<opt-spec>`.
 
 `<flags>`::
        `<flags>` are of `*`, `=`, `?` or `!`.
index 7ae467ba415e5cb4413d0246883b8a620b8960e3..f14705ee04e491e698e63ab10d15d9ec427b56f4 100644 (file)
@@ -49,17 +49,17 @@ Composing
        of 'sendemail.annotate'. See the CONFIGURATION section for
        'sendemail.multiEdit'.
 
---bcc=<address>::
+--bcc=<address>,...::
        Specify a "Bcc:" value for each email. Default is the value of
        'sendemail.bcc'.
 +
-The --bcc option must be repeated for each user you want on the bcc list.
+This option may be specified multiple times.
 
---cc=<address>::
+--cc=<address>,...::
        Specify a starting "Cc:" value for each email.
        Default is the value of 'sendemail.cc'.
 +
-The --cc option must be repeated for each user you want on the cc list.
+This option may be specified multiple times.
 
 --compose::
        Invoke a text editor (see GIT_EDITOR in linkgit:git-var[1])
@@ -110,13 +110,13 @@ is not set, this will be prompted for.
        Only necessary if --compose is also set.  If --compose
        is not set, this will be prompted for.
 
---to=<address>::
+--to=<address>,...::
        Specify the primary recipient of the emails generated. Generally, this
        will be the upstream maintainer of the project involved. Default is the
        value of the 'sendemail.to' configuration value; if that is unspecified,
        and --to-cmd is not specified, this will be prompted for.
 +
-The --to option must be repeated for each user you want on the to list.
+This option may be specified multiple times.
 
 --8bit-encoding=<encoding>::
        When encountering a non-ASCII message or subject that does not
index 034d10d633c343b17a83879b00aae3ba2a98a388..84f6496bf228454acaa04e570f7857ba1975cda4 100644 (file)
@@ -13,8 +13,7 @@ SYNOPSIS
        <tagname> [<commit> | <object>]
 'git tag' -d <tagname>...
 'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]
-       [--column[=<options>] | --no-column] [<pattern>...]
-       [<pattern>...]
+       [--column[=<options>] | --no-column] [--create-reflog] [<pattern>...]
 'git tag' -v <tagname>...
 
 DESCRIPTION
@@ -143,6 +142,9 @@ This option is only applicable when listing tags without annotation lines.
        all, 'whitespace' removes just leading/trailing whitespace lines and
        'strip' removes both whitespace and commentary.
 
+--create-reflog::
+       Create a reflog for the tag.
+
 <tagname>::
        The name of the tag to create, delete, or describe.
        The new tag name must pass all checks defined by
index 78a0d955ec22e22fbb1028f4eb6bfceeafc976f7..2f4ff501568d3a3e0a65212cf177e6903343a6d9 100644 (file)
-A short Git tools survey
-========================
+Git Tools
+=========
 
+When Git was young, people looking for third-party Git-related tools came
+to the Git project itself to find them, thus a list of such tools was
+maintained here. These days, however, search engines fill that role much
+more efficiently, so this manually-maintained list has been retired.
 
-Introduction
-------------
-
-Apart from Git contrib/ area there are some others third-party tools
-you may want to look.
-
-This document presents a brief summary of each tool and the corresponding
-link.
-
-
-Alternative/Augmentative Porcelains
------------------------------------
-
-   - *Cogito* (http://www.kernel.org/pub/software/scm/cogito/)
-
-   Cogito is a version control system layered on top of the Git tree history
-   storage system. It aims at seamless user interface and ease of use,
-   providing generally smoother user experience than the "raw" Core Git
-   itself and indeed many other version control systems.
-
-   Cogito is no longer maintained as most of its functionality
-   is now in core Git.
-
-
-   - *pg* (http://www.spearce.org/category/projects/scm/pg/)
-
-   pg is a shell script wrapper around Git to help the user manage a set of
-   patches to files. pg is somewhat like quilt or StGit, but it does have a
-   slightly different feature set.
-
-
-   - *StGit* (http://www.procode.org/stgit/)
-
-   Stacked Git provides a quilt-like patch management functionality in the
-   Git environment. You can easily manage your patches in the scope of Git
-   until they get merged upstream.
-
-
-History Viewers
----------------
-
-   - *gitk* (shipped with git-core)
-
-   gitk is a simple Tk GUI for browsing history of Git repositories easily.
-
-
-   - *gitview*  (contrib/)
-
-   gitview is a GTK based repository browser for Git
-
-
-   - *gitweb* (shipped with git-core)
-
-   Gitweb provides full-fledged web interface for Git repositories.
-
-
-   - *qgit* (http://digilander.libero.it/mcostalba/)
-
-   QGit is a git/StGit GUI viewer built on Qt/C++. QGit could be used
-   to browse history and directory tree, view annotated files, commit
-   changes cherry picking single files or applying patches.
-   Currently it is the fastest and most feature rich among the Git
-   viewers and commit tools.
-
-   - *tig* (http://jonas.nitro.dk/tig/)
-
-   tig by Jonas Fonseca is a simple Git repository browser
-   written using ncurses. Basically, it just acts as a front-end
-   for git-log and git-show/git-diff. Additionally, you can also
-   use it as a pager for Git commands.
-
-
-Foreign SCM interface
----------------------
-
-   - *git-svn* (shipped with git-core)
-
-   git-svn is a simple conduit for changesets between a single Subversion
-   branch and Git.
-
-
-   - *quilt2git / git2quilt* (http://home-tj.org/wiki/index.php/Misc)
-
-   These utilities convert patch series in a quilt repository and commit
-   series in Git back and forth.
-
-
-   - *hg-to-git* (contrib/)
-
-   hg-to-git converts a Mercurial repository into a Git one, and
-   preserves the full branch history in the process. hg-to-git can
-   also be used in an incremental way to keep the Git repository
-   in sync with the master Mercurial repository.
-
-
-Others
-------
-
-   - *(h)gct* (http://www.cyd.liu.se/users/~freku045/gct/)
-
-   Commit Tool or (h)gct is a GUI enabled commit tool for Git and
-   Mercurial (hg). It allows the user to view diffs, select which files
-   to committed (or ignored / reverted) write commit messages and
-   perform the commit itself.
-
-   - *git.el* (contrib/)
-
-   This is an Emacs interface for Git. The user interface is modelled on
-   pcl-cvs. It has been developed on Emacs 21 and will probably need some
-   tweaking to work on XEmacs.
-
-
-http://git.or.cz/gitwiki/InterfacesFrontendsAndTools has more
-comprehensive list.
+See also the `contrib/` area, and the Git wiki:
+http://git.or.cz/gitwiki/InterfacesFrontendsAndTools
index c8f5ae5cb362276de94aa867af59818a75b04595..969bfab2ab422ca8b7bdf2eb3ba45edc92fcd00b 100644 (file)
@@ -8,7 +8,7 @@ git-update-ref - Update the object name stored in a ref safely
 SYNOPSIS
 --------
 [verse]
-'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>] | --stdin [-z])
+'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] [--create-reflog] <ref> <newvalue> [<oldvalue>] | --stdin [-z])
 
 DESCRIPTION
 -----------
@@ -67,6 +67,9 @@ performs all modifications together.  Specify commands of the form:
        verify SP <ref> [SP <oldvalue>] LF
        option SP <opt> LF
 
+With `--create-reflog`, update-ref will create a reflog for each ref
+even if one would not ordinarily be created.
+
 Quote fields containing whitespace as if they were strings in C source
 code; i.e., surrounded by double-quotes and with backslash escapes.
 Use 40 "0" characters or the empty string to specify a zero value.  To
index 9413e2802a718117bd0b7c9997d55619ea18ba59..ecf4da16cf0a270cda7916777524ac129b4bead1 100644 (file)
@@ -16,6 +16,10 @@ Validates the gpg signature created by 'git commit -S'.
 
 OPTIONS
 -------
+--raw::
+       Print the raw gpg status output to standard error instead of the normal
+       human-readable output.
+
 -v::
 --verbose::
        Print the contents of the commit object before validating it.
index f88ba96f023ac9f497f01ef699d0931a6d14e59a..d590edcebd99a3c9321cfdc00499aa6996b78a5b 100644 (file)
@@ -16,6 +16,10 @@ Validates the gpg signature created by 'git tag'.
 
 OPTIONS
 -------
+--raw::
+       Print the raw gpg status output to standard error instead of the normal
+       human-readable output.
+
 -v::
 --verbose::
        Print the contents of the tag object before validating it.
index 3387e2f03767db2dad69adb756579bfa13509252..fb68156cf8ad0695eb6e5a7548c9a2e56be5c3f2 100644 (file)
@@ -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
 ---
index f4cb5cb200cf69461d5af39d4c29b51138e4d3ec..2795340fb2e793b51f775e79defe52453adcdc9d 100644 (file)
@@ -46,11 +46,12 @@ Documentation for older releases are available here:
 * link:v2.5.0/git.html[documentation for release 2.5]
 
 * release notes for
-  link:RelNotes/2.5.0.txt[2.5],
+  link:RelNotes/2.5.0.txt[2.5].
 
-* link:v2.4.7/git.html[documentation for release 2.4.7]
+* link:v2.4.8/git.html[documentation for release 2.4.8]
 
 * release notes for
+  link:RelNotes/2.4.8.txt[2.4.8],
   link:RelNotes/2.4.7.txt[2.4.7],
   link:RelNotes/2.4.6.txt[2.4.6],
   link:RelNotes/2.4.5.txt[2.4.5],
@@ -780,7 +781,7 @@ The Git Repository
 ~~~~~~~~~~~~~~~~~~
 These environment variables apply to 'all' core Git commands. Nb: it
 is worth noting that they may be used/overridden by SCMS sitting above
-Git so take care if using Cogito etc.
+Git so take care if using a foreign front-end.
 
 'GIT_INDEX_FILE'::
        This environment allows the specification of an alternate
@@ -1009,9 +1010,20 @@ Unsetting the variable, or setting it to empty, "0" or
        Enables trace messages for all packets coming in or out of a
        given program. This can help with debugging object negotiation
        or other protocol issues. Tracing is turned off at a packet
-       starting with "PACK".
+       starting with "PACK" (but see 'GIT_TRACE_PACKFILE' below).
        See 'GIT_TRACE' for available trace output options.
 
+'GIT_TRACE_PACKFILE'::
+       Enables tracing of packfiles sent or received by a
+       given program. Unlike other trace output, this trace is
+       verbatim: no headers, and no quoting of binary data. You almost
+       certainly want to direct into a file (e.g.,
+       `GIT_TRACE_PACKFILE=/tmp/my.pack`) rather than displaying it on
+       the terminal or mixing it with other trace output.
++
+Note that this is currently only implemented for the client side
+of clones and fetches.
+
 'GIT_TRACE_PERFORMANCE'::
        Enables performance related trace messages, e.g. total execution
        time of each Git command.
index 81fe586948582fae4945029a5cd49e61b09fac5b..e3b1de80335e713507875fb54c31c3ba5dab8c9f 100644 (file)
@@ -527,6 +527,8 @@ patterns are available:
 
 - `fortran` suitable for source code in the Fortran language.
 
+- `fountain` suitable for Fountain documents.
+
 - `html` suitable for HTML/XHTML documents.
 
 - `java` suitable for source code in the Java language.
index 7173b38830015ba1be09772e80cf3973c10bab69..577ee844e0ed09e9f0339de55cf14a29ca7bdab2 100644 (file)
@@ -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
index e9a1d5d25a698c3bc60a022ac2f749a7dd7d2ea0..2dd79db5cbf674d3eeeb70c78cc961d0b15503ab 100644 (file)
@@ -1,18 +1,31 @@
-At the core level, Git is character encoding agnostic.
-
- - The pathnames recorded in the index and in the tree objects
-   are treated as uninterpreted sequences of non-NUL bytes.
-   What readdir(2) returns are what are recorded and compared
-   with the data Git keeps track of, which in turn are expected
-   to be what lstat(2) and creat(2) accepts.  There is no such
-   thing as pathname encoding translation.
+Git is to some extent character encoding agnostic.
 
  - The contents of the blob objects are uninterpreted sequences
    of bytes.  There is no encoding translation at the core
    level.
 
- - The commit log messages are uninterpreted sequences of non-NUL
-   bytes.
+ - Path names are encoded in UTF-8 normalization form C. This
+   applies to tree objects, the index file, ref names, as well as
+   path names in command line arguments, environment variables
+   and config files (`.git/config` (see linkgit:git-config[1]),
+   linkgit:gitignore[5], linkgit:gitattributes[5] and
+   linkgit:gitmodules[5]).
++
+Note that Git at the core level treats path names simply as
+sequences of non-NUL bytes, there are no path name encoding
+conversions (except on Mac and Windows). Therefore, using
+non-ASCII path names will mostly work even on platforms and file
+systems that use legacy extended ASCII encodings. However,
+repositories created on such systems will not work properly on
+UTF-8-based systems (e.g. Linux, Mac, Windows) and vice versa.
+Additionally, many Git-based tools simply assume path names to
+be UTF-8 and will fail to display other encodings correctly.
+
+ - Commit log messages are typically encoded in UTF-8, but other
+   extended ASCII encodings are also supported. This includes
+   ISO-8859-x, CP125x and many others, but _not_ UTF-16/32,
+   EBCDIC and CJK multi-byte encodings (GBK, Shift-JIS, Big5,
+   EUC-x, CP9xx etc.).
 
 Although we encourage that the commit log messages are encoded
 in UTF-8, both the core and Git Porcelain are designed not to
index 77ac439234f40f51aae8613a672de06e3e143771..a9b808fab321e8287806c0bca34f1e41af3d41d1 100644 (file)
@@ -727,6 +727,11 @@ format, often found in email messages.
 +
 `--date=raw` shows the date in the internal raw Git format `%s %z` format.
 +
+`--date=format:...` feeds the format `...` to your system `strftime`.
+Use `--date=format:%c` to show the date in your system locale's
+preferred format.  See the `strftime` manual for a complete list of
+format placeholders.
++
 `--date=default` shows timestamps in the original time zone
 (either committer's or author's).
 
index 1a797812fb426189b03a814498dd7019c96607b4..8076172a08ce0dfc80e9656cd1ee7712c32c1d3e 100644 (file)
@@ -46,6 +46,9 @@ Functions
        Format a string and push it onto the end of the array. This is a
        convenience wrapper combining `strbuf_addf` and `argv_array_push`.
 
+`argv_array_pushv`::
+       Push a null-terminated array of strings onto the end of the array.
+
 `argv_array_pop`::
        Remove the final element from the array. If there are no
        elements in the array, do nothing.
index 1f2db313122ccb18223f3be429620fa3385cec51..5f0757dcc965fc89b96f18e31732390b1afeb84e 100644 (file)
@@ -168,6 +168,12 @@ There are some macros to easily define options:
        Introduce an option with integer argument.
        The integer is put into `int_var`.
 
+`OPT_MAGNITUDE(short, long, &unsigned_long_var, description)`::
+       Introduce an option with a size argument. The argument must be a
+       non-negative integer and may include a suffix of 'k', 'm' or 'g' to
+       scale the provided value by 1024, 1024^2 or 1024^3 respectively.
+       The scaled value is put into `unsigned_long_var`.
+
 `OPT_DATE(short, long, &int_var, description)`::
        Introduce an option with date argument, see `approxidate()`.
        The timestamp is put into `int_var`.
@@ -212,6 +218,19 @@ There are some macros to easily define options:
        Use it to hide deprecated options that are still to be recognized
        and ignored silently.
 
+`OPT_PASSTHRU(short, long, &char_var, arg_str, description, flags)`::
+       Introduce an option that will be reconstructed into a char* string,
+       which must be initialized to NULL. This is useful when you need to
+       pass the command-line option to another command. Any previous value
+       will be overwritten, so this should only be used for options where
+       the last one specified on the command line wins.
+
+`OPT_PASSTHRU_ARGV(short, long, &argv_array_var, arg_str, description, flags)`::
+       Introduce an option where all instances of it on the command-line will
+       be reconstructed into an argv_array. This is useful when you need to
+       pass the command-line option, which can be specified multiple times,
+       to another command.
+
 
 The last element of the array must be `OPT_END()`.
 
index b7093af8b23e6a83741b81678e7aae0c5ff7c88a..7392ff636c6dda148b41993448f4a5589f66938c 100644 (file)
@@ -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:
 
index cd968201627162f36e56e078d5631a19ef505b08..fc45283501a3b004edabc55781b486b37509dd37 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v2.5.0
+DEF_VER=v2.5.0.GIT
 
 LF='
 '
@@ -38,5 +38,3 @@ test "$VN" = "$VC" || {
        echo >&2 "GIT_VERSION = $VN"
        echo "GIT_VERSION = $VN" >$GVF
 }
-
-
index 8c3c724a7f5e0594ad836065757371f7a144c472..e39ca6ca6480618c032128df8d12aed1da81e78e 100644 (file)
--- 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
@@ -475,7 +474,6 @@ SCRIPT_SH += git-merge-octopus.sh
 SCRIPT_SH += git-merge-one-file.sh
 SCRIPT_SH += git-merge-resolve.sh
 SCRIPT_SH += git-mergetool.sh
-SCRIPT_SH += git-pull.sh
 SCRIPT_SH += git-quiltimport.sh
 SCRIPT_SH += git-rebase.sh
 SCRIPT_SH += git-remote-testgit.sh
@@ -763,6 +761,7 @@ LIB_OBJS += reachable.o
 LIB_OBJS += read-cache.o
 LIB_OBJS += reflog-walk.o
 LIB_OBJS += refs.o
+LIB_OBJS += ref-filter.o
 LIB_OBJS += remote.o
 LIB_OBJS += replace_object.o
 LIB_OBJS += rerere.o
@@ -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
@@ -878,6 +878,7 @@ BUILTIN_OBJS += builtin/pack-refs.o
 BUILTIN_OBJS += builtin/patch-id.o
 BUILTIN_OBJS += builtin/prune-packed.o
 BUILTIN_OBJS += builtin/prune.o
+BUILTIN_OBJS += builtin/pull.o
 BUILTIN_OBJS += builtin/push.o
 BUILTIN_OBJS += builtin/read-tree.o
 BUILTIN_OBJS += builtin/receive-pack.o
index 3295d667f396a686891335ceaaf817545a7d9e25..84a20c84c648cacea6b2cbdab43d36e92aaaa5c8 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.5.0.txt
\ No newline at end of file
+Documentation/RelNotes/2.6.0.txt
\ No newline at end of file
index 575bec20b35a31e653e49125386f3dcee880c217..4965686e19cbb44b86d48363f8f3e289bebef94d 100644 (file)
--- a/advice.c
+++ b/advice.c
@@ -96,6 +96,14 @@ void NORETURN die_resolve_conflict(const char *me)
        die("Exiting because of an unresolved conflict.");
 }
 
+void NORETURN die_conclude_merge(void)
+{
+       error(_("You have not concluded your merge (MERGE_HEAD exists)."));
+       if (advice_resolve_conflict)
+               advise(_("Please, commit your changes before you can merge."));
+       die(_("Exiting because of unfinished merge."));
+}
+
 void detach_advice(const char *new_name)
 {
        const char fmt[] =
index 5ecc6c154e5a7207d18bbb05da5e35b2b4eae230..b341a55ce7c250cc34b49dc06a2843b5a5df1aac 100644 (file)
--- a/advice.h
+++ b/advice.h
@@ -24,6 +24,7 @@ __attribute__((format (printf, 1, 2)))
 void advise(const char *advice, ...);
 int error_resolve_conflict(const char *me);
 extern void NORETURN die_resolve_conflict(const char *me);
+void NORETURN die_conclude_merge(void);
 void detach_advice(const char *new_name);
 
 #endif /* ADVICE_H */
index d37c41daf29b1163ac7743e5ce17cdafc9b2e8a3..01b0899b3f9027c5a392bd58667ed0e216d082ed 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -1,4 +1,5 @@
 #include "cache.h"
+#include "refs.h"
 #include "commit.h"
 #include "tree-walk.h"
 #include "attr.h"
@@ -33,7 +34,7 @@ static void format_subst(const struct commit *commit,
        char *to_free = NULL;
        struct strbuf fmt = STRBUF_INIT;
        struct pretty_print_context ctx = {0};
-       ctx.date_mode = DATE_NORMAL;
+       ctx.date_mode.type = DATE_NORMAL;
        ctx.abbrev = DEFAULT_ABBREV;
 
        if (src == buf->buf)
index 256741d2262b237c56b6730bea9d52c9b39b7ee3..eaed47712b44ed1a5f9e5d35d356d0de4a5a4d54 100644 (file)
@@ -49,6 +49,12 @@ void argv_array_pushl(struct argv_array *array, ...)
        va_end(ap);
 }
 
+void argv_array_pushv(struct argv_array *array, const char **argv)
+{
+       for (; *argv; argv++)
+               argv_array_push(array, *argv);
+}
+
 void argv_array_pop(struct argv_array *array)
 {
        if (!array->argc)
index c65e6e825a96efca6111184e5c50d2406064631f..a2fa0aa606a01d5277e87d2368d7d6876b699474 100644 (file)
@@ -17,6 +17,7 @@ __attribute__((format (printf,2,3)))
 void argv_array_pushf(struct argv_array *, const char *fmt, ...);
 LAST_ARG_MUST_BE_NULL
 void argv_array_pushl(struct argv_array *, ...);
+void argv_array_pushv(struct argv_array *, const char **);
 void argv_array_pop(struct argv_array *);
 void argv_array_clear(struct argv_array *);
 
diff --git a/attr.c b/attr.c
index 8f2ac6c88c8c2f7cff514981a7c01c136f734892..086c08dcfab613ef2927ff2769797fa40cd03308 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -490,6 +490,8 @@ static int git_attr_system(void)
        return !git_env_bool("GIT_ATTR_NOSYSTEM", 0);
 }
 
+static GIT_PATH_FUNC(git_path_info_attributes, INFOATTRIBUTES_FILE)
+
 static void bootstrap_attr_stack(void)
 {
        struct attr_stack *elem;
@@ -531,7 +533,7 @@ static void bootstrap_attr_stack(void)
                debug_push(elem);
        }
 
-       elem = read_attr_from_file(git_path(INFOATTRIBUTES_FILE), 1);
+       elem = read_attr_from_file(git_path_info_attributes(), 1);
        if (!elem)
                elem = xcalloc(1, sizeof(*elem));
        elem->origin = NULL;
index 03d5cd9454207d0eb6fc87768e3dc432a5b921f5..f9da9d134680c7581231f27584b10e83a6110bcd 100644 (file)
--- 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;
 }
 
@@ -420,10 +429,13 @@ static int read_bisect_refs(void)
        return for_each_ref_in("refs/bisect/", register_ref, NULL);
 }
 
+static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES")
+static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV")
+
 static void read_bisect_paths(struct argv_array *array)
 {
        struct strbuf str = STRBUF_INIT;
-       const char *filename = git_path("BISECT_NAMES");
+       const char *filename = git_path_bisect_names();
        FILE *fp = fopen(filename, "r");
 
        if (!fp)
@@ -634,7 +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));
@@ -644,7 +656,7 @@ static void exit_if_skipped_commits(struct commit_list *tried,
 
 static int is_expected_rev(const struct object_id *oid)
 {
-       const char *filename = git_path("BISECT_EXPECTED_REV");
+       const char *filename = git_path_bisect_expected_rev();
        struct stat st;
        struct strbuf str = STRBUF_INIT;
        FILE *fp;
@@ -732,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);
 }
 
@@ -755,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);
 }
 
@@ -839,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))
@@ -889,6 +907,36 @@ static void show_diff_tree(const char *prefix, struct commit *commit)
        log_tree_commit(&opt, 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.
@@ -905,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");
 
@@ -926,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);
        }
 
@@ -942,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);
index 2a6c831f3ecdd12839dff9a9740881fc836b09bd..acd12ef802c611505139d641fc44776cf6431a5c 100644 (file)
--- 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
index b0024353f4409c8b54c4b569b8fb7c435b26cdae..3364adf1821b9a8b9bb0d81e8f1987f611eecdbc 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -302,11 +302,78 @@ void create_branch(const char *head,
 
 void remove_branch_state(void)
 {
-       unlink(git_path("CHERRY_PICK_HEAD"));
-       unlink(git_path("REVERT_HEAD"));
-       unlink(git_path("MERGE_HEAD"));
-       unlink(git_path("MERGE_RR"));
-       unlink(git_path("MERGE_MSG"));
-       unlink(git_path("MERGE_MODE"));
-       unlink(git_path("SQUASH_MSG"));
+       unlink(git_path_cherry_pick_head());
+       unlink(git_path_revert_head());
+       unlink(git_path_merge_head());
+       unlink(git_path_merge_rr());
+       unlink(git_path_merge_msg());
+       unlink(git_path_merge_mode());
+       unlink(git_path_squash_msg());
+}
+
+static 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);
 }
index 64173abf4db65b0a8e71c1c8880f97a3350306f7..58aa45fe72ca356a8bd3648782b36e63e207ee36 100644 (file)
--- 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
index 9e04f979bc6ff251bcd18efd55db986d77b1c6aa..79aaf0afe8912d154573df3baa6ea5c28f8097d6 100644 (file)
--- 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);
@@ -98,6 +99,7 @@ extern int cmd_pack_redundant(int argc, const char **argv, const char *prefix);
 extern int cmd_patch_id(int argc, const char **argv, const char *prefix);
 extern int cmd_prune(int argc, const char **argv, const char *prefix);
 extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
+extern int cmd_pull(int argc, const char **argv, const char *prefix);
 extern int cmd_push(int argc, const char **argv, const char *prefix);
 extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_receive_pack(int argc, const char **argv, const char *prefix);
index 4bd98b799e7e1a46beb09249ab2299f4d2b335b8..b2a5c57f0afde85a7584e1edc7a45b67d12d143d 100644 (file)
@@ -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 (file)
index 0000000..1399c8d
--- /dev/null
@@ -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;
+}
index a22ac174078742cc8991c0ee563980d239731e9b..4db01c195cd27719023eeb599e76428ad752c9c9 100644 (file)
@@ -6,6 +6,7 @@
  */
 
 #include "cache.h"
+#include "refs.h"
 #include "builtin.h"
 #include "blob.h"
 #include "commit.h"
@@ -50,7 +51,7 @@ static int xdl_opts;
 static int abbrev = -1;
 static int no_whole_file_rename;
 
-static enum date_mode blame_date_mode = DATE_ISO8601;
+static struct date_mode blame_date_mode = { DATE_ISO8601 };
 static size_t blame_date_width;
 
 static struct string_list mailmap;
@@ -1827,7 +1828,7 @@ static const char *format_time(unsigned long time, const char *tz_str,
                size_t time_width;
                int tz;
                tz = atoi(tz_str);
-               time_str = show_date(time, tz, blame_date_mode);
+               time_str = show_date(time, tz, &blame_date_mode);
                strbuf_addstr(&time_buf, time_str);
                /*
                 * Add space paddings to time_buf to display a fixed width
@@ -2187,7 +2188,7 @@ static int git_blame_config(const char *var, const char *value, void *cb)
        if (!strcmp(var, "blame.date")) {
                if (!value)
                        return config_error_nonbool(var);
-               blame_date_mode = parse_date_format(value);
+               parse_date_format(value, &blame_date_mode);
                return 0;
        }
 
@@ -2226,20 +2227,19 @@ static struct commit_list **append_parent(struct commit_list **tail, const unsig
 static void append_merge_parents(struct commit_list **tail)
 {
        int merge_head;
-       const char *merge_head_file = git_path("MERGE_HEAD");
        struct strbuf line = STRBUF_INIT;
 
-       merge_head = open(merge_head_file, O_RDONLY);
+       merge_head = open(git_path_merge_head(), O_RDONLY);
        if (merge_head < 0) {
                if (errno == ENOENT)
                        return;
-               die("cannot open '%s' for reading", merge_head_file);
+               die("cannot open '%s' for reading", git_path_merge_head());
        }
 
        while (!strbuf_getwholeline_fd(&line, merge_head, '\n')) {
                unsigned char sha1[20];
                if (line.len < 40 || get_sha1_hex(line.buf, sha1))
-                       die("unknown line in '%s': %s", merge_head_file, line.buf);
+                       die("unknown line in '%s': %s", git_path_merge_head(), line.buf);
                tail = append_parent(tail, sha1);
        }
        close(merge_head);
@@ -2569,13 +2569,13 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
 
        if (cmd_is_annotate) {
                output_option |= OUTPUT_ANNOTATE_COMPAT;
-               blame_date_mode = DATE_ISO8601;
+               blame_date_mode.type = DATE_ISO8601;
        } else {
                blame_date_mode = revs.date_mode;
        }
 
        /* The maximum width used to show the dates */
-       switch (blame_date_mode) {
+       switch (blame_date_mode.type) {
        case DATE_RFC2822:
                blame_date_width = sizeof("Thu, 19 Oct 2006 16:00:04 -0700");
                break;
@@ -2604,6 +2604,9 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        case DATE_NORMAL:
                blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
                break;
+       case DATE_STRFTIME:
+               blame_date_width = strlen(show_date(0, 0, &blame_date_mode)) + 1; /* add the null */
+               break;
        }
        blame_date_width -= 1; /* strip the null */
 
index b42e5b6dbc76016ca18e5ddd7ded2af613013eb7..58aa84f1e85212772d15175679e95263eae75123 100644 (file)
@@ -160,7 +160,7 @@ static int branch_merged(int kind, const char *name,
 }
 
 static int check_branch_commit(const char *branchname, const char *refname,
-                              unsigned char *sha1, struct commit *head_rev,
+                              const unsigned char *sha1, struct commit *head_rev,
                               int kinds, int force)
 {
        struct commit *rev = lookup_commit_reference(sha1);
@@ -253,7 +253,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
                        continue;
                }
 
-               if (delete_ref(name, sha1, REF_NODEREF)) {
+               if (delete_ref(name, is_null_sha1(sha1) ? NULL : sha1,
+                              REF_NODEREF)) {
                        error(remote_branch
                              ? _("Error deleting remote-tracking branch '%s'")
                              : _("Error deleting branch '%s'"),
index 049a95f1f113289a1a01f74a2485d1e9b282209f..07baad1e59c1977e5fce04d4c7288f8263947f4e 100644 (file)
@@ -9,6 +9,16 @@
 #include "userdiff.h"
 #include "streaming.h"
 #include "tree-walk.h"
+#include "sha1-array.h"
+
+struct batch_options {
+       int enabled;
+       int follow_symlinks;
+       int print_contents;
+       int buffer_output;
+       int all_objects;
+       const char *format;
+};
 
 static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
                        int unknown_type)
@@ -204,14 +214,25 @@ static size_t expand_format(struct strbuf *sb, const char *start, void *data)
        return end - start + 1;
 }
 
-static void print_object_or_die(int fd, struct expand_data *data)
+static void batch_write(struct batch_options *opt, const void *data, int len)
+{
+       if (opt->buffer_output) {
+               if (fwrite(data, 1, len, stdout) != len)
+                       die_errno("unable to write to stdout");
+       } else
+               write_or_die(1, data, len);
+}
+
+static void print_object_or_die(struct batch_options *opt, struct expand_data *data)
 {
        const unsigned char *sha1 = data->sha1;
 
        assert(data->info.typep);
 
        if (data->type == OBJ_BLOB) {
-               if (stream_blob_to_fd(fd, sha1, NULL, 0) < 0)
+               if (opt->buffer_output)
+                       fflush(stdout);
+               if (stream_blob_to_fd(1, sha1, NULL, 0) < 0)
                        die("unable to stream %s to stdout", sha1_to_hex(sha1));
        }
        else {
@@ -227,29 +248,40 @@ static void print_object_or_die(int fd, struct expand_data *data)
                if (data->info.sizep && size != data->size)
                        die("object %s changed size!?", sha1_to_hex(sha1));
 
-               write_or_die(fd, contents, size);
+               batch_write(opt, contents, size);
                free(contents);
        }
 }
 
-struct batch_options {
-       int enabled;
-       int follow_symlinks;
-       int print_contents;
-       const char *format;
-};
-
-static int batch_one_object(const char *obj_name, struct batch_options *opt,
-                           struct expand_data *data)
+static void batch_object_write(const char *obj_name, struct batch_options *opt,
+                              struct expand_data *data)
 {
        struct strbuf buf = STRBUF_INIT;
+
+       if (sha1_object_info_extended(data->sha1, &data->info, LOOKUP_REPLACE_OBJECT) < 0) {
+               printf("%s missing\n", obj_name ? obj_name : sha1_to_hex(data->sha1));
+               fflush(stdout);
+               return;
+       }
+
+       strbuf_expand(&buf, opt->format, expand_format, data);
+       strbuf_addch(&buf, '\n');
+       batch_write(opt, buf.buf, buf.len);
+       strbuf_release(&buf);
+
+       if (opt->print_contents) {
+               print_object_or_die(opt, data);
+               batch_write(opt, "\n", 1);
+       }
+}
+
+static void batch_one_object(const char *obj_name, struct batch_options *opt,
+                            struct expand_data *data)
+{
        struct object_context ctx;
        int flags = opt->follow_symlinks ? GET_SHA1_FOLLOW_SYMLINKS : 0;
        enum follow_symlinks_result result;
 
-       if (!obj_name)
-          return 1;
-
        result = get_sha1_with_context(obj_name, flags, data->sha1, &ctx);
        if (result != FOUND) {
                switch (result) {
@@ -274,7 +306,7 @@ static int batch_one_object(const char *obj_name, struct batch_options *opt,
                        break;
                }
                fflush(stdout);
-               return 0;
+               return;
        }
 
        if (ctx.mode == 0) {
@@ -282,24 +314,38 @@ static int batch_one_object(const char *obj_name, struct batch_options *opt,
                       (uintmax_t)ctx.symlink_path.len,
                       ctx.symlink_path.buf);
                fflush(stdout);
-               return 0;
+               return;
        }
 
-       if (sha1_object_info_extended(data->sha1, &data->info, LOOKUP_REPLACE_OBJECT) < 0) {
-               printf("%s missing\n", obj_name);
-               fflush(stdout);
-               return 0;
-       }
+       batch_object_write(obj_name, opt, data);
+}
 
-       strbuf_expand(&buf, opt->format, expand_format, data);
-       strbuf_addch(&buf, '\n');
-       write_or_die(1, buf.buf, buf.len);
-       strbuf_release(&buf);
+struct object_cb_data {
+       struct batch_options *opt;
+       struct expand_data *expand;
+};
 
-       if (opt->print_contents) {
-               print_object_or_die(1, data);
-               write_or_die(1, "\n", 1);
-       }
+static void batch_object_cb(const unsigned char sha1[20], void *vdata)
+{
+       struct object_cb_data *data = vdata;
+       hashcpy(data->expand->sha1, sha1);
+       batch_object_write(NULL, data->opt, data->expand);
+}
+
+static int batch_loose_object(const unsigned char *sha1,
+                             const char *path,
+                             void *data)
+{
+       sha1_array_append(data, sha1);
+       return 0;
+}
+
+static int batch_packed_object(const unsigned char *sha1,
+                              struct packed_git *pack,
+                              uint32_t pos,
+                              void *data)
+{
+       sha1_array_append(data, sha1);
        return 0;
 }
 
@@ -330,6 +376,21 @@ static int batch_objects(struct batch_options *opt)
        if (opt->print_contents)
                data.info.typep = &data.type;
 
+       if (opt->all_objects) {
+               struct sha1_array sa = SHA1_ARRAY_INIT;
+               struct object_cb_data cb;
+
+               for_each_loose_object(batch_loose_object, &sa, 0);
+               for_each_packed_object(batch_packed_object, &sa, 0);
+
+               cb.opt = opt;
+               cb.expand = &data;
+               sha1_array_for_each_unique(&sa, batch_object_cb, &cb);
+
+               sha1_array_clear(&sa);
+               return 0;
+       }
+
        /*
         * We are going to call get_sha1 on a potentially very large number of
         * objects. In most large cases, these will be actual object sha1s. The
@@ -355,9 +416,7 @@ static int batch_objects(struct batch_options *opt)
                        data.rest = p;
                }
 
-               retval = batch_one_object(buf.buf, opt, &data);
-               if (retval)
-                       break;
+               batch_one_object(buf.buf, opt, &data);
        }
 
        strbuf_release(&buf);
@@ -412,8 +471,9 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
                OPT_CMDMODE('p', NULL, &opt, N_("pretty-print object's content"), 'p'),
                OPT_CMDMODE(0, "textconv", &opt,
                            N_("for blob objects, run textconv on object's content"), 'c'),
-               OPT_BOOL( 0, "allow-unknown-type", &unknown_type,
+               OPT_BOOL(0, "allow-unknown-type", &unknown_type,
                          N_("allow -s and -t to work with broken/corrupt objects")),
+               OPT_BOOL(0, "buffer", &batch.buffer_output, N_("buffer --batch output")),
                { OPTION_CALLBACK, 0, "batch", &batch, "format",
                        N_("show info and content of objects fed from the standard input"),
                        PARSE_OPT_OPTARG, batch_option_callback },
@@ -422,6 +482,8 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
                        PARSE_OPT_OPTARG, batch_option_callback },
                OPT_BOOL(0, "follow-symlinks", &batch.follow_symlinks,
                         N_("follow in-tree symlinks (used with --batch or --batch-check)")),
+               OPT_BOOL(0, "batch-all-objects", &batch.all_objects,
+                        N_("show all objects with --batch or --batch-check")),
                OPT_END()
        };
 
@@ -446,7 +508,7 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
                usage_with_options(cat_file_usage, options);
        }
 
-       if (batch.follow_symlinks && !batch.enabled) {
+       if ((batch.follow_symlinks || batch.all_objects) && !batch.enabled) {
                usage_with_options(cat_file_usage, options);
        }
 
index f71844a23a9d4bda0664bec709648911bc0f2609..e1403bec272c332160e0346782436167fa016d93 100644 (file)
@@ -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);
@@ -612,22 +610,20 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
        if (opts->new_branch) {
                if (opts->new_orphan_branch) {
                        if (opts->new_branch_log && !log_all_ref_updates) {
-                               int temp;
-                               struct strbuf log_file = STRBUF_INIT;
                                int ret;
-                               const char *ref_name;
-
-                               ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch);
-                               temp = log_all_ref_updates;
-                               log_all_ref_updates = 1;
-                               ret = log_ref_setup(ref_name, &log_file);
-                               log_all_ref_updates = temp;
-                               strbuf_release(&log_file);
+                               char *refname;
+                               struct strbuf err = STRBUF_INIT;
+
+                               refname = mkpathdup("refs/heads/%s", opts->new_orphan_branch);
+                               ret = safe_create_reflog(refname, 1, &err);
+                               free(refname);
                                if (ret) {
-                                       fprintf(stderr, _("Can not do reflog for '%s'\n"),
-                                           opts->new_orphan_branch);
+                                       fprintf(stderr, _("Can not do reflog for '%s': %s\n"),
+                                               opts->new_orphan_branch, err.buf);
+                                       strbuf_release(&err);
                                        return;
                                }
+                               strbuf_release(&err);
                        }
                }
                else
@@ -832,8 +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);
@@ -898,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,
@@ -1170,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);
        }
 
@@ -1241,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);
index 6dcb72e64484fa5082bd2b77b370107984373340..df53def63f29e295e09261b9bd56960cf27582b1 100644 (file)
@@ -10,7 +10,6 @@
 #include "cache.h"
 #include "dir.h"
 #include "parse-options.h"
-#include "refs.h"
 #include "string-list.h"
 #include "quote.h"
 #include "column.h"
@@ -148,6 +147,31 @@ static int exclude_cb(const struct option *opt, const char *arg, int unset)
        return 0;
 }
 
+/*
+ * Return 1 if the given path is the root of a git repository or
+ * submodule else 0. Will not return 1 for bare repositories with the
+ * exception of creating a bare repository in "foo/.git" and calling
+ * is_git_repository("foo").
+ */
+static int is_git_repository(struct strbuf *path)
+{
+       int ret = 0;
+       int gitfile_error;
+       size_t orig_path_len = path->len;
+       assert(orig_path_len != 0);
+       if (path->buf[orig_path_len - 1] != '/')
+               strbuf_addch(path, '/');
+       strbuf_addstr(path, ".git");
+       if (read_gitfile_gently(path->buf, &gitfile_error) || is_git_directory(path->buf))
+               ret = 1;
+       if (gitfile_error == READ_GITFILE_ERR_OPEN_FAILED ||
+           gitfile_error == READ_GITFILE_ERR_READ_FAILED)
+               ret = 1;  /* This could be a real .git file, take the
+                          * safe option and avoid cleaning */
+       strbuf_setlen(path, orig_path_len);
+       return ret;
+}
+
 static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
                int dry_run, int quiet, int *dir_gone)
 {
@@ -155,13 +179,11 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
        struct strbuf quoted = STRBUF_INIT;
        struct dirent *e;
        int res = 0, ret = 0, gone = 1, original_len = path->len, len;
-       unsigned char submodule_head[20];
        struct string_list dels = STRING_LIST_INIT_DUP;
 
        *dir_gone = 1;
 
-       if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
-                       !resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) {
+       if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) && is_git_repository(path)) {
                if (!quiet) {
                        quote_path_relative(path->buf, prefix, &quoted);
                        printf(dry_run ?  _(msg_would_skip_git_dir) : _(msg_skip_git_dir),
index a72ff7e0098da9c89f6000e724ea097e3403601f..578da85254418a620d3c7c847af8eed6a3a29d58 100644 (file)
@@ -99,82 +99,140 @@ static const char *argv_submodule[] = {
        "submodule", "update", "--init", "--recursive", NULL
 };
 
-static char *get_repo_path(const char *repo, int *is_bundle)
+static const char *get_repo_path_1(struct strbuf *path, int *is_bundle)
 {
        static char *suffix[] = { "/.git", "", ".git/.git", ".git" };
        static char *bundle_suffix[] = { ".bundle", "" };
+       size_t baselen = path->len;
        struct stat st;
        int i;
 
        for (i = 0; i < ARRAY_SIZE(suffix); i++) {
-               const char *path;
-               path = mkpath("%s%s", repo, suffix[i]);
-               if (stat(path, &st))
+               strbuf_setlen(path, baselen);
+               strbuf_addstr(path, suffix[i]);
+               if (stat(path->buf, &st))
                        continue;
-               if (S_ISDIR(st.st_mode) && is_git_directory(path)) {
+               if (S_ISDIR(st.st_mode) && is_git_directory(path->buf)) {
                        *is_bundle = 0;
-                       return xstrdup(absolute_path(path));
+                       return path->buf;
                } else if (S_ISREG(st.st_mode) && st.st_size > 8) {
                        /* Is it a "gitfile"? */
                        char signature[8];
-                       int len, fd = open(path, O_RDONLY);
+                       const char *dst;
+                       int len, fd = open(path->buf, O_RDONLY);
                        if (fd < 0)
                                continue;
                        len = read_in_full(fd, signature, 8);
                        close(fd);
                        if (len != 8 || strncmp(signature, "gitdir: ", 8))
                                continue;
-                       path = read_gitfile(path);
-                       if (path) {
+                       dst = read_gitfile(path->buf);
+                       if (dst) {
                                *is_bundle = 0;
-                               return xstrdup(absolute_path(path));
+                               return dst;
                        }
                }
        }
 
        for (i = 0; i < ARRAY_SIZE(bundle_suffix); i++) {
-               const char *path;
-               path = mkpath("%s%s", repo, bundle_suffix[i]);
-               if (!stat(path, &st) && S_ISREG(st.st_mode)) {
+               strbuf_setlen(path, baselen);
+               strbuf_addstr(path, bundle_suffix[i]);
+               if (!stat(path->buf, &st) && S_ISREG(st.st_mode)) {
                        *is_bundle = 1;
-                       return xstrdup(absolute_path(path));
+                       return path->buf;
                }
        }
 
        return NULL;
 }
 
+static char *get_repo_path(const char *repo, int *is_bundle)
+{
+       struct strbuf path = STRBUF_INIT;
+       const char *raw;
+       char *canon;
+
+       strbuf_addstr(&path, repo);
+       raw = get_repo_path_1(&path, is_bundle);
+       canon = raw ? xstrdup(absolute_path(raw)) : NULL;
+       strbuf_release(&path);
+       return canon;
+}
+
 static char *guess_dir_name(const char *repo, int is_bundle, int is_bare)
 {
-       const char *end = repo + strlen(repo), *start;
+       const char *end = repo + strlen(repo), *start, *ptr;
        size_t len;
        char *dir;
 
+       /*
+        * Skip scheme.
+        */
+       start = strstr(repo, "://");
+       if (start == NULL)
+               start = repo;
+       else
+               start += 3;
+
+       /*
+        * Skip authentication data. The stripping does happen
+        * greedily, such that we strip up to the last '@' inside
+        * the host part.
+        */
+       for (ptr = start; ptr < end && !is_dir_sep(*ptr); ptr++) {
+               if (*ptr == '@')
+                       start = ptr + 1;
+       }
+
        /*
         * Strip trailing spaces, slashes and /.git
         */
-       while (repo < end && (is_dir_sep(end[-1]) || isspace(end[-1])))
+       while (start < end && (is_dir_sep(end[-1]) || isspace(end[-1])))
                end--;
-       if (end - repo > 5 && is_dir_sep(end[-5]) &&
+       if (end - start > 5 && is_dir_sep(end[-5]) &&
            !strncmp(end - 4, ".git", 4)) {
                end -= 5;
-               while (repo < end && is_dir_sep(end[-1]))
+               while (start < end && is_dir_sep(end[-1]))
                        end--;
        }
 
        /*
-        * Find last component, but be prepared that repo could have
-        * the form  "remote.example.com:foo.git", i.e. no slash
-        * in the directory part.
+        * Strip trailing port number if we've got only a
+        * hostname (that is, there is no dir separator but a
+        * colon). This check is required such that we do not
+        * strip URI's like '/foo/bar:2222.git', which should
+        * result in a dir '2222' being guessed due to backwards
+        * compatibility.
+        */
+       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);
@@ -484,16 +542,26 @@ static void write_remote_refs(const struct ref *local_refs)
 {
        const struct ref *r;
 
-       lock_packed_refs(LOCK_DIE_ON_ERROR);
+       struct ref_transaction *t;
+       struct strbuf err = STRBUF_INIT;
+
+       t = ref_transaction_begin(&err);
+       if (!t)
+               die("%s", err.buf);
 
        for (r = local_refs; r; r = r->next) {
                if (!r->peer_ref)
                        continue;
-               add_packed_ref(r->peer_ref->name, r->old_sha1);
+               if (ref_transaction_create(t, r->peer_ref->name, r->old_sha1,
+                                          0, NULL, &err))
+                       die("%s", err.buf);
        }
 
-       if (commit_packed_refs())
-               die_errno("unable to overwrite old ref-pack file");
+       if (initial_ref_transaction_commit(t, &err))
+               die("%s", err.buf);
+
+       strbuf_release(&err);
+       ref_transaction_free(t);
 }
 
 static void write_followtags(const struct ref *refs, const char *msg)
index 254477fd1d4e8b96f50eb42dc11ec1253c7467f8..4cbd5ff4de97aa41932bfe3e0826d71a54fe4f43 100644 (file)
@@ -166,9 +166,9 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset)
 
 static void determine_whence(struct wt_status *s)
 {
-       if (file_exists(git_path("MERGE_HEAD")))
+       if (file_exists(git_path_merge_head()))
                whence = FROM_MERGE;
-       else if (file_exists(git_path("CHERRY_PICK_HEAD"))) {
+       else if (file_exists(git_path_cherry_pick_head())) {
                whence = FROM_CHERRY_PICK;
                if (file_exists(git_path(SEQ_DIR)))
                        sequencer_in_use = 1;
@@ -725,12 +725,12 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                format_commit_message(commit, "fixup! %s\n\n",
                                      &sb, &ctx);
                hook_arg1 = "message";
-       } else if (!stat(git_path("MERGE_MSG"), &statbuf)) {
-               if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0)
+       } else if (!stat(git_path_merge_msg(), &statbuf)) {
+               if (strbuf_read_file(&sb, git_path_merge_msg(), 0) < 0)
                        die_errno(_("could not read MERGE_MSG"));
                hook_arg1 = "merge";
-       } else if (!stat(git_path("SQUASH_MSG"), &statbuf)) {
-               if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0)
+       } else if (!stat(git_path_squash_msg(), &statbuf)) {
+               if (strbuf_read_file(&sb, git_path_squash_msg(), 0) < 0)
                        die_errno(_("could not read SQUASH_MSG"));
                hook_arg1 = "squash";
        } else if (template_file) {
@@ -856,7 +856,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                                _("%s"
                                "Date:      %s"),
                                ident_shown++ ? "" : "\n",
-                               show_ident_date(&ai, DATE_NORMAL));
+                               show_ident_date(&ai, DATE_MODE(NORMAL)));
 
                if (!committer_ident_sufficiently_given())
                        status_printf_ln(s, GIT_COLOR_NORMAL,
@@ -1046,7 +1046,7 @@ static const char *find_author_by_nickname(const char *name)
        commit = get_revision(&revs);
        if (commit) {
                struct pretty_print_context ctx = {0};
-               ctx.date_mode = DATE_NORMAL;
+               ctx.date_mode.type = DATE_NORMAL;
                strbuf_release(&buf);
                format_commit_message(commit, "%aN <%aE>", &buf, &ctx);
                clear_mailmap(&mailmap);
@@ -1684,10 +1684,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                if (!reflog_msg)
                        reflog_msg = "commit (merge)";
                pptr = &commit_list_insert(current_head, pptr)->next;
-               fp = fopen(git_path("MERGE_HEAD"), "r");
+               fp = fopen(git_path_merge_head(), "r");
                if (fp == NULL)
                        die_errno(_("could not open '%s' for reading"),
-                                 git_path("MERGE_HEAD"));
+                                 git_path_merge_head());
                while (strbuf_getline(&m, fp, '\n') != EOF) {
                        struct commit *parent;
 
@@ -1698,8 +1698,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                }
                fclose(fp);
                strbuf_release(&m);
-               if (!stat(git_path("MERGE_MODE"), &statbuf)) {
-                       if (strbuf_read_file(&sb, git_path("MERGE_MODE"), 0) < 0)
+               if (!stat(git_path_merge_mode(), &statbuf)) {
+                       if (strbuf_read_file(&sb, git_path_merge_mode(), 0) < 0)
                                die_errno(_("could not read MERGE_MODE"));
                        if (!strcmp(sb.buf, "no-ff"))
                                allow_fast_forward = 0;
@@ -1775,12 +1775,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
        }
        ref_transaction_free(transaction);
 
-       unlink(git_path("CHERRY_PICK_HEAD"));
-       unlink(git_path("REVERT_HEAD"));
-       unlink(git_path("MERGE_HEAD"));
-       unlink(git_path("MERGE_MSG"));
-       unlink(git_path("MERGE_MODE"));
-       unlink(git_path("SQUASH_MSG"));
+       unlink(git_path_cherry_pick_head());
+       unlink(git_path_revert_head());
+       unlink(git_path_merge_head());
+       unlink(git_path_merge_msg());
+       unlink(git_path_merge_mode());
+       unlink(git_path_squash_msg());
 
        if (commit_index_files())
                die (_("Repository has been updated, but unable to write\n"
index b8182c241dad4fd7221432cb5b40674f76789be4..d23f3beba9b35a65c3de63d3843ca6158aef72f0 100644 (file)
@@ -5,6 +5,7 @@
  */
 #include "builtin.h"
 #include "cache.h"
+#include "refs.h"
 #include "commit.h"
 #include "object.h"
 #include "tag.h"
index 8d5b2dba2bc4251a7c35bc6aa6ff260ea72b7455..d3a08545ad66b518a88ff94d21af4234391a986f 100644 (file)
@@ -591,7 +591,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
        const char *what, *kind;
        struct ref *rm;
        char *url;
-       const char *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
+       const char *filename = dry_run ? "/dev/null" : git_path_fetch_head();
        int want_status;
 
        fp = fopen(filename, "a");
@@ -790,20 +790,29 @@ static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map,
        if (4 < i && !strncmp(".git", url + i - 3, 4))
                url_len = i - 3;
 
-       for (ref = stale_refs; ref; ref = ref->next) {
-               if (!dry_run)
-                       result |= delete_ref(ref->name, NULL, 0);
-               if (verbosity >= 0 && !shown_url) {
-                       fprintf(stderr, _("From %.*s\n"), url_len, url);
-                       shown_url = 1;
-               }
-               if (verbosity >= 0) {
+       if (!dry_run) {
+               struct string_list refnames = STRING_LIST_INIT_NODUP;
+
+               for (ref = stale_refs; ref; ref = ref->next)
+                       string_list_append(&refnames, ref->name);
+
+               result = delete_refs(&refnames);
+               string_list_clear(&refnames, 0);
+       }
+
+       if (verbosity >= 0) {
+               for (ref = stale_refs; ref; ref = ref->next) {
+                       if (!shown_url) {
+                               fprintf(stderr, _("From %.*s\n"), url_len, url);
+                               shown_url = 1;
+                       }
                        fprintf(stderr, " x %-*s %-*s -> %s\n",
                                TRANSPORT_SUMMARY(_("[deleted]")),
                                REFCOL_WIDTH, _("(none)"), prettify_refname(ref->name));
                        warn_dangling_symref(stderr, dangling_msg, ref->name);
                }
        }
+
        free(url);
        free_refs(stale_refs);
        return result;
@@ -825,7 +834,7 @@ static void check_not_current_branch(struct ref *ref_map)
 
 static int truncate_fetch_head(void)
 {
-       const char *filename = git_path("FETCH_HEAD");
+       const char *filename = git_path_fetch_head();
        FILE *fp = fopen(filename, "w");
 
        if (!fp)
@@ -979,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');
                }
        }
 
index 05f4c263112ae94978e4961988e9a4805fde2427..4ba7f282a5f6cd2e0af643f865a34c3048f2f591 100644 (file)
@@ -1,5 +1,6 @@
 #include "builtin.h"
 #include "cache.h"
+#include "refs.h"
 #include "commit.h"
 #include "diff.h"
 #include "revision.h"
index cb7db230d3ef76ee6a8b73971cbdabcc6d6590a9..7919206187c9968cc974c10bb9b5f75c7574e630 100644 (file)
 #include "cache.h"
 #include "refs.h"
 #include "object.h"
-#include "tag.h"
-#include "commit.h"
-#include "tree.h"
-#include "blob.h"
-#include "quote.h"
 #include "parse-options.h"
-#include "remote.h"
-#include "color.h"
-
-/* Quoting styles */
-#define QUOTE_NONE 0
-#define QUOTE_SHELL 1
-#define QUOTE_PERL 2
-#define QUOTE_PYTHON 4
-#define QUOTE_TCL 8
-
-typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
-
-struct atom_value {
-       const char *s;
-       unsigned long ul; /* used for sorting when not FIELD_STR */
-};
-
-struct ref_sort {
-       struct ref_sort *next;
-       int atom; /* index into used_atom array */
-       unsigned reverse : 1;
-};
-
-struct refinfo {
-       char *refname;
-       unsigned char objectname[20];
-       int flag;
-       const char *symref;
-       struct atom_value *value;
-};
-
-static struct {
-       const char *name;
-       cmp_type cmp_type;
-} valid_atom[] = {
-       { "refname" },
-       { "objecttype" },
-       { "objectsize", FIELD_ULONG },
-       { "objectname" },
-       { "tree" },
-       { "parent" },
-       { "numparent", FIELD_ULONG },
-       { "object" },
-       { "type" },
-       { "tag" },
-       { "author" },
-       { "authorname" },
-       { "authoremail" },
-       { "authordate", FIELD_TIME },
-       { "committer" },
-       { "committername" },
-       { "committeremail" },
-       { "committerdate", FIELD_TIME },
-       { "tagger" },
-       { "taggername" },
-       { "taggeremail" },
-       { "taggerdate", FIELD_TIME },
-       { "creator" },
-       { "creatordate", FIELD_TIME },
-       { "subject" },
-       { "body" },
-       { "contents" },
-       { "contents:subject" },
-       { "contents:body" },
-       { "contents:signature" },
-       { "upstream" },
-       { "push" },
-       { "symref" },
-       { "flag" },
-       { "HEAD" },
-       { "color" },
-};
-
-/*
- * An atom is a valid field atom listed above, possibly prefixed with
- * a "*" to denote deref_tag().
- *
- * We parse given format string and sort specifiers, and make a list
- * of properties that we need to extract out of objects.  refinfo
- * structure will hold an array of values extracted that can be
- * indexed with the "atom number", which is an index into this
- * array.
- */
-static const char **used_atom;
-static cmp_type *used_atom_type;
-static int used_atom_cnt, need_tagged, need_symref;
-static int need_color_reset_at_eol;
-
-/*
- * Used to parse format string and sort specifiers
- */
-static int parse_atom(const char *atom, const char *ep)
-{
-       const char *sp;
-       int i, at;
-
-       sp = atom;
-       if (*sp == '*' && sp < ep)
-               sp++; /* deref */
-       if (ep <= sp)
-               die("malformed field name: %.*s", (int)(ep-atom), atom);
-
-       /* Do we have the atom already used elsewhere? */
-       for (i = 0; i < used_atom_cnt; i++) {
-               int len = strlen(used_atom[i]);
-               if (len == ep - atom && !memcmp(used_atom[i], atom, len))
-                       return i;
-       }
-
-       /* Is the atom a valid one? */
-       for (i = 0; i < ARRAY_SIZE(valid_atom); i++) {
-               int len = strlen(valid_atom[i].name);
-               /*
-                * If the atom name has a colon, strip it and everything after
-                * it off - it specifies the format for this entry, and
-                * shouldn't be used for checking against the valid_atom
-                * table.
-                */
-               const char *formatp = strchr(sp, ':');
-               if (!formatp || ep < formatp)
-                       formatp = ep;
-               if (len == formatp - sp && !memcmp(valid_atom[i].name, sp, len))
-                       break;
-       }
-
-       if (ARRAY_SIZE(valid_atom) <= i)
-               die("unknown field name: %.*s", (int)(ep-atom), atom);
-
-       /* Add it in, including the deref prefix */
-       at = used_atom_cnt;
-       used_atom_cnt++;
-       REALLOC_ARRAY(used_atom, used_atom_cnt);
-       REALLOC_ARRAY(used_atom_type, used_atom_cnt);
-       used_atom[at] = xmemdupz(atom, ep - atom);
-       used_atom_type[at] = valid_atom[i].cmp_type;
-       if (*atom == '*')
-               need_tagged = 1;
-       if (!strcmp(used_atom[at], "symref"))
-               need_symref = 1;
-       return at;
-}
-
-/*
- * In a format string, find the next occurrence of %(atom).
- */
-static const char *find_next(const char *cp)
-{
-       while (*cp) {
-               if (*cp == '%') {
-                       /*
-                        * %( is the start of an atom;
-                        * %% is a quoted per-cent.
-                        */
-                       if (cp[1] == '(')
-                               return cp;
-                       else if (cp[1] == '%')
-                               cp++; /* skip over two % */
-                       /* otherwise this is a singleton, literal % */
-               }
-               cp++;
-       }
-       return NULL;
-}
-
-/*
- * Make sure the format string is well formed, and parse out
- * the used atoms.
- */
-static int verify_format(const char *format)
-{
-       const char *cp, *sp;
-
-       need_color_reset_at_eol = 0;
-       for (cp = format; *cp && (sp = find_next(cp)); ) {
-               const char *color, *ep = strchr(sp, ')');
-               int at;
-
-               if (!ep)
-                       return error("malformed format string %s", sp);
-               /* sp points at "%(" and ep points at the closing ")" */
-               at = parse_atom(sp + 2, ep);
-               cp = ep + 1;
-
-               if (skip_prefix(used_atom[at], "color:", &color))
-                       need_color_reset_at_eol = !!strcmp(color, "reset");
-       }
-       return 0;
-}
-
-/*
- * Given an object name, read the object data and size, and return a
- * "struct object".  If the object data we are returning is also borrowed
- * by the "struct object" representation, set *eaten as well---it is a
- * signal from parse_object_buffer to us not to free the buffer.
- */
-static void *get_obj(const unsigned char *sha1, struct object **obj, unsigned long *sz, int *eaten)
-{
-       enum object_type type;
-       void *buf = read_sha1_file(sha1, &type, sz);
-
-       if (buf)
-               *obj = parse_object_buffer(sha1, type, *sz, buf, eaten);
-       else
-               *obj = NULL;
-       return buf;
-}
-
-static int grab_objectname(const char *name, const unsigned char *sha1,
-                           struct atom_value *v)
-{
-       if (!strcmp(name, "objectname")) {
-               char *s = xmalloc(41);
-               strcpy(s, sha1_to_hex(sha1));
-               v->s = s;
-               return 1;
-       }
-       if (!strcmp(name, "objectname:short")) {
-               v->s = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV));
-               return 1;
-       }
-       return 0;
-}
-
-/* See grab_values */
-static void grab_common_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
-{
-       int i;
-
-       for (i = 0; i < used_atom_cnt; i++) {
-               const char *name = used_atom[i];
-               struct atom_value *v = &val[i];
-               if (!!deref != (*name == '*'))
-                       continue;
-               if (deref)
-                       name++;
-               if (!strcmp(name, "objecttype"))
-                       v->s = typename(obj->type);
-               else if (!strcmp(name, "objectsize")) {
-                       char *s = xmalloc(40);
-                       sprintf(s, "%lu", sz);
-                       v->ul = sz;
-                       v->s = s;
-               }
-               else if (deref)
-                       grab_objectname(name, obj->sha1, v);
-       }
-}
-
-/* See grab_values */
-static void grab_tag_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
-{
-       int i;
-       struct tag *tag = (struct tag *) obj;
-
-       for (i = 0; i < used_atom_cnt; i++) {
-               const char *name = used_atom[i];
-               struct atom_value *v = &val[i];
-               if (!!deref != (*name == '*'))
-                       continue;
-               if (deref)
-                       name++;
-               if (!strcmp(name, "tag"))
-                       v->s = tag->tag;
-               else if (!strcmp(name, "type") && tag->tagged)
-                       v->s = typename(tag->tagged->type);
-               else if (!strcmp(name, "object") && tag->tagged) {
-                       char *s = xmalloc(41);
-                       strcpy(s, sha1_to_hex(tag->tagged->sha1));
-                       v->s = s;
-               }
-       }
-}
-
-/* See grab_values */
-static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
-{
-       int i;
-       struct commit *commit = (struct commit *) obj;
-
-       for (i = 0; i < used_atom_cnt; i++) {
-               const char *name = used_atom[i];
-               struct atom_value *v = &val[i];
-               if (!!deref != (*name == '*'))
-                       continue;
-               if (deref)
-                       name++;
-               if (!strcmp(name, "tree")) {
-                       char *s = xmalloc(41);
-                       strcpy(s, sha1_to_hex(commit->tree->object.sha1));
-                       v->s = s;
-               }
-               if (!strcmp(name, "numparent")) {
-                       char *s = xmalloc(40);
-                       v->ul = commit_list_count(commit->parents);
-                       sprintf(s, "%lu", v->ul);
-                       v->s = s;
-               }
-               else if (!strcmp(name, "parent")) {
-                       int num = commit_list_count(commit->parents);
-                       int i;
-                       struct commit_list *parents;
-                       char *s = xmalloc(41 * num + 1);
-                       v->s = s;
-                       for (i = 0, parents = commit->parents;
-                            parents;
-                            parents = parents->next, i = i + 41) {
-                               struct commit *parent = parents->item;
-                               strcpy(s+i, sha1_to_hex(parent->object.sha1));
-                               if (parents->next)
-                                       s[i+40] = ' ';
-                       }
-                       if (!i)
-                               *s = '\0';
-               }
-       }
-}
-
-static const char *find_wholine(const char *who, int wholen, const char *buf, unsigned long sz)
-{
-       const char *eol;
-       while (*buf) {
-               if (!strncmp(buf, who, wholen) &&
-                   buf[wholen] == ' ')
-                       return buf + wholen + 1;
-               eol = strchr(buf, '\n');
-               if (!eol)
-                       return "";
-               eol++;
-               if (*eol == '\n')
-                       return ""; /* end of header */
-               buf = eol;
-       }
-       return "";
-}
-
-static const char *copy_line(const char *buf)
-{
-       const char *eol = strchrnul(buf, '\n');
-       return xmemdupz(buf, eol - buf);
-}
-
-static const char *copy_name(const char *buf)
-{
-       const char *cp;
-       for (cp = buf; *cp && *cp != '\n'; cp++) {
-               if (!strncmp(cp, " <", 2))
-                       return xmemdupz(buf, cp - buf);
-       }
-       return "";
-}
-
-static const char *copy_email(const char *buf)
-{
-       const char *email = strchr(buf, '<');
-       const char *eoemail;
-       if (!email)
-               return "";
-       eoemail = strchr(email, '>');
-       if (!eoemail)
-               return "";
-       return xmemdupz(email, eoemail + 1 - email);
-}
-
-static char *copy_subject(const char *buf, unsigned long len)
-{
-       char *r = xmemdupz(buf, len);
-       int i;
-
-       for (i = 0; i < len; i++)
-               if (r[i] == '\n')
-                       r[i] = ' ';
-
-       return r;
-}
-
-static void grab_date(const char *buf, struct atom_value *v, const char *atomname)
-{
-       const char *eoemail = strstr(buf, "> ");
-       char *zone;
-       unsigned long timestamp;
-       long tz;
-       enum date_mode date_mode = DATE_NORMAL;
-       const char *formatp;
-
-       /*
-        * We got here because atomname ends in "date" or "date<something>";
-        * it's not possible that <something> is not ":<format>" because
-        * parse_atom() wouldn't have allowed it, so we can assume that no
-        * ":" means no format is specified, and use the default.
-        */
-       formatp = strchr(atomname, ':');
-       if (formatp != NULL) {
-               formatp++;
-               date_mode = parse_date_format(formatp);
-       }
-
-       if (!eoemail)
-               goto bad;
-       timestamp = strtoul(eoemail + 2, &zone, 10);
-       if (timestamp == ULONG_MAX)
-               goto bad;
-       tz = strtol(zone, NULL, 10);
-       if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE)
-               goto bad;
-       v->s = xstrdup(show_date(timestamp, tz, date_mode));
-       v->ul = timestamp;
-       return;
- bad:
-       v->s = "";
-       v->ul = 0;
-}
-
-/* See grab_values */
-static void grab_person(const char *who, struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
-{
-       int i;
-       int wholen = strlen(who);
-       const char *wholine = NULL;
-
-       for (i = 0; i < used_atom_cnt; i++) {
-               const char *name = used_atom[i];
-               struct atom_value *v = &val[i];
-               if (!!deref != (*name == '*'))
-                       continue;
-               if (deref)
-                       name++;
-               if (strncmp(who, name, wholen))
-                       continue;
-               if (name[wholen] != 0 &&
-                   strcmp(name + wholen, "name") &&
-                   strcmp(name + wholen, "email") &&
-                   !starts_with(name + wholen, "date"))
-                       continue;
-               if (!wholine)
-                       wholine = find_wholine(who, wholen, buf, sz);
-               if (!wholine)
-                       return; /* no point looking for it */
-               if (name[wholen] == 0)
-                       v->s = copy_line(wholine);
-               else if (!strcmp(name + wholen, "name"))
-                       v->s = copy_name(wholine);
-               else if (!strcmp(name + wholen, "email"))
-                       v->s = copy_email(wholine);
-               else if (starts_with(name + wholen, "date"))
-                       grab_date(wholine, v, name);
-       }
-
-       /*
-        * For a tag or a commit object, if "creator" or "creatordate" is
-        * requested, do something special.
-        */
-       if (strcmp(who, "tagger") && strcmp(who, "committer"))
-               return; /* "author" for commit object is not wanted */
-       if (!wholine)
-               wholine = find_wholine(who, wholen, buf, sz);
-       if (!wholine)
-               return;
-       for (i = 0; i < used_atom_cnt; i++) {
-               const char *name = used_atom[i];
-               struct atom_value *v = &val[i];
-               if (!!deref != (*name == '*'))
-                       continue;
-               if (deref)
-                       name++;
-
-               if (starts_with(name, "creatordate"))
-                       grab_date(wholine, v, name);
-               else if (!strcmp(name, "creator"))
-                       v->s = copy_line(wholine);
-       }
-}
-
-static void find_subpos(const char *buf, unsigned long sz,
-                       const char **sub, unsigned long *sublen,
-                       const char **body, unsigned long *bodylen,
-                       unsigned long *nonsiglen,
-                       const char **sig, unsigned long *siglen)
-{
-       const char *eol;
-       /* skip past header until we hit empty line */
-       while (*buf && *buf != '\n') {
-               eol = strchrnul(buf, '\n');
-               if (*eol)
-                       eol++;
-               buf = eol;
-       }
-       /* skip any empty lines */
-       while (*buf == '\n')
-               buf++;
-
-       /* parse signature first; we might not even have a subject line */
-       *sig = buf + parse_signature(buf, strlen(buf));
-       *siglen = strlen(*sig);
-
-       /* subject is first non-empty line */
-       *sub = buf;
-       /* subject goes to first empty line */
-       while (buf < *sig && *buf && *buf != '\n') {
-               eol = strchrnul(buf, '\n');
-               if (*eol)
-                       eol++;
-               buf = eol;
-       }
-       *sublen = buf - *sub;
-       /* drop trailing newline, if present */
-       if (*sublen && (*sub)[*sublen - 1] == '\n')
-               *sublen -= 1;
-
-       /* skip any empty lines */
-       while (*buf == '\n')
-               buf++;
-       *body = buf;
-       *bodylen = strlen(buf);
-       *nonsiglen = *sig - buf;
-}
-
-/* See grab_values */
-static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
-{
-       int i;
-       const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;
-       unsigned long sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0;
-
-       for (i = 0; i < used_atom_cnt; i++) {
-               const char *name = used_atom[i];
-               struct atom_value *v = &val[i];
-               if (!!deref != (*name == '*'))
-                       continue;
-               if (deref)
-                       name++;
-               if (strcmp(name, "subject") &&
-                   strcmp(name, "body") &&
-                   strcmp(name, "contents") &&
-                   strcmp(name, "contents:subject") &&
-                   strcmp(name, "contents:body") &&
-                   strcmp(name, "contents:signature"))
-                       continue;
-               if (!subpos)
-                       find_subpos(buf, sz,
-                                   &subpos, &sublen,
-                                   &bodypos, &bodylen, &nonsiglen,
-                                   &sigpos, &siglen);
-
-               if (!strcmp(name, "subject"))
-                       v->s = copy_subject(subpos, sublen);
-               else if (!strcmp(name, "contents:subject"))
-                       v->s = copy_subject(subpos, sublen);
-               else if (!strcmp(name, "body"))
-                       v->s = xmemdupz(bodypos, bodylen);
-               else if (!strcmp(name, "contents:body"))
-                       v->s = xmemdupz(bodypos, nonsiglen);
-               else if (!strcmp(name, "contents:signature"))
-                       v->s = xmemdupz(sigpos, siglen);
-               else if (!strcmp(name, "contents"))
-                       v->s = xstrdup(subpos);
-       }
-}
-
-/*
- * We want to have empty print-string for field requests
- * that do not apply (e.g. "authordate" for a tag object)
- */
-static void fill_missing_values(struct atom_value *val)
-{
-       int i;
-       for (i = 0; i < used_atom_cnt; i++) {
-               struct atom_value *v = &val[i];
-               if (v->s == NULL)
-                       v->s = "";
-       }
-}
-
-/*
- * val is a list of atom_value to hold returned values.  Extract
- * the values for atoms in used_atom array out of (obj, buf, sz).
- * when deref is false, (obj, buf, sz) is the object that is
- * pointed at by the ref itself; otherwise it is the object the
- * ref (which is a tag) refers to.
- */
-static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
-{
-       grab_common_values(val, deref, obj, buf, sz);
-       switch (obj->type) {
-       case OBJ_TAG:
-               grab_tag_values(val, deref, obj, buf, sz);
-               grab_sub_body_contents(val, deref, obj, buf, sz);
-               grab_person("tagger", val, deref, obj, buf, sz);
-               break;
-       case OBJ_COMMIT:
-               grab_commit_values(val, deref, obj, buf, sz);
-               grab_sub_body_contents(val, deref, obj, buf, sz);
-               grab_person("author", val, deref, obj, buf, sz);
-               grab_person("committer", val, deref, obj, buf, sz);
-               break;
-       case OBJ_TREE:
-               /* grab_tree_values(val, deref, obj, buf, sz); */
-               break;
-       case OBJ_BLOB:
-               /* grab_blob_values(val, deref, obj, buf, sz); */
-               break;
-       default:
-               die("Eh?  Object of type %d?", obj->type);
-       }
-}
-
-static inline char *copy_advance(char *dst, const char *src)
-{
-       while (*src)
-               *dst++ = *src++;
-       return dst;
-}
-
-/*
- * Parse the object referred by ref, and grab needed value.
- */
-static void populate_value(struct refinfo *ref)
-{
-       void *buf;
-       struct object *obj;
-       int eaten, i;
-       unsigned long size;
-       const unsigned char *tagged;
-
-       ref->value = xcalloc(used_atom_cnt, sizeof(struct atom_value));
-
-       if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) {
-               unsigned char unused1[20];
-               ref->symref = resolve_refdup(ref->refname, RESOLVE_REF_READING,
-                                            unused1, NULL);
-               if (!ref->symref)
-                       ref->symref = "";
-       }
-
-       /* Fill in specials first */
-       for (i = 0; i < used_atom_cnt; i++) {
-               const char *name = used_atom[i];
-               struct atom_value *v = &ref->value[i];
-               int deref = 0;
-               const char *refname;
-               const char *formatp;
-               struct branch *branch = NULL;
-
-               if (*name == '*') {
-                       deref = 1;
-                       name++;
-               }
-
-               if (starts_with(name, "refname"))
-                       refname = ref->refname;
-               else if (starts_with(name, "symref"))
-                       refname = ref->symref ? ref->symref : "";
-               else if (starts_with(name, "upstream")) {
-                       const char *branch_name;
-                       /* only local branches may have an upstream */
-                       if (!skip_prefix(ref->refname, "refs/heads/",
-                                        &branch_name))
-                               continue;
-                       branch = branch_get(branch_name);
-
-                       refname = branch_get_upstream(branch, NULL);
-                       if (!refname)
-                               continue;
-               } else if (starts_with(name, "push")) {
-                       const char *branch_name;
-                       if (!skip_prefix(ref->refname, "refs/heads/",
-                                        &branch_name))
-                               continue;
-                       branch = branch_get(branch_name);
-
-                       refname = branch_get_push(branch, NULL);
-                       if (!refname)
-                               continue;
-               } else if (starts_with(name, "color:")) {
-                       char color[COLOR_MAXLEN] = "";
-
-                       if (color_parse(name + 6, color) < 0)
-                               die(_("unable to parse format"));
-                       v->s = xstrdup(color);
-                       continue;
-               } else if (!strcmp(name, "flag")) {
-                       char buf[256], *cp = buf;
-                       if (ref->flag & REF_ISSYMREF)
-                               cp = copy_advance(cp, ",symref");
-                       if (ref->flag & REF_ISPACKED)
-                               cp = copy_advance(cp, ",packed");
-                       if (cp == buf)
-                               v->s = "";
-                       else {
-                               *cp = '\0';
-                               v->s = xstrdup(buf + 1);
-                       }
-                       continue;
-               } else if (!deref && grab_objectname(name, ref->objectname, v)) {
-                       continue;
-               } else if (!strcmp(name, "HEAD")) {
-                       const char *head;
-                       unsigned char sha1[20];
-
-                       head = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
-                                                 sha1, NULL);
-                       if (!strcmp(ref->refname, head))
-                               v->s = "*";
-                       else
-                               v->s = " ";
-                       continue;
-               } else
-                       continue;
-
-               formatp = strchr(name, ':');
-               if (formatp) {
-                       int num_ours, num_theirs;
-
-                       formatp++;
-                       if (!strcmp(formatp, "short"))
-                               refname = shorten_unambiguous_ref(refname,
-                                                     warn_ambiguous_refs);
-                       else if (!strcmp(formatp, "track") &&
-                                (starts_with(name, "upstream") ||
-                                 starts_with(name, "push"))) {
-                               char buf[40];
-
-                               if (stat_tracking_info(branch, &num_ours,
-                                                      &num_theirs, NULL))
-                                       continue;
-
-                               if (!num_ours && !num_theirs)
-                                       v->s = "";
-                               else if (!num_ours) {
-                                       sprintf(buf, "[behind %d]", num_theirs);
-                                       v->s = xstrdup(buf);
-                               } else if (!num_theirs) {
-                                       sprintf(buf, "[ahead %d]", num_ours);
-                                       v->s = xstrdup(buf);
-                               } else {
-                                       sprintf(buf, "[ahead %d, behind %d]",
-                                               num_ours, num_theirs);
-                                       v->s = xstrdup(buf);
-                               }
-                               continue;
-                       } else if (!strcmp(formatp, "trackshort") &&
-                                  (starts_with(name, "upstream") ||
-                                   starts_with(name, "push"))) {
-                               assert(branch);
-
-                               if (stat_tracking_info(branch, &num_ours,
-                                                       &num_theirs, NULL))
-                                       continue;
-
-                               if (!num_ours && !num_theirs)
-                                       v->s = "=";
-                               else if (!num_ours)
-                                       v->s = "<";
-                               else if (!num_theirs)
-                                       v->s = ">";
-                               else
-                                       v->s = "<>";
-                               continue;
-                       } else
-                               die("unknown %.*s format %s",
-                                   (int)(formatp - name), name, formatp);
-               }
-
-               if (!deref)
-                       v->s = refname;
-               else {
-                       int len = strlen(refname);
-                       char *s = xmalloc(len + 4);
-                       sprintf(s, "%s^{}", refname);
-                       v->s = s;
-               }
-       }
-
-       for (i = 0; i < used_atom_cnt; i++) {
-               struct atom_value *v = &ref->value[i];
-               if (v->s == NULL)
-                       goto need_obj;
-       }
-       return;
-
- need_obj:
-       buf = get_obj(ref->objectname, &obj, &size, &eaten);
-       if (!buf)
-               die("missing object %s for %s",
-                   sha1_to_hex(ref->objectname), ref->refname);
-       if (!obj)
-               die("parse_object_buffer failed on %s for %s",
-                   sha1_to_hex(ref->objectname), ref->refname);
-
-       grab_values(ref->value, 0, obj, buf, size);
-       if (!eaten)
-               free(buf);
-
-       /*
-        * If there is no atom that wants to know about tagged
-        * object, we are done.
-        */
-       if (!need_tagged || (obj->type != OBJ_TAG))
-               return;
-
-       /*
-        * If it is a tag object, see if we use a value that derefs
-        * the object, and if we do grab the object it refers to.
-        */
-       tagged = ((struct tag *)obj)->tagged->sha1;
-
-       /*
-        * NEEDSWORK: This derefs tag only once, which
-        * is good to deal with chains of trust, but
-        * is not consistent with what deref_tag() does
-        * which peels the onion to the core.
-        */
-       buf = get_obj(tagged, &obj, &size, &eaten);
-       if (!buf)
-               die("missing object %s for %s",
-                   sha1_to_hex(tagged), ref->refname);
-       if (!obj)
-               die("parse_object_buffer failed on %s for %s",
-                   sha1_to_hex(tagged), ref->refname);
-       grab_values(ref->value, 1, obj, buf, size);
-       if (!eaten)
-               free(buf);
-}
-
-/*
- * Given a ref, return the value for the atom.  This lazily gets value
- * out of the object by calling populate value.
- */
-static void get_value(struct refinfo *ref, int atom, struct atom_value **v)
-{
-       if (!ref->value) {
-               populate_value(ref);
-               fill_missing_values(ref->value);
-       }
-       *v = &ref->value[atom];
-}
-
-struct grab_ref_cbdata {
-       struct refinfo **grab_array;
-       const char **grab_pattern;
-       int grab_cnt;
-};
-
-/*
- * A call-back given to for_each_ref().  Filter refs and keep them for
- * later object processing.
- */
-static int grab_single_ref(const char *refname, const struct object_id *oid,
-                          int flag, void *cb_data)
-{
-       struct grab_ref_cbdata *cb = cb_data;
-       struct refinfo *ref;
-       int cnt;
-
-       if (flag & REF_BAD_NAME) {
-                 warning("ignoring ref with broken name %s", refname);
-                 return 0;
-       }
-
-       if (flag & REF_ISBROKEN) {
-                 warning("ignoring broken ref %s", refname);
-                 return 0;
-       }
-
-       if (*cb->grab_pattern) {
-               const char **pattern;
-               int namelen = strlen(refname);
-               for (pattern = cb->grab_pattern; *pattern; pattern++) {
-                       const char *p = *pattern;
-                       int plen = strlen(p);
-
-                       if ((plen <= namelen) &&
-                           !strncmp(refname, p, plen) &&
-                           (refname[plen] == '\0' ||
-                            refname[plen] == '/' ||
-                            p[plen-1] == '/'))
-                               break;
-                       if (!wildmatch(p, refname, WM_PATHNAME, NULL))
-                               break;
-               }
-               if (!*pattern)
-                       return 0;
-       }
-
-       /*
-        * We do not open the object yet; sort may only need refname
-        * to do its job and the resulting list may yet to be pruned
-        * by maxcount logic.
-        */
-       ref = xcalloc(1, sizeof(*ref));
-       ref->refname = xstrdup(refname);
-       hashcpy(ref->objectname, oid->hash);
-       ref->flag = flag;
-
-       cnt = cb->grab_cnt;
-       REALLOC_ARRAY(cb->grab_array, cnt + 1);
-       cb->grab_array[cnt++] = ref;
-       cb->grab_cnt = cnt;
-       return 0;
-}
-
-static int cmp_ref_sort(struct ref_sort *s, struct refinfo *a, struct refinfo *b)
-{
-       struct atom_value *va, *vb;
-       int cmp;
-       cmp_type cmp_type = used_atom_type[s->atom];
-
-       get_value(a, s->atom, &va);
-       get_value(b, s->atom, &vb);
-       switch (cmp_type) {
-       case FIELD_STR:
-               cmp = strcmp(va->s, vb->s);
-               break;
-       default:
-               if (va->ul < vb->ul)
-                       cmp = -1;
-               else if (va->ul == vb->ul)
-                       cmp = 0;
-               else
-                       cmp = 1;
-               break;
-       }
-       return (s->reverse) ? -cmp : cmp;
-}
-
-static struct ref_sort *ref_sort;
-static int compare_refs(const void *a_, const void *b_)
-{
-       struct refinfo *a = *((struct refinfo **)a_);
-       struct refinfo *b = *((struct refinfo **)b_);
-       struct ref_sort *s;
-
-       for (s = ref_sort; s; s = s->next) {
-               int cmp = cmp_ref_sort(s, a, b);
-               if (cmp)
-                       return cmp;
-       }
-       return 0;
-}
-
-static void sort_refs(struct ref_sort *sort, struct refinfo **refs, int num_refs)
-{
-       ref_sort = sort;
-       qsort(refs, num_refs, sizeof(struct refinfo *), compare_refs);
-}
-
-static void print_value(struct atom_value *v, int quote_style)
-{
-       struct strbuf sb = STRBUF_INIT;
-       switch (quote_style) {
-       case QUOTE_NONE:
-               fputs(v->s, stdout);
-               break;
-       case QUOTE_SHELL:
-               sq_quote_buf(&sb, v->s);
-               break;
-       case QUOTE_PERL:
-               perl_quote_buf(&sb, v->s);
-               break;
-       case QUOTE_PYTHON:
-               python_quote_buf(&sb, v->s);
-               break;
-       case QUOTE_TCL:
-               tcl_quote_buf(&sb, v->s);
-               break;
-       }
-       if (quote_style != QUOTE_NONE) {
-               fputs(sb.buf, stdout);
-               strbuf_release(&sb);
-       }
-}
-
-static int hex1(char ch)
-{
-       if ('0' <= ch && ch <= '9')
-               return ch - '0';
-       else if ('a' <= ch && ch <= 'f')
-               return ch - 'a' + 10;
-       else if ('A' <= ch && ch <= 'F')
-               return ch - 'A' + 10;
-       return -1;
-}
-static int hex2(const char *cp)
-{
-       if (cp[0] && cp[1])
-               return (hex1(cp[0]) << 4) | hex1(cp[1]);
-       else
-               return -1;
-}
-
-static void emit(const char *cp, const char *ep)
-{
-       while (*cp && (!ep || cp < ep)) {
-               if (*cp == '%') {
-                       if (cp[1] == '%')
-                               cp++;
-                       else {
-                               int ch = hex2(cp + 1);
-                               if (0 <= ch) {
-                                       putchar(ch);
-                                       cp += 3;
-                                       continue;
-                               }
-                       }
-               }
-               putchar(*cp);
-               cp++;
-       }
-}
-
-static void show_ref(struct refinfo *info, const char *format, int quote_style)
-{
-       const char *cp, *sp, *ep;
-
-       for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
-               struct atom_value *atomv;
-
-               ep = strchr(sp, ')');
-               if (cp < sp)
-                       emit(cp, sp);
-               get_value(info, parse_atom(sp + 2, ep), &atomv);
-               print_value(atomv, quote_style);
-       }
-       if (*cp) {
-               sp = cp + strlen(cp);
-               emit(cp, sp);
-       }
-       if (need_color_reset_at_eol) {
-               struct atom_value resetv;
-               char color[COLOR_MAXLEN] = "";
-
-               if (color_parse("reset", color) < 0)
-                       die("BUG: couldn't parse 'reset' as a color");
-               resetv.s = color;
-               print_value(&resetv, quote_style);
-       }
-       putchar('\n');
-}
-
-static struct ref_sort *default_sort(void)
-{
-       static const char cstr_name[] = "refname";
-
-       struct ref_sort *sort = xcalloc(1, sizeof(*sort));
-
-       sort->next = NULL;
-       sort->atom = parse_atom(cstr_name, cstr_name + strlen(cstr_name));
-       return sort;
-}
-
-static int opt_parse_sort(const struct option *opt, const char *arg, int unset)
-{
-       struct ref_sort **sort_tail = opt->value;
-       struct ref_sort *s;
-       int len;
-
-       if (!arg) /* should --no-sort void the list ? */
-               return -1;
-
-       s = xcalloc(1, sizeof(*s));
-       s->next = *sort_tail;
-       *sort_tail = s;
-
-       if (*arg == '-') {
-               s->reverse = 1;
-               arg++;
-       }
-       len = strlen(arg);
-       s->atom = parse_atom(arg, arg+len);
-       return 0;
-}
+#include "ref-filter.h"
 
 static char const * const for_each_ref_usage[] = {
        N_("git for-each-ref [<options>] [<pattern>]"),
@@ -1086,12 +12,12 @@ static char const * const for_each_ref_usage[] = {
 
 int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
 {
-       int i, num_refs;
+       int i;
        const char *format = "%(objectname) %(objecttype)\t%(refname)";
-       struct ref_sort *sort = NULL, **sort_tail = &sort;
+       struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
        int maxcount = 0, quote_style = 0;
-       struct refinfo **refs;
-       struct grab_ref_cbdata cbdata;
+       struct ref_array array;
+       struct ref_filter filter;
 
        struct option opts[] = {
                OPT_BIT('s', "shell", &quote_style,
@@ -1106,8 +32,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
                OPT_GROUP(""),
                OPT_INTEGER( 0 , "count", &maxcount, N_("show only <n> matched refs")),
                OPT_STRING(  0 , "format", &format, N_("format"), N_("format to use for the output")),
-               OPT_CALLBACK(0 , "sort", sort_tail, N_("key"),
-                           N_("field name to sort on"), &opt_parse_sort),
+               OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
+                           N_("field name to sort on"), &parse_opt_ref_sorting),
                OPT_END(),
        };
 
@@ -1120,26 +46,25 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
                error("more than one quoting style?");
                usage_with_options(for_each_ref_usage, opts);
        }
-       if (verify_format(format))
+       if (verify_ref_format(format))
                usage_with_options(for_each_ref_usage, opts);
 
-       if (!sort)
-               sort = default_sort();
+       if (!sorting)
+               sorting = ref_default_sorting();
 
        /* for warn_ambiguous_refs */
        git_config(git_default_config, NULL);
 
-       memset(&cbdata, 0, sizeof(cbdata));
-       cbdata.grab_pattern = argv;
-       for_each_rawref(grab_single_ref, &cbdata);
-       refs = cbdata.grab_array;
-       num_refs = cbdata.grab_cnt;
-
-       sort_refs(sort, refs, num_refs);
+       memset(&array, 0, sizeof(array));
+       memset(&filter, 0, sizeof(filter));
+       filter.name_patterns = argv;
+       filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN);
+       ref_array_sort(sorting, &array);
 
-       if (!maxcount || num_refs < maxcount)
-               maxcount = num_refs;
+       if (!maxcount || array.nr < maxcount)
+               maxcount = array.nr;
        for (i = 0; i < maxcount; i++)
-               show_ref(refs[i], format, quote_style);
+               show_ref_array_item(array.items[i], format, quote_style);
+       ref_array_clear(&array);
        return 0;
 }
index 267979304994158c491b2306388b932c50d7438b..079470342fc926b97ae1c065d37926cf5a449101 100644 (file)
@@ -23,8 +23,11 @@ static int show_tags;
 static int show_unreachable;
 static int include_reflogs = 1;
 static int check_full = 1;
+static int connectivity_only;
 static int check_strict;
 static int keep_cache_objects;
+static struct fsck_options fsck_walk_options = FSCK_OPTIONS_DEFAULT;
+static struct fsck_options fsck_obj_options = FSCK_OPTIONS_DEFAULT;
 static struct object_id head_oid;
 static const char *head_points_at;
 static int errors_found;
@@ -44,39 +47,52 @@ static int show_dangling = 1;
 #define DIRENT_SORT_HINT(de) ((de)->d_ino)
 #endif
 
-static void objreport(struct object *obj, const char *severity,
-                      const char *err, va_list params)
+static int fsck_config(const char *var, const char *value, void *cb)
 {
-       fprintf(stderr, "%s in %s %s: ",
-               severity, typename(obj->type), sha1_to_hex(obj->sha1));
-       vfprintf(stderr, err, params);
-       fputs("\n", stderr);
+       if (strcmp(var, "fsck.skiplist") == 0) {
+               const char *path;
+               struct strbuf sb = STRBUF_INIT;
+
+               if (git_config_pathname(&path, var, value))
+                       return 1;
+               strbuf_addf(&sb, "skiplist=%s", path);
+               free((char *)path);
+               fsck_set_msg_types(&fsck_obj_options, sb.buf);
+               strbuf_release(&sb);
+               return 0;
+       }
+
+       if (skip_prefix(var, "fsck.", &var)) {
+               fsck_set_msg_type(&fsck_obj_options, var, value);
+               return 0;
+       }
+
+       return git_default_config(var, value, cb);
+}
+
+static void objreport(struct object *obj, const char *msg_type,
+                       const char *err)
+{
+       fprintf(stderr, "%s in %s %s: %s\n",
+               msg_type, typename(obj->type), sha1_to_hex(obj->sha1), err);
 }
 
-__attribute__((format (printf, 2, 3)))
-static int objerror(struct object *obj, const char *err, ...)
+static int objerror(struct object *obj, const char *err)
 {
-       va_list params;
-       va_start(params, err);
        errors_found |= ERROR_OBJECT;
-       objreport(obj, "error", err, params);
-       va_end(params);
+       objreport(obj, "error", err);
        return -1;
 }
 
-__attribute__((format (printf, 3, 4)))
-static int fsck_error_func(struct object *obj, int type, const char *err, ...)
+static int fsck_error_func(struct object *obj, int type, const char *message)
 {
-       va_list params;
-       va_start(params, err);
-       objreport(obj, (type == FSCK_WARN) ? "warning" : "error", err, params);
-       va_end(params);
+       objreport(obj, (type == FSCK_WARN) ? "warning" : "error", message);
        return (type == FSCK_WARN) ? 0 : 1;
 }
 
 static struct object_array pending;
 
-static int mark_object(struct object *obj, int type, void *data)
+static int mark_object(struct object *obj, int type, void *data, struct fsck_options *options)
 {
        struct object *parent = data;
 
@@ -119,7 +135,7 @@ static int mark_object(struct object *obj, int type, void *data)
 
 static void mark_object_reachable(struct object *obj)
 {
-       mark_object(obj, OBJ_ANY, NULL);
+       mark_object(obj, OBJ_ANY, NULL, NULL);
 }
 
 static int traverse_one_object(struct object *obj)
@@ -132,7 +148,7 @@ static int traverse_one_object(struct object *obj)
                if (parse_tree(tree) < 0)
                        return 1; /* error already displayed */
        }
-       result = fsck_walk(obj, mark_object, obj);
+       result = fsck_walk(obj, obj, &fsck_walk_options);
        if (tree)
                free_tree_buffer(tree);
        return result;
@@ -158,7 +174,7 @@ static int traverse_reachable(void)
        return !!result;
 }
 
-static int mark_used(struct object *obj, int type, void *data)
+static int mark_used(struct object *obj, int type, void *data, struct fsck_options *options)
 {
        if (!obj)
                return 1;
@@ -179,6 +195,8 @@ static void check_reachable_object(struct object *obj)
        if (!(obj->flags & HAS_OBJ)) {
                if (has_sha1_pack(obj->sha1))
                        return; /* it is in pack - forget about it */
+               if (connectivity_only && has_sha1_file(obj->sha1))
+                       return;
                printf("missing %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1));
                errors_found |= ERROR_REACHABLE;
                return;
@@ -225,13 +243,14 @@ static void check_unreachable_object(struct object *obj)
                        printf("dangling %s %s\n", typename(obj->type),
                               sha1_to_hex(obj->sha1));
                if (write_lost_and_found) {
-                       const char *filename = git_path("lost-found/%s/%s",
+                       char *filename = git_pathdup("lost-found/%s/%s",
                                obj->type == OBJ_COMMIT ? "commit" : "other",
                                sha1_to_hex(obj->sha1));
                        FILE *f;
 
                        if (safe_create_leading_directories_const(filename)) {
                                error("Could not create lost-found");
+                               free(filename);
                                return;
                        }
                        if (!(f = fopen(filename, "w")))
@@ -244,6 +263,7 @@ static void check_unreachable_object(struct object *obj)
                        if (fclose(f))
                                die_errno("Could not finish '%s'",
                                          filename);
+                       free(filename);
                }
                return;
        }
@@ -296,9 +316,9 @@ static int fsck_obj(struct object *obj)
                fprintf(stderr, "Checking %s %s\n",
                        typename(obj->type), sha1_to_hex(obj->sha1));
 
-       if (fsck_walk(obj, mark_used, NULL))
+       if (fsck_walk(obj, NULL, &fsck_obj_options))
                objerror(obj, "broken links");
-       if (fsck_object(obj, NULL, 0, check_strict, fsck_error_func))
+       if (fsck_object(obj, NULL, 0, &fsck_obj_options))
                return -1;
 
        if (obj->type == OBJ_TREE) {
@@ -621,6 +641,7 @@ static struct option fsck_opts[] = {
        OPT_BOOL(0, "cache", &keep_cache_objects, N_("make index objects head nodes")),
        OPT_BOOL(0, "reflogs", &include_reflogs, N_("make reflogs head nodes (default)")),
        OPT_BOOL(0, "full", &check_full, N_("also consider packs and alternate objects")),
+       OPT_BOOL(0, "connectivity-only", &connectivity_only, N_("check only connectivity")),
        OPT_BOOL(0, "strict", &check_strict, N_("enable more strict checking")),
        OPT_BOOL(0, "lost-found", &write_lost_and_found,
                                N_("write dangling objects in .git/lost-found")),
@@ -638,6 +659,12 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
 
        argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0);
 
+       fsck_walk_options.walk = mark_object;
+       fsck_obj_options.walk = mark_used;
+       fsck_obj_options.error_func = fsck_error_func;
+       if (check_strict)
+               fsck_obj_options.strict = 1;
+
        if (show_progress == -1)
                show_progress = isatty(2);
        if (verbose)
@@ -648,8 +675,11 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
                include_reflogs = 0;
        }
 
+       git_config(fsck_config, NULL);
+
        fsck_head_link();
-       fsck_object_dir(get_object_directory());
+       if (!connectivity_only)
+               fsck_object_dir(get_object_directory());
 
        prepare_alt_odb();
        for (alt = alt_odb_list; alt; alt = alt->next) {
index 4957c3903293c2d4262f49c01188973bd3e53aa2..bcc75d9bfc4e256dc222d65a3e43a6c090f08b1a 100644 (file)
@@ -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);
 }
 
index f07bc66ed6679cac715491f0b212aca0b8bcd625..3431de2362d981b1239d8269d17fb75d8de094fd 100644 (file)
@@ -75,6 +75,7 @@ static int nr_threads;
 static int from_stdin;
 static int strict;
 static int do_fsck_object;
+static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT;
 static int verbose;
 static int show_stat;
 static int check_self_contained_and_connected;
@@ -192,7 +193,7 @@ static void cleanup_thread(void)
 #endif
 
 
-static int mark_link(struct object *obj, int type, void *data)
+static int mark_link(struct object *obj, int type, void *data, struct fsck_options *options)
 {
        if (!obj)
                return -1;
@@ -838,10 +839,9 @@ static void sha1_object(const void *data, struct object_entry *obj_entry,
                        if (!obj)
                                die(_("invalid %s"), typename(type));
                        if (do_fsck_object &&
-                           fsck_object(obj, buf, size, 1,
-                                   fsck_error_function))
+                           fsck_object(obj, buf, size, &fsck_options))
                                die(_("Error in object"));
-                       if (fsck_walk(obj, mark_link, NULL))
+                       if (fsck_walk(obj, NULL, &fsck_options))
                                die(_("Not all child objects of %s are reachable"), sha1_to_hex(obj->sha1));
 
                        if (obj->type == OBJ_TREE) {
@@ -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);
@@ -1615,6 +1615,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
                usage(index_pack_usage);
 
        check_replace_refs = 0;
+       fsck_options.walk = mark_link;
 
        reset_pack_idx_option(&opts);
        git_config(git_index_pack_config, &opts);
@@ -1632,6 +1633,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
                        } else if (!strcmp(arg, "--strict")) {
                                strict = 1;
                                do_fsck_object = 1;
+                       } else if (skip_prefix(arg, "--strict=", &arg)) {
+                               strict = 1;
+                               do_fsck_object = 1;
+                               fsck_set_msg_types(&fsck_options, arg);
                        } else if (!strcmp(arg, "--check-self-contained-and-connected")) {
                                strict = 1;
                                check_self_contained_and_connected = 1;
index 4335738135df32aeea3712cbdae88bd862015969..49df78d2624d58a29173e4e3a881af0b0e37b7c5 100644 (file)
@@ -4,6 +4,7 @@
  * Copyright (C) Linus Torvalds, 2005
  */
 #include "cache.h"
+#include "refs.h"
 #include "builtin.h"
 #include "exec_cmd.h"
 #include "parse-options.h"
index 878104943f04b6302dfdfd279a2afabadf89c191..b50ef7510c721ed4f6c53b619aa60ccf3d3470a9 100644 (file)
@@ -5,6 +5,7 @@
  *              2006 Junio Hamano
  */
 #include "cache.h"
+#include "refs.h"
 #include "color.h"
 #include "commit.h"
 #include "diff.h"
@@ -31,6 +32,7 @@ static const char *default_date_mode = NULL;
 
 static int default_abbrev_commit;
 static int default_show_root = 1;
+static int default_follow;
 static int decoration_style;
 static int decoration_given;
 static int use_mailmap_config;
@@ -102,6 +104,8 @@ static void cmd_log_init_defaults(struct rev_info *rev)
 {
        if (fmt_pretty)
                get_commit_format(fmt_pretty, rev);
+       if (default_follow)
+               DIFF_OPT_SET(&rev->diffopt, DEFAULT_FOLLOW_RENAMES);
        rev->verbose_header = 1;
        DIFF_OPT_SET(&rev->diffopt, RECURSIVE);
        rev->diffopt.stat_width = -1; /* use full terminal width */
@@ -112,7 +116,7 @@ static void cmd_log_init_defaults(struct rev_info *rev)
        DIFF_OPT_SET(&rev->diffopt, ALLOW_TEXTCONV);
 
        if (default_date_mode)
-               rev->date_mode = parse_date_format(default_date_mode);
+               parse_date_format(default_date_mode, &rev->date_mode);
        rev->diffopt.touched_flags = 0;
 }
 
@@ -390,6 +394,10 @@ static int git_log_config(const char *var, const char *value, void *cb)
                default_show_root = git_config_bool(var, value);
                return 0;
        }
+       if (!strcmp(var, "log.follow")) {
+               default_follow = git_config_bool(var, value);
+               return 0;
+       }
        if (skip_prefix(var, "color.decorate.", &slot_name))
                return parse_decorate_color_config(var, slot_name, value);
        if (!strcmp(var, "log.mailmap")) {
@@ -618,6 +626,14 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix)
        return cmd_log_walk(&rev);
 }
 
+static void default_follow_tweak(struct rev_info *rev,
+                                struct setup_revision_opt *opt)
+{
+       if (DIFF_OPT_TST(&rev->diffopt, DEFAULT_FOLLOW_RENAMES) &&
+           rev->prune_data.nr == 1)
+               DIFF_OPT_SET(&rev->diffopt, FOLLOW_RENAMES);
+}
+
 int cmd_log(int argc, const char **argv, const char *prefix)
 {
        struct rev_info rev;
@@ -631,6 +647,7 @@ int cmd_log(int argc, const char **argv, const char *prefix)
        memset(&opt, 0, sizeof(opt));
        opt.def = "HEAD";
        opt.revarg_opt = REVARG_COMMITTISH;
+       opt.tweak = default_follow_tweak;
        cmd_log_init(argc, argv, prefix, &rev, &opt);
        return cmd_log_walk(&rev);
 }
@@ -939,7 +956,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
 
        msg = body;
        pp.fmt = CMIT_FMT_EMAIL;
-       pp.date_mode = DATE_RFC2822;
+       pp.date_mode.type = DATE_RFC2822;
        pp_user_info(&pp, NULL, &sb, committer, encoding);
        pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte);
        pp_remainder(&pp, &msg, &sb, 0);
index 85c54dcd5a21f2b8413e5f73585f5f0a6c4c5715..a0edacab20bbe55458bb114bd00d618b1bc41a7a 100644 (file)
@@ -231,9 +231,9 @@ static struct option builtin_merge_options[] = {
 /* Cleans up metadata that is uninteresting after a succeeded merge. */
 static void drop_save(void)
 {
-       unlink(git_path("MERGE_HEAD"));
-       unlink(git_path("MERGE_MSG"));
-       unlink(git_path("MERGE_MODE"));
+       unlink(git_path_merge_head());
+       unlink(git_path_merge_msg());
+       unlink(git_path_merge_mode());
 }
 
 static int save_state(unsigned char *stash)
@@ -338,7 +338,7 @@ static void squash_message(struct commit *commit, struct commit_list *remotehead
        struct pretty_print_context ctx = {0};
 
        printf(_("Squash commit -- not updating HEAD\n"));
-       filename = git_path("SQUASH_MSG");
+       filename = git_path_squash_msg();
        fd = open(filename, O_WRONLY | O_CREAT, 0666);
        if (fd < 0)
                die_errno(_("Could not write to '%s'"), filename);
@@ -754,7 +754,7 @@ static void add_strategies(const char *string, unsigned attr)
 
 static void write_merge_msg(struct strbuf *msg)
 {
-       const char *filename = git_path("MERGE_MSG");
+       const char *filename = git_path_merge_msg();
        int fd = open(filename, O_WRONLY | O_CREAT, 0666);
        if (fd < 0)
                die_errno(_("Could not open '%s' for writing"),
@@ -766,7 +766,7 @@ static void write_merge_msg(struct strbuf *msg)
 
 static void read_merge_msg(struct strbuf *msg)
 {
-       const char *filename = git_path("MERGE_MSG");
+       const char *filename = git_path_merge_msg();
        strbuf_reset(msg);
        if (strbuf_read_file(msg, filename, 0) < 0)
                die_errno(_("Could not read from '%s'"), filename);
@@ -799,10 +799,10 @@ static void prepare_to_commit(struct commit_list *remoteheads)
                strbuf_commented_addf(&msg, _(merge_editor_comment), comment_line_char);
        write_merge_msg(&msg);
        if (run_commit_hook(0 < option_edit, get_index_file(), "prepare-commit-msg",
-                           git_path("MERGE_MSG"), "merge", NULL))
+                           git_path_merge_msg(), "merge", NULL))
                abort_commit(remoteheads, NULL);
        if (0 < option_edit) {
-               if (launch_editor(git_path("MERGE_MSG"), NULL, NULL))
+               if (launch_editor(git_path_merge_msg(), NULL, NULL))
                        abort_commit(remoteheads, NULL);
        }
        read_merge_msg(&msg);
@@ -865,7 +865,7 @@ static int suggest_conflicts(void)
        FILE *fp;
        struct strbuf msgbuf = STRBUF_INIT;
 
-       filename = git_path("MERGE_MSG");
+       filename = git_path_merge_msg();
        fp = fopen(filename, "a");
        if (!fp)
                die_errno(_("Could not open '%s' for writing"), filename);
@@ -967,7 +967,7 @@ static void write_merge_state(struct commit_list *remoteheads)
                }
                strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1));
        }
-       filename = git_path("MERGE_HEAD");
+       filename = git_path_merge_head();
        fd = open(filename, O_WRONLY | O_CREAT, 0666);
        if (fd < 0)
                die_errno(_("Could not open '%s' for writing"), filename);
@@ -977,7 +977,7 @@ static void write_merge_state(struct commit_list *remoteheads)
        strbuf_addch(&merge_msg, '\n');
        write_merge_msg(&merge_msg);
 
-       filename = git_path("MERGE_MODE");
+       filename = git_path_merge_mode();
        fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
        if (fd < 0)
                die_errno(_("Could not open '%s' for writing"), filename);
@@ -1070,7 +1070,7 @@ static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge
        if (!merge_names)
                merge_names = &fetch_head_file;
 
-       filename = git_path("FETCH_HEAD");
+       filename = git_path_fetch_head();
        fd = open(filename, O_RDONLY);
        if (fd < 0)
                die_errno(_("could not open '%s' for reading"), filename);
@@ -1204,7 +1204,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                int nargc = 2;
                const char *nargv[] = {"reset", "--merge", NULL};
 
-               if (!file_exists(git_path("MERGE_HEAD")))
+               if (!file_exists(git_path_merge_head()))
                        die(_("There is no merge to abort (MERGE_HEAD missing)."));
 
                /* Invoke 'git reset --merge' */
@@ -1215,7 +1215,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
        if (read_cache_unmerged())
                die_resolve_conflict("merge");
 
-       if (file_exists(git_path("MERGE_HEAD"))) {
+       if (file_exists(git_path_merge_head())) {
                /*
                 * There is no unmerged entry, don't advise 'git
                 * add/rm <file>', just 'git commit'.
@@ -1226,7 +1226,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                else
                        die(_("You have not concluded your merge (MERGE_HEAD exists)."));
        }
-       if (file_exists(git_path("CHERRY_PICK_HEAD"))) {
+       if (file_exists(git_path_cherry_pick_head())) {
                if (advice_resolve_conflict)
                        die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
                            "Please, commit your changes before you merge."));
index 80fe8c7dc1da9b78d2ba39ba0a70de69372c35ab..62cc16ddc24320273bba4732c4402e8f6221ab95 100644 (file)
@@ -2588,23 +2588,6 @@ static int option_parse_unpack_unreachable(const struct option *opt,
        return 0;
 }
 
-static int option_parse_ulong(const struct option *opt,
-                             const char *arg, int unset)
-{
-       if (unset)
-               die(_("option %s does not accept negative form"),
-                   opt->long_name);
-
-       if (!git_parse_ulong(arg, opt->value))
-               die(_("unable to parse value '%s' for option %s"),
-                   arg, opt->long_name);
-       return 0;
-}
-
-#define OPT_ULONG(s, l, v, h) \
-       { OPTION_CALLBACK, (s), (l), (v), "n", (h),     \
-         PARSE_OPT_NONEG, option_parse_ulong }
-
 int cmd_pack_objects(int argc, const char **argv, const char *prefix)
 {
        int use_internal_rev_list = 0;
@@ -2627,16 +2610,16 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                { OPTION_CALLBACK, 0, "index-version", NULL, N_("version[,offset]"),
                  N_("write the pack index file in the specified idx format version"),
                  0, option_parse_index_version },
-               OPT_ULONG(0, "max-pack-size", &pack_size_limit,
-                         N_("maximum size of each output pack file")),
+               OPT_MAGNITUDE(0, "max-pack-size", &pack_size_limit,
+                             N_("maximum size of each output pack file")),
                OPT_BOOL(0, "local", &local,
                         N_("ignore borrowed objects from alternate object store")),
                OPT_BOOL(0, "incremental", &incremental,
                         N_("ignore packed objects")),
                OPT_INTEGER(0, "window", &window,
                            N_("limit pack window by objects")),
-               OPT_ULONG(0, "window-memory", &window_memory_limit,
-                         N_("limit pack window by memory in addition to object limit")),
+               OPT_MAGNITUDE(0, "window-memory", &window_memory_limit,
+                             N_("limit pack window by memory in addition to object limit")),
                OPT_INTEGER(0, "depth", &depth,
                            N_("maximum length of delta chain allowed in the resulting pack")),
                OPT_BOOL(0, "reuse-delta", &reuse_delta,
diff --git a/builtin/pull.c b/builtin/pull.c
new file mode 100644 (file)
index 0000000..b7bc1ff
--- /dev/null
@@ -0,0 +1,886 @@
+/*
+ * Builtin "git pull"
+ *
+ * Based on git-pull.sh by Junio C Hamano
+ *
+ * Fetch one or more remote refs and merge it/them into the current HEAD.
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "parse-options.h"
+#include "exec_cmd.h"
+#include "run-command.h"
+#include "sha1-array.h"
+#include "remote.h"
+#include "dir.h"
+#include "refs.h"
+#include "revision.h"
+#include "lockfile.h"
+
+enum rebase_type {
+       REBASE_INVALID = -1,
+       REBASE_FALSE = 0,
+       REBASE_TRUE,
+       REBASE_PRESERVE
+};
+
+/**
+ * Parses the value of --rebase. If value is a false value, returns
+ * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is
+ * "preserve", returns REBASE_PRESERVE. If value is a invalid value, dies with
+ * a fatal error if fatal is true, otherwise returns REBASE_INVALID.
+ */
+static enum rebase_type parse_config_rebase(const char *key, const char *value,
+               int fatal)
+{
+       int v = git_config_maybe_bool("pull.rebase", value);
+
+       if (!v)
+               return REBASE_FALSE;
+       else if (v > 0)
+               return REBASE_TRUE;
+       else if (!strcmp(value, "preserve"))
+               return REBASE_PRESERVE;
+
+       if (fatal)
+               die(_("Invalid value for %s: %s"), key, value);
+       else
+               error(_("Invalid value for %s: %s"), key, value);
+
+       return REBASE_INVALID;
+}
+
+/**
+ * Callback for --rebase, which parses arg with parse_config_rebase().
+ */
+static int parse_opt_rebase(const struct option *opt, const char *arg, int unset)
+{
+       enum rebase_type *value = opt->value;
+
+       if (arg)
+               *value = parse_config_rebase("--rebase", arg, 0);
+       else
+               *value = unset ? REBASE_FALSE : REBASE_TRUE;
+       return *value == REBASE_INVALID ? -1 : 0;
+}
+
+static const char * const pull_usage[] = {
+       N_("git pull [options] [<repository> [<refspec>...]]"),
+       NULL
+};
+
+/* Shared options */
+static int opt_verbosity;
+static char *opt_progress;
+
+/* Options passed to git-merge or git-rebase */
+static enum rebase_type opt_rebase = -1;
+static char *opt_diffstat;
+static char *opt_log;
+static char *opt_squash;
+static char *opt_commit;
+static char *opt_edit;
+static char *opt_ff;
+static char *opt_verify_signatures;
+static struct argv_array opt_strategies = ARGV_ARRAY_INIT;
+static struct argv_array opt_strategy_opts = ARGV_ARRAY_INIT;
+static char *opt_gpg_sign;
+
+/* Options passed to git-fetch */
+static char *opt_all;
+static char *opt_append;
+static char *opt_upload_pack;
+static int opt_force;
+static char *opt_tags;
+static char *opt_prune;
+static char *opt_recurse_submodules;
+static int opt_dry_run;
+static char *opt_keep;
+static char *opt_depth;
+static char *opt_unshallow;
+static char *opt_update_shallow;
+static char *opt_refmap;
+
+static struct option pull_options[] = {
+       /* Shared options */
+       OPT__VERBOSITY(&opt_verbosity),
+       OPT_PASSTHRU(0, "progress", &opt_progress, NULL,
+               N_("force progress reporting"),
+               PARSE_OPT_NOARG),
+
+       /* Options passed to git-merge or git-rebase */
+       OPT_GROUP(N_("Options related to merging")),
+       { OPTION_CALLBACK, 'r', "rebase", &opt_rebase,
+         N_("false|true|preserve"),
+         N_("incorporate changes by rebasing rather than merging"),
+         PARSE_OPT_OPTARG, parse_opt_rebase },
+       OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,
+               N_("do not show a diffstat at the end of the merge"),
+               PARSE_OPT_NOARG | PARSE_OPT_NONEG),
+       OPT_PASSTHRU(0, "stat", &opt_diffstat, NULL,
+               N_("show a diffstat at the end of the merge"),
+               PARSE_OPT_NOARG),
+       OPT_PASSTHRU(0, "summary", &opt_diffstat, NULL,
+               N_("(synonym to --stat)"),
+               PARSE_OPT_NOARG | PARSE_OPT_HIDDEN),
+       OPT_PASSTHRU(0, "log", &opt_log, N_("n"),
+               N_("add (at most <n>) entries from shortlog to merge commit message"),
+               PARSE_OPT_OPTARG),
+       OPT_PASSTHRU(0, "squash", &opt_squash, NULL,
+               N_("create a single commit instead of doing a merge"),
+               PARSE_OPT_NOARG),
+       OPT_PASSTHRU(0, "commit", &opt_commit, NULL,
+               N_("perform a commit if the merge succeeds (default)"),
+               PARSE_OPT_NOARG),
+       OPT_PASSTHRU(0, "edit", &opt_edit, NULL,
+               N_("edit message before committing"),
+               PARSE_OPT_NOARG),
+       OPT_PASSTHRU(0, "ff", &opt_ff, NULL,
+               N_("allow fast-forward"),
+               PARSE_OPT_NOARG),
+       OPT_PASSTHRU(0, "ff-only", &opt_ff, NULL,
+               N_("abort if fast-forward is not possible"),
+               PARSE_OPT_NOARG | PARSE_OPT_NONEG),
+       OPT_PASSTHRU(0, "verify-signatures", &opt_verify_signatures, NULL,
+               N_("verify that the named commit has a valid GPG signature"),
+               PARSE_OPT_NOARG),
+       OPT_PASSTHRU_ARGV('s', "strategy", &opt_strategies, N_("strategy"),
+               N_("merge strategy to use"),
+               0),
+       OPT_PASSTHRU_ARGV('X', "strategy-option", &opt_strategy_opts,
+               N_("option=value"),
+               N_("option for selected merge strategy"),
+               0),
+       OPT_PASSTHRU('S', "gpg-sign", &opt_gpg_sign, N_("key-id"),
+               N_("GPG sign commit"),
+               PARSE_OPT_OPTARG),
+
+       /* Options passed to git-fetch */
+       OPT_GROUP(N_("Options related to fetching")),
+       OPT_PASSTHRU(0, "all", &opt_all, NULL,
+               N_("fetch from all remotes"),
+               PARSE_OPT_NOARG),
+       OPT_PASSTHRU('a', "append", &opt_append, NULL,
+               N_("append to .git/FETCH_HEAD instead of overwriting"),
+               PARSE_OPT_NOARG),
+       OPT_PASSTHRU(0, "upload-pack", &opt_upload_pack, N_("path"),
+               N_("path to upload pack on remote end"),
+               0),
+       OPT__FORCE(&opt_force, N_("force overwrite of local branch")),
+       OPT_PASSTHRU('t', "tags", &opt_tags, NULL,
+               N_("fetch all tags and associated objects"),
+               PARSE_OPT_NOARG),
+       OPT_PASSTHRU('p', "prune", &opt_prune, NULL,
+               N_("prune remote-tracking branches no longer on remote"),
+               PARSE_OPT_NOARG),
+       OPT_PASSTHRU(0, "recurse-submodules", &opt_recurse_submodules,
+               N_("on-demand"),
+               N_("control recursive fetching of submodules"),
+               PARSE_OPT_OPTARG),
+       OPT_BOOL(0, "dry-run", &opt_dry_run,
+               N_("dry run")),
+       OPT_PASSTHRU('k', "keep", &opt_keep, NULL,
+               N_("keep downloaded pack"),
+               PARSE_OPT_NOARG),
+       OPT_PASSTHRU(0, "depth", &opt_depth, N_("depth"),
+               N_("deepen history of shallow clone"),
+               0),
+       OPT_PASSTHRU(0, "unshallow", &opt_unshallow, NULL,
+               N_("convert to a complete repository"),
+               PARSE_OPT_NONEG | PARSE_OPT_NOARG),
+       OPT_PASSTHRU(0, "update-shallow", &opt_update_shallow, NULL,
+               N_("accept refs that update .git/shallow"),
+               PARSE_OPT_NOARG),
+       OPT_PASSTHRU(0, "refmap", &opt_refmap, N_("refmap"),
+               N_("specify fetch refmap"),
+               PARSE_OPT_NONEG),
+
+       OPT_END()
+};
+
+/**
+ * Pushes "-q" or "-v" switches into arr to match the opt_verbosity level.
+ */
+static void argv_push_verbosity(struct argv_array *arr)
+{
+       int verbosity;
+
+       for (verbosity = opt_verbosity; verbosity > 0; verbosity--)
+               argv_array_push(arr, "-v");
+
+       for (verbosity = opt_verbosity; verbosity < 0; verbosity++)
+               argv_array_push(arr, "-q");
+}
+
+/**
+ * Pushes "-f" switches into arr to match the opt_force level.
+ */
+static void argv_push_force(struct argv_array *arr)
+{
+       int force = opt_force;
+       while (force-- > 0)
+               argv_array_push(arr, "-f");
+}
+
+/**
+ * Sets the GIT_REFLOG_ACTION environment variable to the concatenation of argv
+ */
+static void set_reflog_message(int argc, const char **argv)
+{
+       int i;
+       struct strbuf msg = STRBUF_INIT;
+
+       for (i = 0; i < argc; i++) {
+               if (i)
+                       strbuf_addch(&msg, ' ');
+               strbuf_addstr(&msg, argv[i]);
+       }
+
+       setenv("GIT_REFLOG_ACTION", msg.buf, 0);
+
+       strbuf_release(&msg);
+}
+
+/**
+ * If pull.ff is unset, returns NULL. If pull.ff is "true", returns "--ff". If
+ * pull.ff is "false", returns "--no-ff". If pull.ff is "only", returns
+ * "--ff-only". Otherwise, if pull.ff is set to an invalid value, die with an
+ * error.
+ */
+static const char *config_get_ff(void)
+{
+       const char *value;
+
+       if (git_config_get_value("pull.ff", &value))
+               return NULL;
+
+       switch (git_config_maybe_bool("pull.ff", value)) {
+       case 0:
+               return "--no-ff";
+       case 1:
+               return "--ff";
+       }
+
+       if (!strcmp(value, "only"))
+               return "--ff-only";
+
+       die(_("Invalid value for pull.ff: %s"), value);
+}
+
+/**
+ * Returns the default configured value for --rebase. It first looks for the
+ * value of "branch.$curr_branch.rebase", where $curr_branch is the current
+ * branch, and if HEAD is detached or the configuration key does not exist,
+ * looks for the value of "pull.rebase". If both configuration keys do not
+ * exist, returns REBASE_FALSE.
+ */
+static enum rebase_type config_get_rebase(void)
+{
+       struct branch *curr_branch = branch_get("HEAD");
+       const char *value;
+
+       if (curr_branch) {
+               char *key = xstrfmt("branch.%s.rebase", curr_branch->name);
+
+               if (!git_config_get_value(key, &value)) {
+                       enum rebase_type ret = parse_config_rebase(key, value, 1);
+                       free(key);
+                       return ret;
+               }
+
+               free(key);
+       }
+
+       if (!git_config_get_value("pull.rebase", &value))
+               return parse_config_rebase("pull.rebase", value, 1);
+
+       return REBASE_FALSE;
+}
+
+/**
+ * Returns 1 if there are unstaged changes, 0 otherwise.
+ */
+static int has_unstaged_changes(const char *prefix)
+{
+       struct rev_info rev_info;
+       int result;
+
+       init_revisions(&rev_info, prefix);
+       DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES);
+       DIFF_OPT_SET(&rev_info.diffopt, QUICK);
+       diff_setup_done(&rev_info.diffopt);
+       result = run_diff_files(&rev_info, 0);
+       return diff_result_code(&rev_info.diffopt, result);
+}
+
+/**
+ * Returns 1 if there are uncommitted changes, 0 otherwise.
+ */
+static int has_uncommitted_changes(const char *prefix)
+{
+       struct rev_info rev_info;
+       int result;
+
+       if (is_cache_unborn())
+               return 0;
+
+       init_revisions(&rev_info, prefix);
+       DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES);
+       DIFF_OPT_SET(&rev_info.diffopt, QUICK);
+       add_head_to_pending(&rev_info);
+       diff_setup_done(&rev_info.diffopt);
+       result = run_diff_index(&rev_info, 1);
+       return diff_result_code(&rev_info.diffopt, result);
+}
+
+/**
+ * If the work tree has unstaged or uncommitted changes, dies with the
+ * appropriate message.
+ */
+static void die_on_unclean_work_tree(const char *prefix)
+{
+       struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file));
+       int do_die = 0;
+
+       hold_locked_index(lock_file, 0);
+       refresh_cache(REFRESH_QUIET);
+       update_index_if_able(&the_index, lock_file);
+       rollback_lock_file(lock_file);
+
+       if (has_unstaged_changes(prefix)) {
+               error(_("Cannot pull with rebase: You have unstaged changes."));
+               do_die = 1;
+       }
+
+       if (has_uncommitted_changes(prefix)) {
+               if (do_die)
+                       error(_("Additionally, your index contains uncommitted changes."));
+               else
+                       error(_("Cannot pull with rebase: Your index contains uncommitted changes."));
+               do_die = 1;
+       }
+
+       if (do_die)
+               exit(1);
+}
+
+/**
+ * Appends merge candidates from FETCH_HEAD that are not marked not-for-merge
+ * into merge_heads.
+ */
+static void get_merge_heads(struct sha1_array *merge_heads)
+{
+       const char *filename = git_path("FETCH_HEAD");
+       FILE *fp;
+       struct strbuf sb = STRBUF_INIT;
+       unsigned char sha1[GIT_SHA1_RAWSZ];
+
+       if (!(fp = fopen(filename, "r")))
+               die_errno(_("could not open '%s' for reading"), filename);
+       while (strbuf_getline(&sb, fp, '\n') != EOF) {
+               if (get_sha1_hex(sb.buf, sha1))
+                       continue;  /* invalid line: does not start with SHA1 */
+               if (starts_with(sb.buf + GIT_SHA1_HEXSZ, "\tnot-for-merge\t"))
+                       continue;  /* ref is not-for-merge */
+               sha1_array_append(merge_heads, sha1);
+       }
+       fclose(fp);
+       strbuf_release(&sb);
+}
+
+/**
+ * Used by die_no_merge_candidates() as a for_each_remote() callback to
+ * retrieve the name of the remote if the repository only has one remote.
+ */
+static int get_only_remote(struct remote *remote, void *cb_data)
+{
+       const char **remote_name = cb_data;
+
+       if (*remote_name)
+               return -1;
+
+       *remote_name = remote->name;
+       return 0;
+}
+
+/**
+ * Dies with the appropriate reason for why there are no merge candidates:
+ *
+ * 1. We fetched from a specific remote, and a refspec was given, but it ended
+ *    up not fetching anything. This is usually because the user provided a
+ *    wildcard refspec which had no matches on the remote end.
+ *
+ * 2. We fetched from a non-default remote, but didn't specify a branch to
+ *    merge. We can't use the configured one because it applies to the default
+ *    remote, thus the user must specify the branches to merge.
+ *
+ * 3. We fetched from the branch's or repo's default remote, but:
+ *
+ *    a. We are not on a branch, so there will never be a configured branch to
+ *       merge with.
+ *
+ *    b. We are on a branch, but there is no configured branch to merge with.
+ *
+ * 4. We fetched from the branch's or repo's default remote, but the configured
+ *    branch to merge didn't get fetched. (Either it doesn't exist, or wasn't
+ *    part of the configured fetch refspec.)
+ */
+static void NORETURN die_no_merge_candidates(const char *repo, const char **refspecs)
+{
+       struct branch *curr_branch = branch_get("HEAD");
+       const char *remote = curr_branch ? curr_branch->remote_name : NULL;
+
+       if (*refspecs) {
+               if (opt_rebase)
+                       fprintf_ln(stderr, _("There is no candidate for rebasing against among the refs that you just fetched."));
+               else
+                       fprintf_ln(stderr, _("There are no candidates for merging among the refs that you just fetched."));
+               fprintf_ln(stderr, _("Generally this means that you provided a wildcard refspec which had no\n"
+                                       "matches on the remote end."));
+       } else if (repo && curr_branch && (!remote || strcmp(repo, remote))) {
+               fprintf_ln(stderr, _("You asked to pull from the remote '%s', but did not specify\n"
+                       "a branch. Because this is not the default configured remote\n"
+                       "for your current branch, you must specify a branch on the command line."),
+                       repo);
+       } else if (!curr_branch) {
+               fprintf_ln(stderr, _("You are not currently on a branch."));
+               if (opt_rebase)
+                       fprintf_ln(stderr, _("Please specify which branch you want to rebase against."));
+               else
+                       fprintf_ln(stderr, _("Please specify which branch you want to merge with."));
+               fprintf_ln(stderr, _("See git-pull(1) for details."));
+               fprintf(stderr, "\n");
+               fprintf_ln(stderr, "    git pull <remote> <branch>");
+               fprintf(stderr, "\n");
+       } else if (!curr_branch->merge_nr) {
+               const char *remote_name = NULL;
+
+               if (for_each_remote(get_only_remote, &remote_name) || !remote_name)
+                       remote_name = "<remote>";
+
+               fprintf_ln(stderr, _("There is no tracking information for the current branch."));
+               if (opt_rebase)
+                       fprintf_ln(stderr, _("Please specify which branch you want to rebase against."));
+               else
+                       fprintf_ln(stderr, _("Please specify which branch you want to merge with."));
+               fprintf_ln(stderr, _("See git-pull(1) for details."));
+               fprintf(stderr, "\n");
+               fprintf_ln(stderr, "    git pull <remote> <branch>");
+               fprintf(stderr, "\n");
+               fprintf_ln(stderr, _("If you wish to set tracking information for this branch you can do so with:\n"
+                               "\n"
+                               "    git branch --set-upstream-to=%s/<branch> %s\n"),
+                               remote_name, curr_branch->name);
+       } else
+               fprintf_ln(stderr, _("Your configuration specifies to merge with the ref '%s'\n"
+                       "from the remote, but no such ref was fetched."),
+                       *curr_branch->merge_name);
+       exit(1);
+}
+
+/**
+ * Parses argv into [<repo> [<refspecs>...]], returning their values in `repo`
+ * as a string and `refspecs` as a null-terminated array of strings. If `repo`
+ * is not provided in argv, it is set to NULL.
+ */
+static void parse_repo_refspecs(int argc, const char **argv, const char **repo,
+               const char ***refspecs)
+{
+       if (argc > 0) {
+               *repo = *argv++;
+               argc--;
+       } else
+               *repo = NULL;
+       *refspecs = argv;
+}
+
+/**
+ * Runs git-fetch, returning its exit status. `repo` and `refspecs` are the
+ * repository and refspecs to fetch, or NULL if they are not provided.
+ */
+static int run_fetch(const char *repo, const char **refspecs)
+{
+       struct argv_array args = ARGV_ARRAY_INIT;
+       int ret;
+
+       argv_array_pushl(&args, "fetch", "--update-head-ok", NULL);
+
+       /* Shared options */
+       argv_push_verbosity(&args);
+       if (opt_progress)
+               argv_array_push(&args, opt_progress);
+
+       /* Options passed to git-fetch */
+       if (opt_all)
+               argv_array_push(&args, opt_all);
+       if (opt_append)
+               argv_array_push(&args, opt_append);
+       if (opt_upload_pack)
+               argv_array_push(&args, opt_upload_pack);
+       argv_push_force(&args);
+       if (opt_tags)
+               argv_array_push(&args, opt_tags);
+       if (opt_prune)
+               argv_array_push(&args, opt_prune);
+       if (opt_recurse_submodules)
+               argv_array_push(&args, opt_recurse_submodules);
+       if (opt_dry_run)
+               argv_array_push(&args, "--dry-run");
+       if (opt_keep)
+               argv_array_push(&args, opt_keep);
+       if (opt_depth)
+               argv_array_push(&args, opt_depth);
+       if (opt_unshallow)
+               argv_array_push(&args, opt_unshallow);
+       if (opt_update_shallow)
+               argv_array_push(&args, opt_update_shallow);
+       if (opt_refmap)
+               argv_array_push(&args, opt_refmap);
+
+       if (repo) {
+               argv_array_push(&args, repo);
+               argv_array_pushv(&args, refspecs);
+       } else if (*refspecs)
+               die("BUG: refspecs without repo?");
+       ret = run_command_v_opt(args.argv, RUN_GIT_CMD);
+       argv_array_clear(&args);
+       return ret;
+}
+
+/**
+ * "Pulls into void" by branching off merge_head.
+ */
+static int pull_into_void(const unsigned char *merge_head,
+               const unsigned char *curr_head)
+{
+       /*
+        * Two-way merge: we treat the index as based on an empty tree,
+        * and try to fast-forward to HEAD. This ensures we will not lose
+        * index/worktree changes that the user already made on the unborn
+        * branch.
+        */
+       if (checkout_fast_forward(EMPTY_TREE_SHA1_BIN, merge_head, 0))
+               return 1;
+
+       if (update_ref("initial pull", "HEAD", merge_head, curr_head, 0, UPDATE_REFS_DIE_ON_ERR))
+               return 1;
+
+       return 0;
+}
+
+/**
+ * Runs git-merge, returning its exit status.
+ */
+static int run_merge(void)
+{
+       int ret;
+       struct argv_array args = ARGV_ARRAY_INIT;
+
+       argv_array_pushl(&args, "merge", NULL);
+
+       /* Shared options */
+       argv_push_verbosity(&args);
+       if (opt_progress)
+               argv_array_push(&args, opt_progress);
+
+       /* Options passed to git-merge */
+       if (opt_diffstat)
+               argv_array_push(&args, opt_diffstat);
+       if (opt_log)
+               argv_array_push(&args, opt_log);
+       if (opt_squash)
+               argv_array_push(&args, opt_squash);
+       if (opt_commit)
+               argv_array_push(&args, opt_commit);
+       if (opt_edit)
+               argv_array_push(&args, opt_edit);
+       if (opt_ff)
+               argv_array_push(&args, opt_ff);
+       if (opt_verify_signatures)
+               argv_array_push(&args, opt_verify_signatures);
+       argv_array_pushv(&args, opt_strategies.argv);
+       argv_array_pushv(&args, opt_strategy_opts.argv);
+       if (opt_gpg_sign)
+               argv_array_push(&args, opt_gpg_sign);
+
+       argv_array_push(&args, "FETCH_HEAD");
+       ret = run_command_v_opt(args.argv, RUN_GIT_CMD);
+       argv_array_clear(&args);
+       return ret;
+}
+
+/**
+ * Returns remote's upstream branch for the current branch. If remote is NULL,
+ * the current branch's configured default remote is used. Returns NULL if
+ * `remote` does not name a valid remote, HEAD does not point to a branch,
+ * remote is not the branch's configured remote or the branch does not have any
+ * configured upstream branch.
+ */
+static const char *get_upstream_branch(const char *remote)
+{
+       struct remote *rm;
+       struct branch *curr_branch;
+       const char *curr_branch_remote;
+
+       rm = remote_get(remote);
+       if (!rm)
+               return NULL;
+
+       curr_branch = branch_get("HEAD");
+       if (!curr_branch)
+               return NULL;
+
+       curr_branch_remote = remote_for_branch(curr_branch, NULL);
+       assert(curr_branch_remote);
+
+       if (strcmp(curr_branch_remote, rm->name))
+               return NULL;
+
+       return branch_get_upstream(curr_branch, NULL);
+}
+
+/**
+ * Derives the remote tracking branch from the remote and refspec.
+ *
+ * FIXME: The current implementation assumes the default mapping of
+ * refs/heads/<branch_name> to refs/remotes/<remote_name>/<branch_name>.
+ */
+static const char *get_tracking_branch(const char *remote, const char *refspec)
+{
+       struct refspec *spec;
+       const char *spec_src;
+       const char *merge_branch;
+
+       spec = parse_fetch_refspec(1, &refspec);
+       spec_src = spec->src;
+       if (!*spec_src || !strcmp(spec_src, "HEAD"))
+               spec_src = "HEAD";
+       else if (skip_prefix(spec_src, "heads/", &spec_src))
+               ;
+       else if (skip_prefix(spec_src, "refs/heads/", &spec_src))
+               ;
+       else if (starts_with(spec_src, "refs/") ||
+               starts_with(spec_src, "tags/") ||
+               starts_with(spec_src, "remotes/"))
+               spec_src = "";
+
+       if (*spec_src) {
+               if (!strcmp(remote, "."))
+                       merge_branch = mkpath("refs/heads/%s", spec_src);
+               else
+                       merge_branch = mkpath("refs/remotes/%s/%s", remote, spec_src);
+       } else
+               merge_branch = NULL;
+
+       free_refspec(1, spec);
+       return merge_branch;
+}
+
+/**
+ * Given the repo and refspecs, sets fork_point to the point at which the
+ * current branch forked from its remote tracking branch. Returns 0 on success,
+ * -1 on failure.
+ */
+static int get_rebase_fork_point(unsigned char *fork_point, const char *repo,
+               const char *refspec)
+{
+       int ret;
+       struct branch *curr_branch;
+       const char *remote_branch;
+       struct child_process cp = CHILD_PROCESS_INIT;
+       struct strbuf sb = STRBUF_INIT;
+
+       curr_branch = branch_get("HEAD");
+       if (!curr_branch)
+               return -1;
+
+       if (refspec)
+               remote_branch = get_tracking_branch(repo, refspec);
+       else
+               remote_branch = get_upstream_branch(repo);
+
+       if (!remote_branch)
+               return -1;
+
+       argv_array_pushl(&cp.args, "merge-base", "--fork-point",
+                       remote_branch, curr_branch->name, NULL);
+       cp.no_stdin = 1;
+       cp.no_stderr = 1;
+       cp.git_cmd = 1;
+
+       ret = capture_command(&cp, &sb, GIT_SHA1_HEXSZ);
+       if (ret)
+               goto cleanup;
+
+       ret = get_sha1_hex(sb.buf, fork_point);
+       if (ret)
+               goto cleanup;
+
+cleanup:
+       strbuf_release(&sb);
+       return ret ? -1 : 0;
+}
+
+/**
+ * Sets merge_base to the octopus merge base of curr_head, merge_head and
+ * fork_point. Returns 0 if a merge base is found, 1 otherwise.
+ */
+static int get_octopus_merge_base(unsigned char *merge_base,
+               const unsigned char *curr_head,
+               const unsigned char *merge_head,
+               const unsigned char *fork_point)
+{
+       struct commit_list *revs = NULL, *result;
+
+       commit_list_insert(lookup_commit_reference(curr_head), &revs);
+       commit_list_insert(lookup_commit_reference(merge_head), &revs);
+       if (!is_null_sha1(fork_point))
+               commit_list_insert(lookup_commit_reference(fork_point), &revs);
+
+       result = reduce_heads(get_octopus_merge_bases(revs));
+       free_commit_list(revs);
+       if (!result)
+               return 1;
+
+       hashcpy(merge_base, result->item->object.sha1);
+       return 0;
+}
+
+/**
+ * Given the current HEAD SHA1, the merge head returned from git-fetch and the
+ * fork point calculated by get_rebase_fork_point(), runs git-rebase with the
+ * appropriate arguments and returns its exit status.
+ */
+static int run_rebase(const unsigned char *curr_head,
+               const unsigned char *merge_head,
+               const unsigned char *fork_point)
+{
+       int ret;
+       unsigned char oct_merge_base[GIT_SHA1_RAWSZ];
+       struct argv_array args = ARGV_ARRAY_INIT;
+
+       if (!get_octopus_merge_base(oct_merge_base, curr_head, merge_head, fork_point))
+               if (!is_null_sha1(fork_point) && !hashcmp(oct_merge_base, fork_point))
+                       fork_point = NULL;
+
+       argv_array_push(&args, "rebase");
+
+       /* Shared options */
+       argv_push_verbosity(&args);
+
+       /* Options passed to git-rebase */
+       if (opt_rebase == REBASE_PRESERVE)
+               argv_array_push(&args, "--preserve-merges");
+       if (opt_diffstat)
+               argv_array_push(&args, opt_diffstat);
+       argv_array_pushv(&args, opt_strategies.argv);
+       argv_array_pushv(&args, opt_strategy_opts.argv);
+       if (opt_gpg_sign)
+               argv_array_push(&args, opt_gpg_sign);
+
+       argv_array_push(&args, "--onto");
+       argv_array_push(&args, sha1_to_hex(merge_head));
+
+       if (fork_point && !is_null_sha1(fork_point))
+               argv_array_push(&args, sha1_to_hex(fork_point));
+       else
+               argv_array_push(&args, sha1_to_hex(merge_head));
+
+       ret = run_command_v_opt(args.argv, RUN_GIT_CMD);
+       argv_array_clear(&args);
+       return ret;
+}
+
+int cmd_pull(int argc, const char **argv, const char *prefix)
+{
+       const char *repo, **refspecs;
+       struct sha1_array merge_heads = SHA1_ARRAY_INIT;
+       unsigned char orig_head[GIT_SHA1_RAWSZ], curr_head[GIT_SHA1_RAWSZ];
+       unsigned char rebase_fork_point[GIT_SHA1_RAWSZ];
+
+       if (!getenv("GIT_REFLOG_ACTION"))
+               set_reflog_message(argc, argv);
+
+       argc = parse_options(argc, argv, prefix, pull_options, pull_usage, 0);
+
+       parse_repo_refspecs(argc, argv, &repo, &refspecs);
+
+       if (!opt_ff)
+               opt_ff = xstrdup_or_null(config_get_ff());
+
+       if (opt_rebase < 0)
+               opt_rebase = config_get_rebase();
+
+       git_config(git_default_config, NULL);
+
+       if (read_cache_unmerged())
+               die_resolve_conflict("Pull");
+
+       if (file_exists(git_path("MERGE_HEAD")))
+               die_conclude_merge();
+
+       if (get_sha1("HEAD", orig_head))
+               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."));
+
+               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);
+       }
+
+       if (run_fetch(repo, refspecs))
+               return 1;
+
+       if (opt_dry_run)
+               return 0;
+
+       if (get_sha1("HEAD", curr_head))
+               hashclr(curr_head);
+
+       if (!is_null_sha1(orig_head) && !is_null_sha1(curr_head) &&
+                       hashcmp(orig_head, curr_head)) {
+               /*
+                * The fetch involved updating the current branch.
+                *
+                * The working tree and the index file are still based on
+                * orig_head commit, but we are merging into curr_head.
+                * Update the working tree to match curr_head.
+                */
+
+               warning(_("fetch updated the current branch head.\n"
+                       "fast-forwarding your working tree from\n"
+                       "commit %s."), sha1_to_hex(orig_head));
+
+               if (checkout_fast_forward(orig_head, curr_head, 0))
+                       die(_("Cannot fast-forward your working tree.\n"
+                               "After making sure that you saved anything precious from\n"
+                               "$ git diff %s\n"
+                               "output, run\n"
+                               "$ git reset --hard\n"
+                               "to recover."), sha1_to_hex(orig_head));
+       }
+
+       get_merge_heads(&merge_heads);
+
+       if (!merge_heads.nr)
+               die_no_merge_candidates(repo, refspecs);
+
+       if (is_null_sha1(orig_head)) {
+               if (merge_heads.nr > 1)
+                       die(_("Cannot merge multiple branches into empty head."));
+               return pull_into_void(*merge_heads.sha1, curr_head);
+       } else if (opt_rebase) {
+               if (merge_heads.nr > 1)
+                       die(_("Cannot rebase onto multiple branches."));
+               return run_rebase(curr_head, *merge_heads.sha1, rebase_fork_point);
+       } else
+               return run_merge();
+}
index 94d05717766151e6b183e2c21d279e72812aeb03..e6b93d026478dce208ab4c267d9b6e67fa856c8d 100644 (file)
@@ -19,6 +19,7 @@
 #include "tag.h"
 #include "gpg-interface.h"
 #include "sigchain.h"
+#include "fsck.h"
 
 static const char receive_pack_usage[] = "git receive-pack <git-dir>";
 
@@ -36,6 +37,7 @@ static enum deny_action deny_current_branch = DENY_UNCONFIGURED;
 static enum deny_action deny_delete_current = DENY_UNCONFIGURED;
 static int receive_fsck_objects = -1;
 static int transfer_fsck_objects = -1;
+static struct strbuf fsck_msg_types = STRBUF_INIT;
 static int receive_unpack_limit = -1;
 static int transfer_unpack_limit = -1;
 static int advertise_atomic_push = 1;
@@ -115,6 +117,26 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
+       if (strcmp(var, "receive.fsck.skiplist") == 0) {
+               const char *path;
+
+               if (git_config_pathname(&path, var, value))
+                       return 1;
+               strbuf_addf(&fsck_msg_types, "%cskiplist=%s",
+                       fsck_msg_types.len ? ',' : '=', path);
+               free((char *)path);
+               return 0;
+       }
+
+       if (skip_prefix(var, "receive.fsck.", &var)) {
+               if (is_valid_msg_type(var, value))
+                       strbuf_addf(&fsck_msg_types, "%c%s=%s",
+                               fsck_msg_types.len ? ',' : '=', var, value);
+               else
+                       warning("Skipping unknown msg id '%s'", var);
+               return 0;
+       }
+
        if (strcmp(var, "receive.fsckobjects") == 0) {
                receive_fsck_objects = git_config_bool(var, value);
                return 0;
@@ -911,7 +933,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
                        return "deletion prohibited";
                }
 
-               if (!strcmp(namespaced_name, head_name)) {
+               if (head_name && !strcmp(namespaced_name, head_name)) {
                        switch (deny_delete_current) {
                        case DENY_IGNORE:
                                break;
@@ -1490,7 +1512,8 @@ static const char *unpack(int err_fd, struct shallow_info *si)
                if (quiet)
                        argv_array_push(&child.args, "-q");
                if (fsck_objects)
-                       argv_array_push(&child.args, "--strict");
+                       argv_array_pushf(&child.args, "--strict%s",
+                               fsck_msg_types.buf);
                child.no_stdout = 1;
                child.err = err_fd;
                child.git_cmd = 1;
@@ -1508,7 +1531,8 @@ static const char *unpack(int err_fd, struct shallow_info *si)
                argv_array_pushl(&child.args, "index-pack",
                                 "--stdin", hdr_arg, keep_arg, NULL);
                if (fsck_objects)
-                       argv_array_push(&child.args, "--strict");
+                       argv_array_pushf(&child.args, "--strict%s",
+                               fsck_msg_types.buf);
                if (fix_thin)
                        argv_array_push(&child.args, "--fix-thin");
                child.out = -1;
index c2eb8ff8402068d7480a3afd262219cb03841ae1..7ed0e8501c149312676c0bce6fa52372a11b6fff 100644 (file)
@@ -13,6 +13,8 @@ static const char reflog_expire_usage[] =
 "git reflog expire [--expire=<time>] [--expire-unreachable=<time>] [--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] [--verbose] [--all] <refs>...";
 static const char reflog_delete_usage[] =
 "git reflog delete [--rewrite] [--updateref] [--dry-run | -n] [--verbose] <refs>...";
+static const char reflog_exists_usage[] =
+"git reflog exists <ref>";
 
 static unsigned long default_reflog_expire;
 static unsigned long default_reflog_expire_unreachable;
@@ -699,12 +701,38 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
        return status;
 }
 
+static int cmd_reflog_exists(int argc, const char **argv, const char *prefix)
+{
+       int i, start = 0;
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               else if (arg[0] == '-')
+                       usage(reflog_exists_usage);
+               else
+                       break;
+       }
+
+       start = i;
+
+       if (argc - start != 1)
+               usage(reflog_exists_usage);
+
+       if (check_refname_format(argv[start], REFNAME_ALLOW_ONELEVEL))
+               die("invalid ref format: %s", argv[start]);
+       return !reflog_exists(argv[start]);
+}
+
 /*
  * main "reflog"
  */
 
 static const char reflog_usage[] =
-"git reflog [ show | expire | delete ]";
+"git reflog [ show | expire | delete | exists ]";
 
 int cmd_reflog(int argc, const char **argv, const char *prefix)
 {
@@ -724,5 +752,8 @@ int cmd_reflog(int argc, const char **argv, const char *prefix)
        if (!strcmp(argv[1], "delete"))
                return cmd_reflog_delete(argc - 1, argv + 1, prefix);
 
+       if (!strcmp(argv[1], "exists"))
+               return cmd_reflog_exists(argc - 1, argv + 1, prefix);
+
        return cmd_log_reflog(argc, argv, prefix);
 }
index f4a6ec9f138bcfe3b2519412fa70c289a0024fb5..181668dedddef9bf79ab91d9740607cf31115a16 100644 (file)
@@ -581,7 +581,6 @@ static int migrate_file(struct remote *remote)
 {
        struct strbuf buf = STRBUF_INIT;
        int i;
-       const char *path = NULL;
 
        strbuf_addf(&buf, "remote.%s.url", remote->name);
        for (i = 0; i < remote->url_nr; i++)
@@ -601,11 +600,9 @@ static int migrate_file(struct remote *remote)
                        return error(_("Could not append '%s' to '%s'"),
                                        remote->fetch_refspec[i], buf.buf);
        if (remote->origin == REMOTE_REMOTES)
-               path = git_path("remotes/%s", remote->name);
+               unlink_or_warn(git_path("remotes/%s", remote->name));
        else if (remote->origin == REMOTE_BRANCHES)
-               path = git_path("branches/%s", remote->name);
-       if (path)
-               unlink_or_warn(path);
+               unlink_or_warn(git_path("branches/%s", remote->name));
        return 0;
 }
 
@@ -746,26 +743,6 @@ static int mv(int argc, const char **argv)
        return 0;
 }
 
-static int remove_branches(struct string_list *branches)
-{
-       struct strbuf err = STRBUF_INIT;
-       int i, result = 0;
-
-       if (repack_without_refs(branches, &err))
-               result |= error("%s", err.buf);
-       strbuf_release(&err);
-
-       for (i = 0; i < branches->nr; i++) {
-               struct string_list_item *item = branches->items + i;
-               const char *refname = item->string;
-
-               if (delete_ref(refname, NULL, 0))
-                       result |= error(_("Could not remove branch %s"), refname);
-       }
-
-       return result;
-}
-
 static int rm(int argc, const char **argv)
 {
        struct option options[] = {
@@ -822,7 +799,7 @@ static int rm(int argc, const char **argv)
        strbuf_release(&buf);
 
        if (!result)
-               result = remove_branches(&branches);
+               result = delete_refs(&branches);
        string_list_clear(&branches, 0);
 
        if (skipped.nr) {
@@ -1334,19 +1311,12 @@ static int prune_remote(const char *remote, int dry_run)
                string_list_append(&refs_to_prune, item->util);
        string_list_sort(&refs_to_prune);
 
-       if (!dry_run) {
-               struct strbuf err = STRBUF_INIT;
-               if (repack_without_refs(&refs_to_prune, &err))
-                       result |= error("%s", err.buf);
-               strbuf_release(&err);
-       }
+       if (!dry_run)
+               result |= delete_refs(&refs_to_prune);
 
        for_each_string_list_item(item, &states.stale) {
                const char *refname = item->util;
 
-               if (!dry_run)
-                       result |= delete_ref(refname, NULL, 0);
-
                if (dry_run)
                        printf_ln(_(" * [would prune] %s"),
                               abbrev_ref(refname, "refs/remotes/"));
index af7340c7bafbfbbf991f782139775e18b6ac570a..70b9b1eaf17f5447021f7174f31e5c19d9754486 100644 (file)
@@ -285,8 +285,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
        failed = 0;
        for_each_string_list_item(item, &names) {
                for (ext = 0; ext < ARRAY_SIZE(exts); ext++) {
-                       const char *fname_old;
-                       char *fname;
+                       char *fname, *fname_old;
                        fname = mkpathdup("%s/pack-%s%s", packdir,
                                                item->string, exts[ext].name);
                        if (!file_exists(fname)) {
@@ -294,7 +293,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
                                continue;
                        }
 
-                       fname_old = mkpath("%s/old-%s%s", packdir,
+                       fname_old = mkpathdup("%s/old-%s%s", packdir,
                                                item->string, exts[ext].name);
                        if (file_exists(fname_old))
                                if (unlink(fname_old))
@@ -302,10 +301,12 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
 
                        if (!failed && rename(fname, fname_old)) {
                                free(fname);
+                               free(fname_old);
                                failed = 1;
                                break;
                        } else {
                                string_list_append(&rollback, fname);
+                               free(fname_old);
                        }
                }
                if (failed)
@@ -314,13 +315,13 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
        if (failed) {
                struct string_list rollback_failure = STRING_LIST_INIT_DUP;
                for_each_string_list_item(item, &rollback) {
-                       const char *fname_old;
-                       char *fname;
+                       char *fname, *fname_old;
                        fname = mkpathdup("%s/%s", packdir, item->string);
-                       fname_old = mkpath("%s/old-%s", packdir, item->string);
+                       fname_old = mkpathdup("%s/old-%s", packdir, item->string);
                        if (rename(fname_old, fname))
                                string_list_append(&rollback_failure, fname);
                        free(fname);
+                       free(fname_old);
                }
 
                if (rollback_failure.nr) {
@@ -368,13 +369,14 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
        /* Remove the "old-" files */
        for_each_string_list_item(item, &names) {
                for (ext = 0; ext < ARRAY_SIZE(exts); ext++) {
-                       const char *fname;
-                       fname = mkpath("%s/old-%s%s",
-                                       packdir,
-                                       item->string,
-                                       exts[ext].name);
+                       char *fname;
+                       fname = mkpathdup("%s/old-%s%s",
+                                         packdir,
+                                         item->string,
+                                         exts[ext].name);
                        if (remove_path(fname))
                                warning(_("removing '%s' failed"), fname);
+                       free(fname);
                }
        }
 
index 0d52e7fa1d394d3c39437010592e4de428f53b34..6b3c469a331e62e13fb6f9626ee5a64f28747c06 100644 (file)
@@ -104,9 +104,9 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn)
                        continue;
                }
                full_hex = sha1_to_hex(sha1);
-               snprintf(ref, sizeof(ref), "refs/replace/%s", full_hex);
+               snprintf(ref, sizeof(ref), "%s%s", git_replace_ref_base, full_hex);
                /* read_ref() may reuse the buffer */
-               full_hex = ref + strlen("refs/replace/");
+               full_hex = ref + strlen(git_replace_ref_base);
                if (read_ref(ref, sha1)) {
                        error("replace ref '%s' not found.", full_hex);
                        had_error = 1;
@@ -134,7 +134,7 @@ static void check_ref_valid(unsigned char object[20],
                            int force)
 {
        if (snprintf(ref, ref_size,
-                    "refs/replace/%s",
+                    "%s%s", git_replace_ref_base,
                     sha1_to_hex(object)) > ref_size - 1)
                die("replace ref name too long: %.*s...", 50, ref);
        if (check_refname_format(ref, 0))
index 4c08ddc1cacc29f2098bb81b434d76f315346dd4..c503e75a590df93bcc46e564583352dcffa60891 100644 (file)
@@ -36,7 +36,7 @@ static const char *reset_type_names[] = {
 
 static inline int is_merge(void)
 {
-       return !access(git_path("MERGE_HEAD"), F_OK);
+       return !access(git_path_merge_head(), F_OK);
 }
 
 static int reset_index(const unsigned char *sha1, int reset_type, int quiet)
index b6232390a649a74f9c188a6f9abe8ab9651191a1..02d747dcb1a318af98ec06a7ab2f4e489e7c2625 100644 (file)
@@ -371,6 +371,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
                                        N_("output in stuck long form")),
                OPT_END(),
        };
+       static const char * const flag_chars = "*=?!";
 
        struct strbuf sb = STRBUF_INIT, parsed = STRBUF_INIT;
        const char **usage = NULL;
@@ -400,7 +401,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
        /* parse: (<short>|<short>,<long>|<long>)[*=?!]*<arghint>? SP+ <help> */
        while (strbuf_getline(&sb, stdin, '\n') != EOF) {
                const char *s;
-               const char *end;
+               const char *help;
                struct option *o;
 
                if (!sb.len)
@@ -410,54 +411,56 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
                memset(opts + onb, 0, sizeof(opts[onb]));
 
                o = &opts[onb++];
-               s = strchr(sb.buf, ' ');
-               if (!s || *sb.buf == ' ') {
+               help = strchr(sb.buf, ' ');
+               if (!help || *sb.buf == ' ') {
                        o->type = OPTION_GROUP;
                        o->help = xstrdup(skipspaces(sb.buf));
                        continue;
                }
 
                o->type = OPTION_CALLBACK;
-               o->help = xstrdup(skipspaces(s));
+               o->help = xstrdup(skipspaces(help));
                o->value = &parsed;
                o->flags = PARSE_OPT_NOARG;
                o->callback = &parseopt_dump;
 
-               /* Possible argument name hint */
-               end = s;
-               while (s > sb.buf && strchr("*=?!", s[-1]) == NULL)
-                       --s;
-               if (s != sb.buf && s != end)
-                       o->argh = xmemdupz(s, end - s);
-               if (s == sb.buf)
-                       s = end;
-
-               while (s > sb.buf && strchr("*=?!", s[-1])) {
-                       switch (*--s) {
+               /* name(s) */
+               s = strpbrk(sb.buf, flag_chars);
+               if (s == NULL)
+                       s = help;
+
+               if (s - sb.buf == 1) /* short option only */
+                       o->short_name = *sb.buf;
+               else if (sb.buf[1] != ',') /* long option only */
+                       o->long_name = xmemdupz(sb.buf, s - sb.buf);
+               else {
+                       o->short_name = *sb.buf;
+                       o->long_name = xmemdupz(sb.buf + 2, s - sb.buf - 2);
+               }
+
+               /* flags */
+               while (s < help) {
+                       switch (*s++) {
                        case '=':
                                o->flags &= ~PARSE_OPT_NOARG;
-                               break;
+                               continue;
                        case '?':
                                o->flags &= ~PARSE_OPT_NOARG;
                                o->flags |= PARSE_OPT_OPTARG;
-                               break;
+                               continue;
                        case '!':
                                o->flags |= PARSE_OPT_NONEG;
-                               break;
+                               continue;
                        case '*':
                                o->flags |= PARSE_OPT_HIDDEN;
-                               break;
+                               continue;
                        }
+                       s--;
+                       break;
                }
 
-               if (s - sb.buf == 1) /* short option only */
-                       o->short_name = *sb.buf;
-               else if (sb.buf[1] != ',') /* long option only */
-                       o->long_name = xmemdupz(sb.buf, s - sb.buf);
-               else {
-                       o->short_name = *sb.buf;
-                       o->long_name = xmemdupz(sb.buf + 2, s - sb.buf - 2);
-               }
+               if (s < help)
+                       o->argh = xmemdupz(s, help - s);
        }
        strbuf_release(&sb);
 
index b961e5ae7856626ebc063e94bfeee75383d27aeb..23b2962cb0c1443b202bcab0b73023109e06999c 100644 (file)
@@ -11,6 +11,7 @@
 #include "transport.h"
 #include "version.h"
 #include "sha1-array.h"
+#include "gpg-interface.h"
 
 static const char send_pack_usage[] =
 "git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] [<host>:]<directory> [<ref>...]\n"
@@ -113,6 +114,8 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
        int from_stdin = 0;
        struct push_cas_option cas = {0};
 
+       git_config(git_gpg_config, NULL);
+
        argv++;
        for (i = 1; i < argc; i++, argv++) {
                const char *arg = *argv;
index c0bab6aaa9755f5a0264601defd0414b0b458e17..007cc66a036429d602cf7afe19c163fcf6d06395 100644 (file)
@@ -138,7 +138,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
                ctx.abbrev = log->abbrev;
                ctx.subject = "";
                ctx.after_subject = "";
-               ctx.date_mode = DATE_NORMAL;
+               ctx.date_mode.type = DATE_NORMAL;
                ctx.output_encoding = get_log_output_encoding();
                pretty_print_commit(&ctx, commit, &ufbuf);
                buffer = ufbuf.buf;
index 323f85746358676cccecd646e6cb820e723031cf..c87c46eb387a8093af92864081673be1a36b5999 100644 (file)
@@ -784,7 +784,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                        else
                                msg++;
                        reflog_msg[i] = xstrfmt("(%s) %s",
-                                               show_date(timestamp, tz, 1),
+                                               show_date(timestamp, tz,
+                                                         DATE_MODE(RELATIVE)),
                                                msg);
                        free(logmsg);
                        sprintf(nth_desc, "%s@{%d}", *av, base+i);
index 5f6cdc5a03cd0d6df75c06453785f110cc3b08ee..cccca991046a6f4b6cf967a99a7f7a4396d4d588 100644 (file)
@@ -579,6 +579,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        struct create_tag_options opt;
        char *cleanup_arg = NULL;
        int annotate = 0, force = 0, lines = -1;
+       int create_reflog = 0;
        int cmdmode = 0;
        const char *msgfile = NULL, *keyid = NULL;
        struct msg_arg msg = { 0, STRBUF_INIT };
@@ -605,6 +606,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                OPT_STRING('u', "local-user", &keyid, N_("key-id"),
                                        N_("use another key to sign the tag")),
                OPT__FORCE(&force, N_("replace the tag if exists")),
+               OPT_BOOL(0, "create-reflog", &create_reflog, N_("create_reflog")),
 
                OPT_GROUP(N_("Tag listing options")),
                OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
@@ -733,7 +735,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
            ref_transaction_update(transaction, ref.buf, object, prev,
-                                  0, NULL, &err) ||
+                                  create_reflog ? REF_FORCE_CREATE_REFLOG : 0,
+                                  NULL, &err) ||
            ref_transaction_commit(transaction, &err))
                die("%s", err.buf);
        ref_transaction_free(transaction);
index ac6667242c562bb7a215982754211a3e035aae8a..7cc086f5f2fb4c4aa9a24a6b676f1bd6626f1b69 100644 (file)
@@ -20,6 +20,7 @@ static unsigned char buffer[4096];
 static unsigned int offset, len;
 static off_t consumed_bytes;
 static git_SHA_CTX ctx;
+static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT;
 
 /*
  * When running under --strict mode, objects whose reachability are
@@ -178,7 +179,7 @@ static void write_cached_object(struct object *obj, struct obj_buffer *obj_buf)
  * that have reachability requirements and calls this function.
  * Verify its reachability and validity recursively and write it out.
  */
-static int check_object(struct object *obj, int type, void *data)
+static int check_object(struct object *obj, int type, void *data, struct fsck_options *options)
 {
        struct obj_buffer *obj_buf;
 
@@ -203,10 +204,10 @@ static int check_object(struct object *obj, int type, void *data)
        obj_buf = lookup_object_buffer(obj);
        if (!obj_buf)
                die("Whoops! Cannot find object '%s'", sha1_to_hex(obj->sha1));
-       if (fsck_object(obj, obj_buf->buffer, obj_buf->size, 1,
-                       fsck_error_function))
+       if (fsck_object(obj, obj_buf->buffer, obj_buf->size, &fsck_options))
                die("Error in object");
-       if (fsck_walk(obj, check_object, NULL))
+       fsck_options.walk = check_object;
+       if (fsck_walk(obj, NULL, &fsck_options))
                die("Error on reachable objects of %s", sha1_to_hex(obj->sha1));
        write_cached_object(obj, obj_buf);
        return 0;
@@ -217,7 +218,7 @@ static void write_rest(void)
        unsigned i;
        for (i = 0; i < nr_objects; i++) {
                if (obj_list[i].obj)
-                       check_object(obj_list[i].obj, OBJ_ANY, NULL);
+                       check_object(obj_list[i].obj, OBJ_ANY, NULL, NULL);
        }
 }
 
@@ -529,6 +530,11 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
                                strict = 1;
                                continue;
                        }
+                       if (skip_prefix(arg, "--strict=", &arg)) {
+                               strict = 1;
+                               fsck_set_msg_types(&fsck_options, arg);
+                               continue;
+                       }
                        if (starts_with(arg, "--pack_header=")) {
                                struct pack_header *hdr;
                                char *c;
index 3d79a46b036fa7bea29bbc0941442b5dd92f648c..04dd00f734166acc9c0670de061c4612877a7187 100644 (file)
@@ -14,6 +14,7 @@ static const char * const git_update_ref_usage[] = {
 
 static char line_termination = '\n';
 static int update_flags;
+static unsigned create_reflog_flag;
 static const char *msg;
 
 /*
@@ -200,7 +201,8 @@ static const char *parse_cmd_update(struct ref_transaction *transaction,
 
        if (ref_transaction_update(transaction, refname,
                                   new_sha1, have_old ? old_sha1 : NULL,
-                                  update_flags, msg, &err))
+                                  update_flags | create_reflog_flag,
+                                  msg, &err))
                die("%s", err.buf);
 
        update_flags = 0;
@@ -231,7 +233,8 @@ static const char *parse_cmd_create(struct ref_transaction *transaction,
                die("create %s: extra input: %s", refname, next);
 
        if (ref_transaction_create(transaction, refname, new_sha1,
-                                  update_flags, msg, &err))
+                                  update_flags | create_reflog_flag,
+                                  msg, &err))
                die("%s", err.buf);
 
        update_flags = 0;
@@ -354,6 +357,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
        unsigned char sha1[20], oldsha1[20];
        int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0;
        unsigned int flags = 0;
+       int create_reflog = 0;
        struct option options[] = {
                OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")),
                OPT_BOOL('d', NULL, &delete, N_("delete the reference")),
@@ -361,6 +365,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
                                        N_("update <refname> not the one it points to")),
                OPT_BOOL('z', NULL, &end_null, N_("stdin has NUL-terminated arguments")),
                OPT_BOOL( 0 , "stdin", &read_stdin, N_("read updates from stdin")),
+               OPT_BOOL( 0 , "create-reflog", &create_reflog, N_("create_reflog")),
                OPT_END(),
        };
 
@@ -370,6 +375,8 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
        if (msg && !*msg)
                die("Refusing to perform update with empty message.");
 
+       create_reflog_flag = create_reflog ? REF_FORCE_CREATE_REFLOG : 0;
+
        if (read_stdin) {
                struct strbuf err = STRBUF_INIT;
                struct ref_transaction *transaction;
@@ -408,15 +415,29 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
                        die("%s: not a valid SHA1", value);
        }
 
-       hashclr(oldsha1); /* all-zero hash in case oldval is the empty string */
-       if (oldval && *oldval && get_sha1(oldval, oldsha1))
-               die("%s: not a valid old SHA1", oldval);
+       if (oldval) {
+               if (!*oldval)
+                       /*
+                        * The empty string implies that the reference
+                        * must not already exist:
+                        */
+                       hashclr(oldsha1);
+               else if (get_sha1(oldval, oldsha1))
+                       die("%s: not a valid old SHA1", oldval);
+       }
 
        if (no_deref)
                flags = REF_NODEREF;
        if (delete)
-               return delete_ref(refname, oldval ? oldsha1 : NULL, flags);
+               /*
+                * For purposes of backwards compatibility, we treat
+                * NULL_SHA1 as "don't care" here:
+                */
+               return delete_ref(refname,
+                                 (oldval && !is_null_sha1(oldsha1)) ? oldsha1 : NULL,
+                                 flags);
        else
                return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,
-                                 flags, UPDATE_REFS_DIE_ON_ERR);
+                                 flags | create_reflog_flag,
+                                 UPDATE_REFS_DIE_ON_ERR);
 }
index ec0c4e3d836f9242a8e34dd7fffa5ddfb835907b..38bedf8f9fe6a718af704d8be16b7787881f8c11 100644 (file)
@@ -18,25 +18,21 @@ static const char * const verify_commit_usage[] = {
                NULL
 };
 
-static int run_gpg_verify(const unsigned char *sha1, const char *buf, unsigned long size, int verbose)
+static int run_gpg_verify(const unsigned char *sha1, const char *buf, unsigned long size, unsigned flags)
 {
        struct signature_check signature_check;
+       int ret;
 
        memset(&signature_check, 0, sizeof(signature_check));
 
-       check_commit_signature(lookup_commit(sha1), &signature_check);
-
-       if (verbose && signature_check.payload)
-               fputs(signature_check.payload, stdout);
-
-       if (signature_check.gpg_output)
-               fputs(signature_check.gpg_output, stderr);
+       ret = check_commit_signature(lookup_commit(sha1), &signature_check);
+       print_signature_buffer(&signature_check, flags);
 
        signature_check_clear(&signature_check);
-       return signature_check.result != 'G';
+       return ret;
 }
 
-static int verify_commit(const char *name, int verbose)
+static int verify_commit(const char *name, unsigned flags)
 {
        enum object_type type;
        unsigned char sha1[20];
@@ -54,7 +50,7 @@ static int verify_commit(const char *name, int verbose)
                return error("%s: cannot verify a non-commit object of type %s.",
                                name, typename(type));
 
-       ret = run_gpg_verify(sha1, buf, size, verbose);
+       ret = run_gpg_verify(sha1, buf, size, flags);
 
        free(buf);
        return ret;
@@ -71,8 +67,10 @@ static int git_verify_commit_config(const char *var, const char *value, void *cb
 int cmd_verify_commit(int argc, const char **argv, const char *prefix)
 {
        int i = 1, verbose = 0, had_error = 0;
+       unsigned flags = 0;
        const struct option verify_commit_options[] = {
                OPT__VERBOSE(&verbose, N_("print commit contents")),
+               OPT_BIT(0, "raw", &flags, N_("print raw gpg status output"), GPG_VERIFY_RAW),
                OPT_END()
        };
 
@@ -83,11 +81,14 @@ int cmd_verify_commit(int argc, const char **argv, const char *prefix)
        if (argc <= i)
                usage_with_options(verify_commit_usage, verify_commit_options);
 
+       if (verbose)
+               flags |= GPG_VERIFY_VERBOSE;
+
        /* sometimes the program was terminated because this signal
         * was received in the process of writing the gpg input: */
        signal(SIGPIPE, SIG_IGN);
        while (i < argc)
-               if (verify_commit(argv[i++], verbose))
+               if (verify_commit(argv[i++], flags))
                        had_error = 1;
        return had_error;
 }
index 53c68fce3ac182db907224d12d89659469f4b9d6..00663f6a3003976aaa33f08ae7309fc190ea4748 100644 (file)
@@ -18,21 +18,30 @@ static const char * const verify_tag_usage[] = {
                NULL
 };
 
-static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
+static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags)
 {
+       struct signature_check sigc;
        int len;
+       int ret;
+
+       memset(&sigc, 0, sizeof(sigc));
 
        len = parse_signature(buf, size);
-       if (verbose)
-               write_in_full(1, buf, len);
 
-       if (size == len)
+       if (size == len) {
+               if (flags & GPG_VERIFY_VERBOSE)
+                       write_in_full(1, buf, len);
                return error("no signature found");
+       }
+
+       ret = check_signature(buf, len, buf + len, size - len, &sigc);
+       print_signature_buffer(&sigc, flags);
 
-       return verify_signed_buffer(buf, len, buf + len, size - len, NULL, NULL);
+       signature_check_clear(&sigc);
+       return ret;
 }
 
-static int verify_tag(const char *name, int verbose)
+static int verify_tag(const char *name, unsigned flags)
 {
        enum object_type type;
        unsigned char sha1[20];
@@ -52,7 +61,7 @@ static int verify_tag(const char *name, int verbose)
        if (!buf)
                return error("%s: unable to read file.", name);
 
-       ret = run_gpg_verify(buf, size, verbose);
+       ret = run_gpg_verify(buf, size, flags);
 
        free(buf);
        return ret;
@@ -69,8 +78,10 @@ static int git_verify_tag_config(const char *var, const char *value, void *cb)
 int cmd_verify_tag(int argc, const char **argv, const char *prefix)
 {
        int i = 1, verbose = 0, had_error = 0;
+       unsigned flags = 0;
        const struct option verify_tag_options[] = {
                OPT__VERBOSE(&verbose, N_("print tag contents")),
+               OPT_BIT(0, "raw", &flags, N_("print raw gpg status output"), GPG_VERIFY_RAW),
                OPT_END()
        };
 
@@ -81,11 +92,14 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix)
        if (argc <= i)
                usage_with_options(verify_tag_usage, verify_tag_options);
 
+       if (verbose)
+               flags |= GPG_VERIFY_VERBOSE;
+
        /* sometimes the program was terminated because this signal
         * was received in the process of writing the gpg input: */
        signal(SIGPIPE, SIG_IGN);
        while (i < argc)
-               if (verify_tag(argv[i++], verbose))
+               if (verify_tag(argv[i++], flags))
                        had_error = 1;
        return had_error;
 }
index 6a264ee749221cd6c6bf98ce68790bdec3dd82c5..430b51e7a74bb28ac1532ac836e9770121977096 100644 (file)
@@ -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)
index 32772b95644d2f32841984b25b685379c6914ad4..feace8bd90913ff28fa7ec2d052b355ad23935ca 100644 (file)
@@ -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;
index aa7b3e4a0a9d4bbb29574d71e23c05349be4c6fc..41c574663a14840aa726c46c19b071fa6555f0ef 100644 (file)
@@ -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 4f554664c5bd064405082797ee1e8786ebdcea75..4e25271e596ae0b4929c74907c4eaf9003e8dfc6 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -397,6 +397,7 @@ static inline enum object_type object_type(unsigned int mode)
 #define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH"
 #define CEILING_DIRECTORIES_ENVIRONMENT "GIT_CEILING_DIRECTORIES"
 #define NO_REPLACE_OBJECTS_ENVIRONMENT "GIT_NO_REPLACE_OBJECTS"
+#define GIT_REPLACE_REF_BASE_ENVIRONMENT "GIT_REPLACE_REF_BASE"
 #define GITATTRIBUTES_FILE ".gitattributes"
 #define INFOATTRIBUTES_FILE "info/attributes"
 #define ATTRIBUTE_MACRO_PREFIX "[attr]"
@@ -446,7 +447,17 @@ extern int get_common_dir(struct strbuf *sb, const char *gitdir);
 extern const char *get_git_namespace(void);
 extern const char *strip_namespace(const char *namespaced_ref);
 extern const char *get_git_work_tree(void);
-extern const char *read_gitfile(const char *path);
+
+#define READ_GITFILE_ERR_STAT_FAILED 1
+#define READ_GITFILE_ERR_NOT_A_FILE 2
+#define READ_GITFILE_ERR_OPEN_FAILED 3
+#define READ_GITFILE_ERR_READ_FAILED 4
+#define READ_GITFILE_ERR_INVALID_FORMAT 5
+#define READ_GITFILE_ERR_NO_PATH 6
+#define READ_GITFILE_ERR_NOT_A_REPO 7
+#define READ_GITFILE_ERR_TOO_LARGE 8
+extern const char *read_gitfile_gently(const char *path, int *return_error_code);
+#define read_gitfile(path) read_gitfile_gently((path), NULL)
 extern const char *resolve_gitdir(const char *suspect);
 extern void set_git_work_tree(const char *tree);
 
@@ -585,8 +596,6 @@ extern void update_index_if_able(struct index_state *, struct lock_file *);
 extern int hold_locked_index(struct lock_file *, int);
 extern void set_alternate_index_output(const char *);
 
-extern int delete_ref(const char *, const unsigned char *sha1, unsigned int flags);
-
 /* Environment bits from configuration mechanism */
 extern int trust_executable_bit;
 extern int trust_ctime;
@@ -622,6 +631,7 @@ extern unsigned long pack_size_limit_cfg;
  * been sought but there were none.
  */
 extern int check_replace_refs;
+extern char *git_replace_ref_base;
 
 extern int fsync_object_files;
 extern int core_preload_index;
@@ -698,22 +708,59 @@ extern int check_repository_format(void);
 #define DATA_CHANGED    0x0020
 #define TYPE_CHANGED    0x0040
 
+/*
+ * Return a statically allocated filename, either generically (mkpath), in
+ * the repository directory (git_path), or in a submodule's repository
+ * directory (git_path_submodule). In all cases, note that the result
+ * may be overwritten by another call to _any_ of the functions. Consider
+ * using the safer "dup" or "strbuf" formats below (in some cases, the
+ * unsafe versions have already been removed).
+ */
+extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+
 extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
        __attribute__((format (printf, 3, 4)));
 extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
        __attribute__((format (printf, 2, 3)));
+extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path,
+                                     const char *fmt, ...)
+       __attribute__((format (printf, 3, 4)));
 extern char *git_pathdup(const char *fmt, ...)
        __attribute__((format (printf, 1, 2)));
 extern char *mkpathdup(const char *fmt, ...)
        __attribute__((format (printf, 1, 2)));
-
-/* Return a statically allocated filename matching the sha1 signature */
-extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
-extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
-extern const char *git_path_submodule(const char *path, const char *fmt, ...)
+extern char *git_pathdup_submodule(const char *path, const char *fmt, ...)
        __attribute__((format (printf, 2, 3)));
+
 extern void report_linked_checkout_garbage(void);
 
+/*
+ * You can define a static memoized git path like:
+ *
+ *    static GIT_PATH_FUNC(git_path_foo, "FOO");
+ *
+ * or use one of the global ones below.
+ */
+#define GIT_PATH_FUNC(func, filename) \
+       const char *func(void) \
+       { \
+               static char *ret; \
+               if (!ret) \
+                       ret = git_pathdup(filename); \
+               return ret; \
+       }
+
+const char *git_path_cherry_pick_head(void);
+const char *git_path_revert_head(void);
+const char *git_path_squash_msg(void);
+const char *git_path_merge_msg(void);
+const char *git_path_merge_rr(void);
+const char *git_path_merge_mode(void);
+const char *git_path_merge_head(void);
+const char *git_path_fetch_head(void);
+const char *git_path_shallow(void);
+
 /*
  * Return the name of the file in the local object database that would
  * be used to store a loose object with the specified sha1.  The
@@ -935,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);
 
@@ -1020,76 +1067,10 @@ extern int get_oid_hex(const char *hex, struct object_id *sha1);
 
 extern char *sha1_to_hex(const unsigned char *sha1);   /* static buffer result! */
 extern char *oid_to_hex(const struct object_id *oid);  /* same static buffer as sha1_to_hex */
-extern int read_ref_full(const char *refname, int resolve_flags,
-                        unsigned char *sha1, int *flags);
-extern int read_ref(const char *refname, unsigned char *sha1);
 
-/*
- * Resolve a reference, recursively following symbolic refererences.
- *
- * Store the referred-to object's name in sha1 and return the name of
- * the non-symbolic reference that ultimately pointed at it.  The
- * return value, if not NULL, is a pointer into either a static buffer
- * or the input ref.
- *
- * If the reference cannot be resolved to an object, the behavior
- * depends on the RESOLVE_REF_READING flag:
- *
- * - If RESOLVE_REF_READING is set, return NULL.
- *
- * - If RESOLVE_REF_READING is not set, clear sha1 and return the name of
- *   the last reference name in the chain, which will either be a non-symbolic
- *   reference or an undefined reference.  If this is a prelude to
- *   "writing" to the ref, the return value is the name of the ref
- *   that will actually be created or changed.
- *
- * If the RESOLVE_REF_NO_RECURSE flag is passed, only resolves one
- * level of symbolic reference.  The value stored in sha1 for a symbolic
- * reference will always be null_sha1 in this case, and the return
- * value is the reference that the symref refers to directly.
- *
- * If flags is non-NULL, set the value that it points to the
- * combination of REF_ISPACKED (if the reference was found among the
- * packed references), REF_ISSYMREF (if the initial reference was a
- * symbolic reference), REF_BAD_NAME (if the reference name is ill
- * formed --- see RESOLVE_REF_ALLOW_BAD_NAME below), and REF_ISBROKEN
- * (if the ref is malformed or has a bad name). See refs.h for more detail
- * on each flag.
- *
- * If ref is not a properly-formatted, normalized reference, return
- * NULL.  If more than MAXDEPTH recursive symbolic lookups are needed,
- * give up and return NULL.
- *
- * RESOLVE_REF_ALLOW_BAD_NAME allows resolving refs even when their
- * name is invalid according to git-check-ref-format(1).  If the name
- * is bad then the value stored in sha1 will be null_sha1 and the two
- * flags REF_ISBROKEN and REF_BAD_NAME will be set.
- *
- * Even with RESOLVE_REF_ALLOW_BAD_NAME, names that escape the refs/
- * directory and do not consist of all caps and underscores cannot be
- * resolved. The function returns NULL for such ref names.
- * Caps and underscores refers to the special refs, such as HEAD,
- * FETCH_HEAD and friends, that all live outside of the refs/ directory.
- */
-#define RESOLVE_REF_READING 0x01
-#define RESOLVE_REF_NO_RECURSE 0x02
-#define RESOLVE_REF_ALLOW_BAD_NAME 0x04
-extern const char *resolve_ref_unsafe(const char *ref, int resolve_flags, unsigned char *sha1, int *flags);
-extern char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags);
-
-extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
-extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
 extern int interpret_branch_name(const char *str, int len, struct strbuf *);
 extern int get_sha1_mb(const char *str, unsigned char *sha1);
 
-/*
- * Return true iff abbrev_name is a possible abbreviation for
- * full_name according to the rules defined by ref_rev_parse_rules in
- * refs.c.
- */
-extern int refname_match(const char *abbrev_name, const char *full_name);
-
-extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg);
 extern int validate_headref(const char *ref);
 
 extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
@@ -1105,18 +1086,30 @@ extern void *read_object_with_reference(const unsigned char *sha1,
 extern struct object *peel_to_type(const char *name, int namelen,
                                   struct object *o, enum object_type);
 
-enum date_mode {
-       DATE_NORMAL = 0,
-       DATE_RELATIVE,
-       DATE_SHORT,
-       DATE_LOCAL,
-       DATE_ISO8601,
-       DATE_ISO8601_STRICT,
-       DATE_RFC2822,
-       DATE_RAW
+struct date_mode {
+       enum date_mode_type {
+               DATE_NORMAL = 0,
+               DATE_RELATIVE,
+               DATE_SHORT,
+               DATE_LOCAL,
+               DATE_ISO8601,
+               DATE_ISO8601_STRICT,
+               DATE_RFC2822,
+               DATE_STRFTIME,
+               DATE_RAW
+       } type;
+       const char *strftime_fmt;
 };
 
-const char *show_date(unsigned long time, int timezone, enum date_mode mode);
+/*
+ * Convenience helper for passing a constant type, like:
+ *
+ *   show_date(t, tz, DATE_MODE(NORMAL));
+ */
+#define DATE_MODE(t) date_mode_from_type(DATE_##t)
+struct date_mode *date_mode_from_type(enum date_mode_type type);
+
+const char *show_date(unsigned long time, int timezone, const struct date_mode *mode);
 void show_date_relative(unsigned long time, int tz, const struct timeval *now,
                        struct strbuf *timebuf);
 int parse_date(const char *date, struct strbuf *out);
@@ -1126,7 +1119,7 @@ void datestamp(struct strbuf *out);
 #define approxidate(s) approxidate_careful((s), NULL)
 unsigned long approxidate_careful(const char *, int *);
 unsigned long approxidate_relative(const char *date, const struct timeval *now);
-enum date_mode parse_date_format(const char *format);
+void parse_date_format(const char *format, struct date_mode *mode);
 int date_overflows(unsigned long date);
 
 #define IDENT_STRICT          1
@@ -1163,7 +1156,8 @@ extern int split_ident_line(struct ident_split *, const char *, int);
  * the ident_split. It will also sanity-check the values and produce
  * a well-known sentinel date if they appear bogus.
  */
-const char *show_ident_date(const struct ident_split *id, enum date_mode mode);
+const char *show_ident_date(const struct ident_split *id,
+                           const struct date_mode *mode);
 
 /*
  * Compare split idents for equality or strict ordering. Note that we
index f37ec3831f60cb9072d6cbf4f3d0126bc0681bf1..f84b449413d434b8145426503a98bcd4f9ad995f 100644 (file)
  * - int *indegree_at(struct indegree *, struct commit *);
  *
  *   This function locates the data associated with the given commit in
- *   the indegree slab, and returns the pointer to it.
+ *   the indegree slab, and returns the pointer to it.  The location to
+ *   store the data is allocated as necessary.
+ *
+ * - int *indegree_peek(struct indegree *, struct commit *);
+ *
+ *   This function is similar to indegree_at(), but it will return NULL
+ *   until a call to indegree_at() was made for the commit.
  *
  * - void init_indegree(struct indegree *);
  *   void init_indegree_with_stride(struct indegree *, int);
@@ -80,8 +86,9 @@ static MAYBE_UNUSED void clear_ ##slabname(struct slabname *s)                \
        s->slab = NULL;                                                 \
 }                                                                      \
                                                                        \
-static MAYBE_UNUSED elemtype *slabname## _at(struct slabname *s,       \
-                                      const struct commit *c)          \
+static MAYBE_UNUSED elemtype *slabname## _at_peek(struct slabname *s,  \
+                                                 const struct commit *c, \
+                                                 int add_if_missing)   \
 {                                                                      \
        int nth_slab, nth_slot;                                         \
                                                                        \
@@ -90,16 +97,33 @@ static MAYBE_UNUSED elemtype *slabname## _at(struct slabname *s,    \
                                                                        \
        if (s->slab_count <= nth_slab) {                                \
                int i;                                                  \
+               if (!add_if_missing)                                    \
+                       return NULL;                                    \
                REALLOC_ARRAY(s->slab, nth_slab + 1);                   \
                stat_ ##slabname## realloc++;                           \
                for (i = s->slab_count; i <= nth_slab; i++)             \
                        s->slab[i] = NULL;                              \
                s->slab_count = nth_slab + 1;                           \
        }                                                               \
-       if (!s->slab[nth_slab])                                         \
+       if (!s->slab[nth_slab]) {                                       \
+               if (!add_if_missing)                                    \
+                       return NULL;                                    \
                s->slab[nth_slab] = xcalloc(s->slab_size,               \
                                            sizeof(**s->slab) * s->stride);             \
-       return &s->slab[nth_slab][nth_slot * s->stride];                                \
+       }                                                               \
+       return &s->slab[nth_slab][nth_slot * s->stride];                \
+}                                                                      \
+                                                                       \
+static MAYBE_UNUSED elemtype *slabname## _at(struct slabname *s,       \
+                                            const struct commit *c)    \
+{                                                                      \
+       return slabname##_at_peek(s, c, 1);                             \
+}                                                                      \
+                                                                       \
+static MAYBE_UNUSED elemtype *slabname## _peek(struct slabname *s,     \
+                                            const struct commit *c)    \
+{                                                                      \
+       return slabname##_at_peek(s, c, 0);                             \
 }                                                                      \
                                                                        \
 static int stat_ ##slabname## realloc
index 6e2103cef60e8a0c2df18685280dbb5fda5c133f..494615d6ff15af5eb95e78053b2799e4c55577fc 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -245,7 +245,12 @@ void set_commit_buffer(struct commit *commit, void *buffer, unsigned long size)
 
 const void *get_cached_commit_buffer(const struct commit *commit, unsigned long *sizep)
 {
-       struct commit_buffer *v = buffer_slab_at(&buffer_slab, commit);
+       struct commit_buffer *v = buffer_slab_peek(&buffer_slab, commit);
+       if (!v) {
+               if (sizep)
+                       *sizep = 0;
+               return NULL;
+       }
        if (sizep)
                *sizep = v->size;
        return v->buffer;
@@ -272,24 +277,31 @@ const void *get_commit_buffer(const struct commit *commit, unsigned long *sizep)
 
 void unuse_commit_buffer(const struct commit *commit, const void *buffer)
 {
-       struct commit_buffer *v = buffer_slab_at(&buffer_slab, commit);
-       if (v->buffer != buffer)
+       struct commit_buffer *v = buffer_slab_peek(&buffer_slab, commit);
+       if (!(v && v->buffer == buffer))
                free((void *)buffer);
 }
 
 void free_commit_buffer(struct commit *commit)
 {
-       struct commit_buffer *v = buffer_slab_at(&buffer_slab, commit);
-       free(v->buffer);
-       v->buffer = NULL;
-       v->size = 0;
+       struct commit_buffer *v = buffer_slab_peek(&buffer_slab, commit);
+       if (v) {
+               free(v->buffer);
+               v->buffer = NULL;
+               v->size = 0;
+       }
 }
 
 const void *detach_commit_buffer(struct commit *commit, unsigned long *sizep)
 {
-       struct commit_buffer *v = buffer_slab_at(&buffer_slab, commit);
+       struct commit_buffer *v = buffer_slab_peek(&buffer_slab, commit);
        void *ret;
 
+       if (!v) {
+               if (sizep)
+                       *sizep = 0;
+               return NULL;
+       }
        ret = v->buffer;
        if (sizep)
                *sizep = v->size;
@@ -1232,33 +1244,24 @@ static void handle_signed_tag(struct commit *parent, struct commit_extra_header
        free(buf);
 }
 
-void check_commit_signature(const struct commit *commit, struct signature_check *sigc)
+int check_commit_signature(const struct commit *commit, struct signature_check *sigc)
 {
        struct strbuf payload = STRBUF_INIT;
        struct strbuf signature = STRBUF_INIT;
-       struct strbuf gpg_output = STRBUF_INIT;
-       struct strbuf gpg_status = STRBUF_INIT;
-       int status;
+       int ret = 1;
 
        sigc->result = 'N';
 
        if (parse_signed_commit(commit, &payload, &signature) <= 0)
                goto out;
-       status = verify_signed_buffer(payload.buf, payload.len,
-                                     signature.buf, signature.len,
-                                     &gpg_output, &gpg_status);
-       if (status && !gpg_output.len)
-               goto out;
-       sigc->payload = strbuf_detach(&payload, NULL);
-       sigc->gpg_output = strbuf_detach(&gpg_output, NULL);
-       sigc->gpg_status = strbuf_detach(&gpg_status, NULL);
-       parse_gpg_output(sigc);
+       ret = check_signature(payload.buf, payload.len, signature.buf,
+               signature.len, sigc);
 
  out:
-       strbuf_release(&gpg_status);
-       strbuf_release(&gpg_output);
        strbuf_release(&payload);
        strbuf_release(&signature);
+
+       return ret;
 }
 
 
index 9a1fa961d2ba0e3ec3eae9096ef49bc7155cdd92..5d58be0017a8eb26cd3f58c1e3b0b610f684beba 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -145,7 +145,7 @@ struct pretty_print_context {
        const char *subject;
        const char *after_subject;
        int preserve_subject;
-       enum date_mode date_mode;
+       struct date_mode date_mode;
        unsigned date_mode_explicit:1;
        int need_8bit_cte;
        char *notes_message;
@@ -379,7 +379,7 @@ extern void print_commit_list(struct commit_list *list,
  * at all.  This may allocate memory for sig->gpg_output, sig->gpg_status,
  * sig->signer and sig->key.
  */
-extern void check_commit_signature(const struct commit *commit, struct signature_check *sigc);
+extern int check_commit_signature(const struct commit *commit, struct signature_check *sigc);
 
 int compare_commits_by_commit_date(const void *a_, const void *b_, void *unused);
 
index c97c648d7e07fe9eeea1b1afff1ce55ac730cf4f..087771bb89d05b0353f6759cfd03da3a953a798b 100644 (file)
@@ -1667,7 +1667,10 @@ _git_push ()
 _git_rebase ()
 {
        local dir="$(__gitdir)"
-       if [ -d "$dir"/rebase-apply ] || [ -d "$dir"/rebase-merge ]; then
+       if [ -f "$dir"/rebase-merge/interactive ]; then
+               __gitcomp "--continue --skip --abort --edit-todo"
+               return
+       elif [ -d "$dir"/rebase-apply ] || [ -d "$dir"/rebase-merge ]; then
                __gitcomp "--continue --skip --abort"
                return
        fi
index 366f0bc1e9b5597f1714574700452cd241c14f8d..07b52bedf183231b5708caf1ce255a115c9a980c 100644 (file)
@@ -491,7 +491,7 @@ __git_ps1 ()
 
                if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ] &&
                   [ "$(git config --bool bash.showUntrackedFiles)" != "false" ] &&
-                  git ls-files --others --exclude-standard --error-unmatch -- ':/*' >/dev/null 2>/dev/null
+                  git ls-files --others --exclude-standard --directory --no-empty-directory --error-unmatch -- ':/*' >/dev/null 2>/dev/null
                then
                        u="%${ZSH_VERSION+%}"
                fi
index ee1916641e46c1adfaac31e27c78c11b023d6ffa..a3eb19de0418cbb3bccf54ba7e97e331f079e32e 100644 (file)
@@ -516,7 +516,7 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix)
 
                if (argc != 8)
                        return error("append-fetch-head takes 6 args");
-               filename = git_path("FETCH_HEAD");
+               filename = git_path_fetch_head();
                fp = fopen(filename, "a");
                if (!fp)
                        return error("cannot open %s: %s", filename, strerror(errno));
@@ -534,7 +534,7 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix)
 
                if (argc != 5)
                        return error("fetch-native-store takes 3 args");
-               filename = git_path("FETCH_HEAD");
+               filename = git_path_fetch_head();
                fp = fopen(filename, "a");
                if (!fp)
                        return error("cannot open %s: %s", filename, strerror(errno));
diff --git a/contrib/examples/git-am.sh b/contrib/examples/git-am.sh
new file mode 100755 (executable)
index 0000000..3b77028
--- /dev/null
@@ -0,0 +1,975 @@
+#!/bin/sh
+#
+# Copyright (c) 2005, 2006 Junio C Hamano
+
+SUBDIRECTORY_OK=Yes
+OPTIONS_KEEPDASHDASH=
+OPTIONS_STUCKLONG=t
+OPTIONS_SPEC="\
+git am [options] [(<mbox>|<Maildir>)...]
+git am [options] (--continue | --skip | --abort)
+--
+i,interactive   run interactively
+b,binary*       (historical option -- no-op)
+3,3way          allow fall back on 3way merging if needed
+q,quiet         be quiet
+s,signoff       add a Signed-off-by line to the commit message
+u,utf8          recode into utf8 (default)
+k,keep          pass -k flag to git-mailinfo
+keep-non-patch  pass -b flag to git-mailinfo
+m,message-id    pass -m flag to git-mailinfo
+keep-cr         pass --keep-cr flag to git-mailsplit for mbox format
+no-keep-cr      do not pass --keep-cr flag to git-mailsplit independent of am.keepcr
+c,scissors      strip everything before a scissors line
+whitespace=     pass it through git-apply
+ignore-space-change pass it through git-apply
+ignore-whitespace pass it through git-apply
+directory=      pass it through git-apply
+exclude=        pass it through git-apply
+include=        pass it through git-apply
+C=              pass it through git-apply
+p=              pass it through git-apply
+patch-format=   format the patch(es) are in
+reject          pass it through git-apply
+resolvemsg=     override error message when patch failure occurs
+continue        continue applying patches after resolving a conflict
+r,resolved      synonyms for --continue
+skip            skip the current patch
+abort           restore the original branch and abort the patching operation.
+committer-date-is-author-date    lie about committer date
+ignore-date     use current timestamp for author date
+rerere-autoupdate update the index with reused conflict resolution if possible
+S,gpg-sign?     GPG-sign commits
+rebasing*       (internal use for git-rebase)"
+
+. git-sh-setup
+. git-sh-i18n
+prefix=$(git rev-parse --show-prefix)
+set_reflog_action am
+require_work_tree
+cd_to_toplevel
+
+git var GIT_COMMITTER_IDENT >/dev/null ||
+       die "$(gettext "You need to set your committer info first")"
+
+if git rev-parse --verify -q HEAD >/dev/null
+then
+       HAS_HEAD=yes
+else
+       HAS_HEAD=
+fi
+
+cmdline="git am"
+if test '' != "$interactive"
+then
+       cmdline="$cmdline -i"
+fi
+if test '' != "$threeway"
+then
+       cmdline="$cmdline -3"
+fi
+
+empty_tree=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+
+sq () {
+       git rev-parse --sq-quote "$@"
+}
+
+stop_here () {
+    echo "$1" >"$dotest/next"
+    git rev-parse --verify -q HEAD >"$dotest/abort-safety"
+    exit 1
+}
+
+safe_to_abort () {
+       if test -f "$dotest/dirtyindex"
+       then
+               return 1
+       fi
+
+       if ! test -f "$dotest/abort-safety"
+       then
+               return 0
+       fi
+
+       abort_safety=$(cat "$dotest/abort-safety")
+       if test "z$(git rev-parse --verify -q HEAD)" = "z$abort_safety"
+       then
+               return 0
+       fi
+       gettextln "You seem to have moved HEAD since the last 'am' failure.
+Not rewinding to ORIG_HEAD" >&2
+       return 1
+}
+
+stop_here_user_resolve () {
+    if [ -n "$resolvemsg" ]; then
+           printf '%s\n' "$resolvemsg"
+           stop_here $1
+    fi
+    eval_gettextln "When you have resolved this problem, run \"\$cmdline --continue\".
+If you prefer to skip this patch, run \"\$cmdline --skip\" instead.
+To restore the original branch and stop patching, run \"\$cmdline --abort\"."
+
+    stop_here $1
+}
+
+go_next () {
+       rm -f "$dotest/$msgnum" "$dotest/msg" "$dotest/msg-clean" \
+               "$dotest/patch" "$dotest/info"
+       echo "$next" >"$dotest/next"
+       this=$next
+}
+
+cannot_fallback () {
+       echo "$1"
+       gettextln "Cannot fall back to three-way merge."
+       exit 1
+}
+
+fall_back_3way () {
+    O_OBJECT=$(cd "$GIT_OBJECT_DIRECTORY" && pwd)
+
+    rm -fr "$dotest"/patch-merge-*
+    mkdir "$dotest/patch-merge-tmp-dir"
+
+    # First see if the patch records the index info that we can use.
+    cmd="git apply $git_apply_opt --build-fake-ancestor" &&
+    cmd="$cmd "'"$dotest/patch-merge-tmp-index" "$dotest/patch"' &&
+    eval "$cmd" &&
+    GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
+    git write-tree >"$dotest/patch-merge-base+" ||
+    cannot_fallback "$(gettext "Repository lacks necessary blobs to fall back on 3-way merge.")"
+
+    say "$(gettext "Using index info to reconstruct a base tree...")"
+
+    cmd='GIT_INDEX_FILE="$dotest/patch-merge-tmp-index"'
+
+    if test -z "$GIT_QUIET"
+    then
+       eval "$cmd git diff-index --cached --diff-filter=AM --name-status HEAD"
+    fi
+
+    cmd="$cmd git apply --cached $git_apply_opt"' <"$dotest/patch"'
+    if eval "$cmd"
+    then
+       mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base"
+       mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index"
+    else
+       cannot_fallback "$(gettext "Did you hand edit your patch?
+It does not apply to blobs recorded in its index.")"
+    fi
+
+    test -f "$dotest/patch-merge-index" &&
+    his_tree=$(GIT_INDEX_FILE="$dotest/patch-merge-index" git write-tree) &&
+    orig_tree=$(cat "$dotest/patch-merge-base") &&
+    rm -fr "$dotest"/patch-merge-* || exit 1
+
+    say "$(gettext "Falling back to patching base and 3-way merge...")"
+
+    # 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.
+
+    eval GITHEAD_$his_tree='"$FIRSTLINE"'
+    export GITHEAD_$his_tree
+    if test -n "$GIT_QUIET"
+    then
+           GIT_MERGE_VERBOSITY=0 && export GIT_MERGE_VERBOSITY
+    fi
+    our_tree=$(git rev-parse --verify -q HEAD || echo $empty_tree)
+    git-merge-recursive $orig_tree -- $our_tree $his_tree || {
+           git rerere $allow_rerere_autoupdate
+           die "$(gettext "Failed to merge in the changes.")"
+    }
+    unset GITHEAD_$his_tree
+}
+
+clean_abort () {
+       test $# = 0 || echo >&2 "$@"
+       rm -fr "$dotest"
+       exit 1
+}
+
+patch_format=
+
+check_patch_format () {
+       # early return if patch_format was set from the command line
+       if test -n "$patch_format"
+       then
+               return 0
+       fi
+
+       # we default to mbox format if input is from stdin and for
+       # directories
+       if test $# = 0 || test "x$1" = "x-" || test -d "$1"
+       then
+               patch_format=mbox
+               return 0
+       fi
+
+       # otherwise, check the first few non-blank lines of the first
+       # patch to try to detect its format
+       {
+               # Start from first line containing non-whitespace
+               l1=
+               while test -z "$l1"
+               do
+                       read l1 || break
+               done
+               read l2
+               read l3
+               case "$l1" in
+               "From "* | "From: "*)
+                       patch_format=mbox
+                       ;;
+               '# This series applies on GIT commit'*)
+                       patch_format=stgit-series
+                       ;;
+               "# HG changeset patch")
+                       patch_format=hg
+                       ;;
+               *)
+                       # if the second line is empty and the third is
+                       # a From, Author or Date entry, this is very
+                       # likely an StGIT patch
+                       case "$l2,$l3" in
+                       ,"From: "* | ,"Author: "* | ,"Date: "*)
+                               patch_format=stgit
+                               ;;
+                       *)
+                               ;;
+                       esac
+                       ;;
+               esac
+               if test -z "$patch_format" &&
+                       test -n "$l1" &&
+                       test -n "$l2" &&
+                       test -n "$l3"
+               then
+                       # This begins with three non-empty lines.  Is this a
+                       # piece of e-mail a-la RFC2822?  Grab all the headers,
+                       # discarding the indented remainder of folded lines,
+                       # and see if it looks like that they all begin with the
+                       # header field names...
+                       tr -d '\015' <"$1" |
+                       sed -n -e '/^$/q' -e '/^[       ]/d' -e p |
+                       sane_egrep -v '^[!-9;-~]+:' >/dev/null ||
+                       patch_format=mbox
+               fi
+       } < "$1" || clean_abort
+}
+
+split_patches () {
+       case "$patch_format" in
+       mbox)
+               if test t = "$keepcr"
+               then
+                   keep_cr=--keep-cr
+               else
+                   keep_cr=
+               fi
+               git mailsplit -d"$prec" -o"$dotest" -b $keep_cr -- "$@" > "$dotest/last" ||
+               clean_abort
+               ;;
+       stgit-series)
+               if test $# -ne 1
+               then
+                       clean_abort "$(gettext "Only one StGIT patch series can be applied at once")"
+               fi
+               series_dir=$(dirname "$1")
+               series_file="$1"
+               shift
+               {
+                       set x
+                       while read filename
+                       do
+                               set "$@" "$series_dir/$filename"
+                       done
+                       # remove the safety x
+                       shift
+                       # remove the arg coming from the first-line comment
+                       shift
+               } < "$series_file" || clean_abort
+               # set the patch format appropriately
+               patch_format=stgit
+               # now handle the actual StGIT patches
+               split_patches "$@"
+               ;;
+       stgit)
+               this=0
+               test 0 -eq "$#" && set -- -
+               for stgit in "$@"
+               do
+                       this=$(expr "$this" + 1)
+                       msgnum=$(printf "%0${prec}d" $this)
+                       # Perl version of StGIT parse_patch. The first nonemptyline
+                       # not starting with Author, From or Date is the
+                       # subject, and the body starts with the next nonempty
+                       # line not starting with Author, From or Date
+                       @@PERL@@ -ne 'BEGIN { $subject = 0 }
+                               if ($subject > 1) { print ; }
+                               elsif (/^\s+$/) { next ; }
+                               elsif (/^Author:/) { s/Author/From/ ; print ;}
+                               elsif (/^(From|Date)/) { print ; }
+                               elsif ($subject) {
+                                       $subject = 2 ;
+                                       print "\n" ;
+                                       print ;
+                               } else {
+                                       print "Subject: ", $_ ;
+                                       $subject = 1;
+                               }
+                       ' -- "$stgit" >"$dotest/$msgnum" || clean_abort
+               done
+               echo "$this" > "$dotest/last"
+               this=
+               msgnum=
+               ;;
+       hg)
+               this=0
+               test 0 -eq "$#" && set -- -
+               for hg in "$@"
+               do
+                       this=$(( $this + 1 ))
+                       msgnum=$(printf "%0${prec}d" $this)
+                       # hg stores changeset metadata in #-commented lines preceding
+                       # the commit message and diff(s). The only metadata we care about
+                       # are the User and Date (Node ID and Parent are hashes which are
+                       # only relevant to the hg repository and thus not useful to us)
+                       # Since we cannot guarantee that the commit message is in
+                       # git-friendly format, we put no Subject: line and just consume
+                       # all of the message as the body
+                       LANG=C LC_ALL=C @@PERL@@ -M'POSIX qw(strftime)' -ne 'BEGIN { $subject = 0 }
+                               if ($subject) { print ; }
+                               elsif (/^\# User /) { s/\# User/From:/ ; print ; }
+                               elsif (/^\# Date /) {
+                                       my ($hashsign, $str, $time, $tz) = split ;
+                                       $tz_str = sprintf "%+05d", (0-$tz)/36;
+                                       print "Date: " .
+                                             strftime("%a, %d %b %Y %H:%M:%S ",
+                                                      gmtime($time-$tz))
+                                             . "$tz_str\n";
+                               } elsif (/^\# /) { next ; }
+                               else {
+                                       print "\n", $_ ;
+                                       $subject = 1;
+                               }
+                       ' -- "$hg" >"$dotest/$msgnum" || clean_abort
+               done
+               echo "$this" >"$dotest/last"
+               this=
+               msgnum=
+               ;;
+       *)
+               if test -n "$patch_format"
+               then
+                       clean_abort "$(eval_gettext "Patch format \$patch_format is not supported.")"
+               else
+                       clean_abort "$(gettext "Patch format detection failed.")"
+               fi
+               ;;
+       esac
+}
+
+prec=4
+dotest="$GIT_DIR/rebase-apply"
+sign= utf8=t keep= keepcr= skip= interactive= resolved= rebasing= abort=
+messageid= resolvemsg= resume= scissors= no_inbody_headers=
+git_apply_opt=
+committer_date_is_author_date=
+ignore_date=
+allow_rerere_autoupdate=
+gpg_sign_opt=
+threeway=
+
+if test "$(git config --bool --get am.messageid)" = true
+then
+    messageid=t
+fi
+
+if test "$(git config --bool --get am.keepcr)" = true
+then
+    keepcr=t
+fi
+
+while test $# != 0
+do
+       case "$1" in
+       -i|--interactive)
+               interactive=t ;;
+       -b|--binary)
+               gettextln >&2 "The -b/--binary option has been a no-op for long time, and
+it will be removed. Please do not use it anymore."
+               ;;
+       -3|--3way)
+               threeway=t ;;
+       -s|--signoff)
+               sign=t ;;
+       -u|--utf8)
+               utf8=t ;; # this is now default
+       --no-utf8)
+               utf8= ;;
+       -m|--message-id)
+               messageid=t ;;
+       --no-message-id)
+               messageid=f ;;
+       -k|--keep)
+               keep=t ;;
+       --keep-non-patch)
+               keep=b ;;
+       -c|--scissors)
+               scissors=t ;;
+       --no-scissors)
+               scissors=f ;;
+       -r|--resolved|--continue)
+               resolved=t ;;
+       --skip)
+               skip=t ;;
+       --abort)
+               abort=t ;;
+       --rebasing)
+               rebasing=t threeway=t ;;
+       --resolvemsg=*)
+               resolvemsg="${1#--resolvemsg=}" ;;
+       --whitespace=*|--directory=*|--exclude=*|--include=*)
+               git_apply_opt="$git_apply_opt $(sq "$1")" ;;
+       -C*|-p*)
+               git_apply_opt="$git_apply_opt $(sq "$1")" ;;
+       --patch-format=*)
+               patch_format="${1#--patch-format=}" ;;
+       --reject|--ignore-whitespace|--ignore-space-change)
+               git_apply_opt="$git_apply_opt $1" ;;
+       --committer-date-is-author-date)
+               committer_date_is_author_date=t ;;
+       --ignore-date)
+               ignore_date=t ;;
+       --rerere-autoupdate|--no-rerere-autoupdate)
+               allow_rerere_autoupdate="$1" ;;
+       -q|--quiet)
+               GIT_QUIET=t ;;
+       --keep-cr)
+               keepcr=t ;;
+       --no-keep-cr)
+               keepcr=f ;;
+       --gpg-sign)
+               gpg_sign_opt=-S ;;
+       --gpg-sign=*)
+               gpg_sign_opt="-S${1#--gpg-sign=}" ;;
+       --)
+               shift; break ;;
+       *)
+               usage ;;
+       esac
+       shift
+done
+
+# If the dotest directory exists, but we have finished applying all the
+# patches in them, clear it out.
+if test -d "$dotest" &&
+   test -f "$dotest/last" &&
+   test -f "$dotest/next" &&
+   last=$(cat "$dotest/last") &&
+   next=$(cat "$dotest/next") &&
+   test $# != 0 &&
+   test "$next" -gt "$last"
+then
+   rm -fr "$dotest"
+fi
+
+if test -d "$dotest" && test -f "$dotest/last" && test -f "$dotest/next"
+then
+       case "$#,$skip$resolved$abort" in
+       0,*t*)
+               # Explicit resume command and we do not have file, so
+               # we are happy.
+               : ;;
+       0,)
+               # No file input but without resume parameters; catch
+               # user error to feed us a patch from standard input
+               # when there is already $dotest.  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.
+               test -t 0
+               ;;
+       *)
+               false
+               ;;
+       esac ||
+       die "$(eval_gettext "previous rebase directory \$dotest still exists but mbox given.")"
+       resume=yes
+
+       case "$skip,$abort" in
+       t,t)
+               die "$(gettext "Please make up your mind. --skip or --abort?")"
+               ;;
+       t,)
+               git rerere clear
+               head_tree=$(git rev-parse --verify -q HEAD || echo $empty_tree) &&
+               git read-tree --reset -u $head_tree $head_tree &&
+               index_tree=$(git write-tree) &&
+               git read-tree -m -u $index_tree $head_tree
+               git read-tree $head_tree
+               ;;
+       ,t)
+               if test -f "$dotest/rebasing"
+               then
+                       exec git rebase --abort
+               fi
+               git rerere clear
+               if safe_to_abort
+               then
+                       head_tree=$(git rev-parse --verify -q HEAD || echo $empty_tree) &&
+                       git read-tree --reset -u $head_tree $head_tree &&
+                       index_tree=$(git write-tree) &&
+                       orig_head=$(git rev-parse --verify -q ORIG_HEAD || echo $empty_tree) &&
+                       git read-tree -m -u $index_tree $orig_head
+                       if git rev-parse --verify -q ORIG_HEAD >/dev/null 2>&1
+                       then
+                               git reset ORIG_HEAD
+                       else
+                               git read-tree $empty_tree
+                               curr_branch=$(git symbolic-ref HEAD 2>/dev/null) &&
+                               git update-ref -d $curr_branch
+                       fi
+               fi
+               rm -fr "$dotest"
+               exit ;;
+       esac
+       rm -f "$dotest/dirtyindex"
+else
+       # Possible stray $dotest directory in the independent-run
+       # case; in the --rebasing case, it is upto the caller
+       # (git-rebase--am) to take care of stray directories.
+       if test -d "$dotest" && test -z "$rebasing"
+       then
+               case "$skip,$resolved,$abort" in
+               ,,t)
+                       rm -fr "$dotest"
+                       exit 0
+                       ;;
+               *)
+                       die "$(eval_gettext "Stray \$dotest directory found.
+Use \"git am --abort\" to remove it.")"
+                       ;;
+               esac
+       fi
+
+       # Make sure we are not given --skip, --continue, or --abort
+       test "$skip$resolved$abort" = "" ||
+               die "$(gettext "Resolve operation not in progress, we are not resuming.")"
+
+       # Start afresh.
+       mkdir -p "$dotest" || exit
+
+       if test -n "$prefix" && test $# != 0
+       then
+               first=t
+               for arg
+               do
+                       test -n "$first" && {
+                               set x
+                               first=
+                       }
+                       if is_absolute_path "$arg"
+                       then
+                               set "$@" "$arg"
+                       else
+                               set "$@" "$prefix$arg"
+                       fi
+               done
+               shift
+       fi
+
+       check_patch_format "$@"
+
+       split_patches "$@"
+
+       # -i can and must be given when resuming; everything
+       # else is kept
+       echo " $git_apply_opt" >"$dotest/apply-opt"
+       echo "$threeway" >"$dotest/threeway"
+       echo "$sign" >"$dotest/sign"
+       echo "$utf8" >"$dotest/utf8"
+       echo "$keep" >"$dotest/keep"
+       echo "$messageid" >"$dotest/messageid"
+       echo "$scissors" >"$dotest/scissors"
+       echo "$no_inbody_headers" >"$dotest/no_inbody_headers"
+       echo "$GIT_QUIET" >"$dotest/quiet"
+       echo 1 >"$dotest/next"
+       if test -n "$rebasing"
+       then
+               : >"$dotest/rebasing"
+       else
+               : >"$dotest/applying"
+               if test -n "$HAS_HEAD"
+               then
+                       git update-ref ORIG_HEAD HEAD
+               else
+                       git update-ref -d ORIG_HEAD >/dev/null 2>&1
+               fi
+       fi
+fi
+
+git update-index -q --refresh
+
+case "$resolved" in
+'')
+       case "$HAS_HEAD" in
+       '')
+               files=$(git ls-files) ;;
+       ?*)
+               files=$(git diff-index --cached --name-only HEAD --) ;;
+       esac || exit
+       if test "$files"
+       then
+               test -n "$HAS_HEAD" && : >"$dotest/dirtyindex"
+               die "$(eval_gettext "Dirty index: cannot apply patches (dirty: \$files)")"
+       fi
+esac
+
+# Now, decide what command line options we will give to the git
+# commands we invoke, based on the result of parsing command line
+# options and previous invocation state stored in $dotest/ files.
+
+if test "$(cat "$dotest/utf8")" = t
+then
+       utf8=-u
+else
+       utf8=-n
+fi
+keep=$(cat "$dotest/keep")
+case "$keep" in
+t)
+       keep=-k ;;
+b)
+       keep=-b ;;
+*)
+       keep= ;;
+esac
+case "$(cat "$dotest/messageid")" in
+t)
+       messageid=-m ;;
+f)
+       messageid= ;;
+esac
+case "$(cat "$dotest/scissors")" in
+t)
+       scissors=--scissors ;;
+f)
+       scissors=--no-scissors ;;
+esac
+if test "$(cat "$dotest/no_inbody_headers")" = t
+then
+       no_inbody_headers=--no-inbody-headers
+else
+       no_inbody_headers=
+fi
+if test "$(cat "$dotest/quiet")" = t
+then
+       GIT_QUIET=t
+fi
+if test "$(cat "$dotest/threeway")" = t
+then
+       threeway=t
+fi
+git_apply_opt=$(cat "$dotest/apply-opt")
+if test "$(cat "$dotest/sign")" = t
+then
+       SIGNOFF=$(git var GIT_COMMITTER_IDENT | sed -e '
+                       s/>.*/>/
+                       s/^/Signed-off-by: /'
+               )
+else
+       SIGNOFF=
+fi
+
+last=$(cat "$dotest/last")
+this=$(cat "$dotest/next")
+if test "$skip" = t
+then
+       this=$(expr "$this" + 1)
+       resume=
+fi
+
+while test "$this" -le "$last"
+do
+       msgnum=$(printf "%0${prec}d" $this)
+       next=$(expr "$this" + 1)
+       test -f "$dotest/$msgnum" || {
+               resume=
+               go_next
+               continue
+       }
+
+       # If we are not resuming, parse and extract the patch information
+       # into separate files:
+       #  - info records the authorship and title
+       #  - msg is the rest of commit log message
+       #  - patch is the patch body.
+       #
+       # When we are resuming, these files are either already prepared
+       # by the user, or the user can tell us to do so by --continue flag.
+       case "$resume" in
+       '')
+               if test -f "$dotest/rebasing"
+               then
+                       commit=$(sed -e 's/^From \([0-9a-f]*\) .*/\1/' \
+                               -e q "$dotest/$msgnum") &&
+                       test "$(git cat-file -t "$commit")" = commit ||
+                               stop_here $this
+                       git cat-file commit "$commit" |
+                       sed -e '1,/^$/d' >"$dotest/msg-clean"
+                       echo "$commit" >"$dotest/original-commit"
+                       get_author_ident_from_commit "$commit" >"$dotest/author-script"
+                       git diff-tree --root --binary --full-index "$commit" >"$dotest/patch"
+               else
+                       git mailinfo $keep $no_inbody_headers $messageid $scissors $utf8 "$dotest/msg" "$dotest/patch" \
+                               <"$dotest/$msgnum" >"$dotest/info" ||
+                               stop_here $this
+
+                       # skip pine's internal folder data
+                       sane_grep '^Author: Mail System Internal Data$' \
+                               <"$dotest"/info >/dev/null &&
+                               go_next && continue
+
+                       test -s "$dotest/patch" || {
+                               eval_gettextln "Patch is empty.  Was it split wrong?
+If you would prefer to skip this patch, instead run \"\$cmdline --skip\".
+To restore the original branch and stop patching run \"\$cmdline --abort\"."
+                               stop_here $this
+                       }
+                       rm -f "$dotest/original-commit" "$dotest/author-script"
+                       {
+                               sed -n '/^Subject/ s/Subject: //p' "$dotest/info"
+                               echo
+                               cat "$dotest/msg"
+                       } |
+                       git stripspace > "$dotest/msg-clean"
+               fi
+               ;;
+       esac
+
+       if test -f "$dotest/author-script"
+       then
+               eval $(cat "$dotest/author-script")
+       else
+               GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$dotest/info")"
+               GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$dotest/info")"
+               GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$dotest/info")"
+       fi
+
+       if test -z "$GIT_AUTHOR_EMAIL"
+       then
+               gettextln "Patch does not have a valid e-mail address."
+               stop_here $this
+       fi
+
+       export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
+
+       case "$resume" in
+       '')
+           if test '' != "$SIGNOFF"
+           then
+               LAST_SIGNED_OFF_BY=$(
+                   sed -ne '/^Signed-off-by: /p' \
+                   "$dotest/msg-clean" |
+                   sed -ne '$p'
+               )
+               ADD_SIGNOFF=$(
+                   test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || {
+                   test '' = "$LAST_SIGNED_OFF_BY" && echo
+                   echo "$SIGNOFF"
+               })
+           else
+               ADD_SIGNOFF=
+           fi
+           {
+               if test -s "$dotest/msg-clean"
+               then
+                       cat "$dotest/msg-clean"
+               fi
+               if test '' != "$ADD_SIGNOFF"
+               then
+                       echo "$ADD_SIGNOFF"
+               fi
+           } >"$dotest/final-commit"
+           ;;
+       *)
+               case "$resolved$interactive" in
+               tt)
+                       # This is used only for interactive view option.
+                       git diff-index -p --cached HEAD -- >"$dotest/patch"
+                       ;;
+               esac
+       esac
+
+       resume=
+       if test "$interactive" = t
+       then
+           test -t 0 ||
+           die "$(gettext "cannot be interactive without stdin connected to a terminal.")"
+           action=again
+           while test "$action" = again
+           do
+               gettextln "Commit Body is:"
+               echo "--------------------------"
+               cat "$dotest/final-commit"
+               echo "--------------------------"
+               # TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
+               # in your translation. The program will only accept English
+               # input at this point.
+               gettext "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+               read reply
+               case "$reply" in
+               [yY]*) action=yes ;;
+               [aA]*) action=yes interactive= ;;
+               [nN]*) action=skip ;;
+               [eE]*) git_editor "$dotest/final-commit"
+                      action=again ;;
+               [vV]*) action=again
+                      git_pager "$dotest/patch" ;;
+               *)     action=again ;;
+               esac
+           done
+       else
+           action=yes
+       fi
+
+       if test $action = skip
+       then
+               go_next
+               continue
+       fi
+
+       hook="$(git rev-parse --git-path hooks/applypatch-msg)"
+       if test -x "$hook"
+       then
+               "$hook" "$dotest/final-commit" || stop_here $this
+       fi
+
+       if test -f "$dotest/final-commit"
+       then
+               FIRSTLINE=$(sed 1q "$dotest/final-commit")
+       else
+               FIRSTLINE=""
+       fi
+
+       say "$(eval_gettext "Applying: \$FIRSTLINE")"
+
+       case "$resolved" in
+       '')
+               # When we are allowed to fall back to 3-way later, don't give
+               # false errors during the initial attempt.
+               squelch=
+               if test "$threeway" = t
+               then
+                       squelch='>/dev/null 2>&1 '
+               fi
+               eval "git apply $squelch$git_apply_opt"' --index "$dotest/patch"'
+               apply_status=$?
+               ;;
+       t)
+               # Resolved means the user did all the hard work, and
+               # we do not have to do any patch application.  Just
+               # trust what the user has in the index file and the
+               # working tree.
+               resolved=
+               git diff-index --quiet --cached HEAD -- && {
+                       gettextln "No changes - did you forget to use 'git add'?
+If there is nothing left to stage, chances are that something else
+already introduced the same changes; you might want to skip this patch."
+                       stop_here_user_resolve $this
+               }
+               unmerged=$(git ls-files -u)
+               if test -n "$unmerged"
+               then
+                       gettextln "You still have unmerged paths in your index
+did you forget to use 'git add'?"
+                       stop_here_user_resolve $this
+               fi
+               apply_status=0
+               git rerere
+               ;;
+       esac
+
+       if test $apply_status != 0 && test "$threeway" = t
+       then
+               if (fall_back_3way)
+               then
+                   # Applying the patch to an earlier tree and merging the
+                   # result may have produced the same tree as ours.
+                   git diff-index --quiet --cached HEAD -- && {
+                       say "$(gettext "No changes -- Patch already applied.")"
+                       go_next
+                       continue
+                   }
+                   # clear apply_status -- we have successfully merged.
+                   apply_status=0
+               fi
+       fi
+       if test $apply_status != 0
+       then
+               eval_gettextln 'Patch failed at $msgnum $FIRSTLINE'
+               if test "$(git config --bool advice.amworkdir)" != false
+               then
+                       eval_gettextln 'The copy of the patch that failed is found in:
+   $dotest/patch'
+               fi
+               stop_here_user_resolve $this
+       fi
+
+       hook="$(git rev-parse --git-path hooks/pre-applypatch)"
+       if test -x "$hook"
+       then
+               "$hook" || stop_here $this
+       fi
+
+       tree=$(git write-tree) &&
+       commit=$(
+               if test -n "$ignore_date"
+               then
+                       GIT_AUTHOR_DATE=
+               fi
+               parent=$(git rev-parse --verify -q HEAD) ||
+               say >&2 "$(gettext "applying to an empty history")"
+
+               if test -n "$committer_date_is_author_date"
+               then
+                       GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
+                       export GIT_COMMITTER_DATE
+               fi &&
+               git commit-tree ${parent:+-p} $parent ${gpg_sign_opt:+"$gpg_sign_opt"} $tree  \
+                       <"$dotest/final-commit"
+       ) &&
+       git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent ||
+       stop_here $this
+
+       if test -f "$dotest/original-commit"; then
+               echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritten"
+       fi
+
+       hook="$(git rev-parse --git-path hooks/post-applypatch)"
+       test -x "$hook" && "$hook"
+
+       go_next
+done
+
+if test -s "$dotest"/rewritten; then
+    git notes copy --for-rewrite=rebase < "$dotest"/rewritten
+    hook="$(git rev-parse --git-path hooks/post-rewrite)"
+    if test -x "$hook"; then
+       "$hook" rebase < "$dotest"/rewritten
+    fi
+fi
+
+# If am was called with --rebasing (from git-rebase--am), it's up to
+# the caller to take care of housekeeping.
+if ! test -f "$dotest/rebasing"
+then
+       rm -fr "$dotest"
+       git gc --auto
+fi
diff --git a/contrib/examples/git-pull.sh b/contrib/examples/git-pull.sh
new file mode 100755 (executable)
index 0000000..26c5e9f
--- /dev/null
@@ -0,0 +1,381 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+# Fetch one or more remote refs and merge it/them into the current HEAD.
+
+SUBDIRECTORY_OK=Yes
+OPTIONS_KEEPDASHDASH=
+OPTIONS_STUCKLONG=Yes
+OPTIONS_SPEC="\
+git pull [options] [<repository> [<refspec>...]]
+
+Fetch one or more remote refs and integrate it/them with the current HEAD.
+--
+v,verbose                  be more verbose
+q,quiet                    be more quiet
+progress                   force progress reporting
+
+  Options related to merging
+r,rebase?false|true|preserve incorporate changes by rebasing rather than merging
+n!                         do not show a diffstat at the end of the merge
+stat                       show a diffstat at the end of the merge
+summary                    (synonym to --stat)
+log?n                      add (at most <n>) entries from shortlog to merge commit message
+squash                     create a single commit instead of doing a merge
+commit                     perform a commit if the merge succeeds (default)
+e,edit                       edit message before committing
+ff                         allow fast-forward
+ff-only!                   abort if fast-forward is not possible
+verify-signatures          verify that the named commit has a valid GPG signature
+s,strategy=strategy        merge strategy to use
+X,strategy-option=option   option for selected merge strategy
+S,gpg-sign?key-id          GPG sign commit
+
+  Options related to fetching
+all                        fetch from all remotes
+a,append                   append to .git/FETCH_HEAD instead of overwriting
+upload-pack=path           path to upload pack on remote end
+f,force                    force overwrite of local branch
+t,tags                     fetch all tags and associated objects
+p,prune                    prune remote-tracking branches no longer on remote
+recurse-submodules?on-demand control recursive fetching of submodules
+dry-run                    dry run
+k,keep                     keep downloaded pack
+depth=depth                deepen history of shallow clone
+unshallow                  convert to a complete repository
+update-shallow             accept refs that update .git/shallow
+refmap=refmap              specify fetch refmap
+"
+test $# -gt 0 && args="$*"
+. git-sh-setup
+. git-sh-i18n
+set_reflog_action "pull${args+ $args}"
+require_work_tree_exists
+cd_to_toplevel
+
+
+die_conflict () {
+    git diff-index --cached --name-status -r --ignore-submodules HEAD --
+    if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then
+       die "$(gettext "Pull is not possible because you have unmerged files.
+Please, fix them up in the work tree, and then use 'git add/rm <file>'
+as appropriate to mark resolution and make a commit.")"
+    else
+       die "$(gettext "Pull is not possible because you have unmerged files.")"
+    fi
+}
+
+die_merge () {
+    if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then
+       die "$(gettext "You have not concluded your merge (MERGE_HEAD exists).
+Please, commit your changes before you can merge.")"
+    else
+       die "$(gettext "You have not concluded your merge (MERGE_HEAD exists).")"
+    fi
+}
+
+test -z "$(git ls-files -u)" || die_conflict
+test -f "$GIT_DIR/MERGE_HEAD" && die_merge
+
+bool_or_string_config () {
+       git config --bool "$1" 2>/dev/null || git config "$1"
+}
+
+strategy_args= diffstat= no_commit= squash= no_ff= ff_only=
+log_arg= verbosity= progress= recurse_submodules= verify_signatures=
+merge_args= edit= rebase_args= all= append= upload_pack= force= tags= prune=
+keep= depth= unshallow= update_shallow= refmap=
+curr_branch=$(git symbolic-ref -q HEAD)
+curr_branch_short="${curr_branch#refs/heads/}"
+rebase=$(bool_or_string_config branch.$curr_branch_short.rebase)
+if test -z "$rebase"
+then
+       rebase=$(bool_or_string_config pull.rebase)
+fi
+
+# Setup default fast-forward options via `pull.ff`
+pull_ff=$(bool_or_string_config pull.ff)
+case "$pull_ff" in
+true)
+       no_ff=--ff
+       ;;
+false)
+       no_ff=--no-ff
+       ;;
+only)
+       ff_only=--ff-only
+       ;;
+esac
+
+
+dry_run=
+while :
+do
+       case "$1" in
+       -q|--quiet)
+               verbosity="$verbosity -q" ;;
+       -v|--verbose)
+               verbosity="$verbosity -v" ;;
+       --progress)
+               progress=--progress ;;
+       --no-progress)
+               progress=--no-progress ;;
+       -n|--no-stat|--no-summary)
+               diffstat=--no-stat ;;
+       --stat|--summary)
+               diffstat=--stat ;;
+       --log|--log=*|--no-log)
+               log_arg="$1" ;;
+       --no-commit)
+               no_commit=--no-commit ;;
+       --commit)
+               no_commit=--commit ;;
+       -e|--edit)
+               edit=--edit ;;
+       --no-edit)
+               edit=--no-edit ;;
+       --squash)
+               squash=--squash ;;
+       --no-squash)
+               squash=--no-squash ;;
+       --ff)
+               no_ff=--ff ;;
+       --no-ff)
+               no_ff=--no-ff ;;
+       --ff-only)
+               ff_only=--ff-only ;;
+       -s*|--strategy=*)
+               strategy_args="$strategy_args $1"
+               ;;
+       -X*|--strategy-option=*)
+               merge_args="$merge_args $(git rev-parse --sq-quote "$1")"
+               ;;
+       -r*|--rebase=*)
+               rebase="${1#*=}"
+               ;;
+       --rebase)
+               rebase=true
+               ;;
+       --no-rebase)
+               rebase=false
+               ;;
+       --recurse-submodules)
+               recurse_submodules=--recurse-submodules
+               ;;
+       --recurse-submodules=*)
+               recurse_submodules="$1"
+               ;;
+       --no-recurse-submodules)
+               recurse_submodules=--no-recurse-submodules
+               ;;
+       --verify-signatures)
+               verify_signatures=--verify-signatures
+               ;;
+       --no-verify-signatures)
+               verify_signatures=--no-verify-signatures
+               ;;
+       --gpg-sign|-S)
+               gpg_sign_args=-S
+               ;;
+       --gpg-sign=*)
+               gpg_sign_args=$(git rev-parse --sq-quote "-S${1#--gpg-sign=}")
+               ;;
+       -S*)
+               gpg_sign_args=$(git rev-parse --sq-quote "$1")
+               ;;
+       --dry-run)
+               dry_run=--dry-run
+               ;;
+       --all|--no-all)
+               all=$1 ;;
+       -a|--append|--no-append)
+               append=$1 ;;
+       --upload-pack=*|--no-upload-pack)
+               upload_pack=$1 ;;
+       -f|--force|--no-force)
+               force="$force $1" ;;
+       -t|--tags|--no-tags)
+               tags=$1 ;;
+       -p|--prune|--no-prune)
+               prune=$1 ;;
+       -k|--keep|--no-keep)
+               keep=$1 ;;
+       --depth=*|--no-depth)
+               depth=$1 ;;
+       --unshallow|--no-unshallow)
+               unshallow=$1 ;;
+       --update-shallow|--no-update-shallow)
+               update_shallow=$1 ;;
+       --refmap=*|--no-refmap)
+               refmap=$1 ;;
+       -h|--help-all)
+               usage
+               ;;
+       --)
+               shift
+               break
+               ;;
+       *)
+               usage
+               ;;
+       esac
+       shift
+done
+
+case "$rebase" in
+preserve)
+       rebase=true
+       rebase_args=--preserve-merges
+       ;;
+true|false|'')
+       ;;
+*)
+       echo "Invalid value for --rebase, should be true, false, or preserve"
+       usage
+       exit 1
+       ;;
+esac
+
+error_on_no_merge_candidates () {
+       exec >&2
+
+       if test true = "$rebase"
+       then
+               op_type=rebase
+               op_prep=against
+       else
+               op_type=merge
+               op_prep=with
+       fi
+
+       upstream=$(git config "branch.$curr_branch_short.merge")
+       remote=$(git config "branch.$curr_branch_short.remote")
+
+       if [ $# -gt 1 ]; then
+               if [ "$rebase" = true ]; then
+                       printf "There is no candidate for rebasing against "
+               else
+                       printf "There are no candidates for merging "
+               fi
+               echo "among the refs that you just fetched."
+               echo "Generally this means that you provided a wildcard refspec which had no"
+               echo "matches on the remote end."
+       elif [ $# -gt 0 ] && [ "$1" != "$remote" ]; then
+               echo "You asked to pull from the remote '$1', but did not specify"
+               echo "a branch. Because this is not the default configured remote"
+               echo "for your current branch, you must specify a branch on the command line."
+       elif [ -z "$curr_branch" -o -z "$upstream" ]; then
+               . git-parse-remote
+               error_on_missing_default_upstream "pull" $op_type $op_prep \
+                       "git pull <remote> <branch>"
+       else
+               echo "Your configuration specifies to $op_type $op_prep the ref '${upstream#refs/heads/}'"
+               echo "from the remote, but no such ref was fetched."
+       fi
+       exit 1
+}
+
+test true = "$rebase" && {
+       if ! git rev-parse -q --verify HEAD >/dev/null
+       then
+               # On an unborn branch
+               if test -f "$(git rev-parse --git-path index)"
+               then
+                       die "$(gettext "updating an unborn branch with changes added to the index")"
+               fi
+       else
+               require_clean_work_tree "pull with rebase" "Please commit or stash them."
+       fi
+       oldremoteref= &&
+       test -n "$curr_branch" &&
+       . git-parse-remote &&
+       remoteref="$(get_remote_merge_branch "$@" 2>/dev/null)" &&
+       oldremoteref=$(git merge-base --fork-point "$remoteref" $curr_branch 2>/dev/null)
+}
+orig_head=$(git rev-parse -q --verify HEAD)
+git fetch $verbosity $progress $dry_run $recurse_submodules $all $append \
+${upload_pack+"$upload_pack"} $force $tags $prune $keep $depth $unshallow $update_shallow \
+$refmap --update-head-ok "$@" || exit 1
+test -z "$dry_run" || exit 0
+
+curr_head=$(git rev-parse -q --verify HEAD)
+if test -n "$orig_head" && test "$curr_head" != "$orig_head"
+then
+       # The fetch involved updating the current branch.
+
+       # The working tree and the index file is still based on the
+       # $orig_head commit, but we are merging into $curr_head.
+       # First update the working tree to match $curr_head.
+
+       eval_gettextln "Warning: fetch updated the current branch head.
+Warning: fast-forwarding your working tree from
+Warning: commit \$orig_head." >&2
+       git update-index -q --refresh
+       git read-tree -u -m "$orig_head" "$curr_head" ||
+               die "$(eval_gettext "Cannot fast-forward your working tree.
+After making sure that you saved anything precious from
+$ git diff \$orig_head
+output, run
+$ git reset --hard
+to recover.")"
+
+fi
+
+merge_head=$(sed -e '/ not-for-merge   /d' \
+       -e 's/  .*//' "$GIT_DIR"/FETCH_HEAD | \
+       tr '\012' ' ')
+
+case "$merge_head" in
+'')
+       error_on_no_merge_candidates "$@"
+       ;;
+?*' '?*)
+       if test -z "$orig_head"
+       then
+               die "$(gettext "Cannot merge multiple branches into empty head")"
+       fi
+       if test true = "$rebase"
+       then
+               die "$(gettext "Cannot rebase onto multiple branches")"
+       fi
+       ;;
+esac
+
+# Pulling into unborn branch: a shorthand for branching off
+# FETCH_HEAD, for lazy typers.
+if test -z "$orig_head"
+then
+       # Two-way merge: we claim the index is based on an empty tree,
+       # and try to fast-forward to HEAD.  This ensures we will not
+       # lose index/worktree changes that the user already made on
+       # the unborn branch.
+       empty_tree=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+       git read-tree -m -u $empty_tree $merge_head &&
+       git update-ref -m "initial pull" HEAD $merge_head "$curr_head"
+       exit
+fi
+
+if test true = "$rebase"
+then
+       o=$(git show-branch --merge-base $curr_branch $merge_head $oldremoteref)
+       if test "$oldremoteref" = "$o"
+       then
+               unset oldremoteref
+       fi
+fi
+
+case "$rebase" in
+true)
+       eval="git-rebase $diffstat $strategy_args $merge_args $rebase_args $verbosity"
+       eval="$eval $gpg_sign_args"
+       eval="$eval --onto $merge_head ${oldremoteref:-$merge_head}"
+       ;;
+*)
+       eval="git-merge $diffstat $no_commit $verify_signatures $edit $squash $no_ff $ff_only"
+       eval="$eval $log_arg $strategy_args $merge_args $verbosity $progress"
+       eval="$eval $gpg_sign_args"
+       eval="$eval FETCH_HEAD"
+       ;;
+esac
+eval "exec $eval"
index 07bd77c4c8c63cff3ecac05f9ce922fda5fddf67..9f065718513c5c1e82d355aa9dab40ee421b765e 100755 (executable)
@@ -305,7 +305,7 @@ copy_commit()
        # We're going to set some environment vars here, so
        # do it in a subshell to get rid of them safely later
        debug copy_commit "{$1}" "{$2}" "{$3}"
-       git log -1 --pretty=format:'%an%n%ae%n%ad%n%cn%n%ce%n%cd%n%B' "$1" |
+       git log -1 --pretty=format:'%an%n%ae%n%aD%n%cn%n%ce%n%cD%n%B' "$1" |
        (
                read GIT_AUTHOR_NAME
                read GIT_AUTHOR_EMAIL
index bd3df97936eb77e39642ec917a8d3366945fb0a8..90519823be381390b7f89a1eb2f6007f1e8c86f4 100755 (executable)
@@ -94,6 +94,10 @@ test_expect_success 'add sub3' '
 # Back to mainline
 cd ..
 
+test_expect_success 'enable log.date=relative to catch errors' '
+       git config log.date relative
+'
+
 test_expect_success 'add main4' '
        create main4 &&
        git commit -m "main4" &&
diff --git a/date.c b/date.c
index 733d1b29b19fc7dda04d599eb6a311e809212775..8f9156909b8d9d2cad6425aa44deebdcd01b2168 100644 (file)
--- a/date.c
+++ b/date.c
@@ -160,18 +160,27 @@ void show_date_relative(unsigned long time, int tz,
                 (diff + 183) / 365);
 }
 
-const char *show_date(unsigned long time, int tz, enum date_mode mode)
+struct date_mode *date_mode_from_type(enum date_mode_type type)
+{
+       static struct date_mode mode;
+       if (type == DATE_STRFTIME)
+               die("BUG: cannot create anonymous strftime date_mode struct");
+       mode.type = type;
+       return &mode;
+}
+
+const char *show_date(unsigned long time, int tz, const struct date_mode *mode)
 {
        struct tm *tm;
        static struct strbuf timebuf = STRBUF_INIT;
 
-       if (mode == DATE_RAW) {
+       if (mode->type == DATE_RAW) {
                strbuf_reset(&timebuf);
                strbuf_addf(&timebuf, "%lu %+05d", time, tz);
                return timebuf.buf;
        }
 
-       if (mode == DATE_RELATIVE) {
+       if (mode->type == DATE_RELATIVE) {
                struct timeval now;
 
                strbuf_reset(&timebuf);
@@ -180,7 +189,7 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)
                return timebuf.buf;
        }
 
-       if (mode == DATE_LOCAL)
+       if (mode->type == DATE_LOCAL)
                tz = local_tzoffset(time);
 
        tm = time_to_tm(time, tz);
@@ -190,17 +199,17 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)
        }
 
        strbuf_reset(&timebuf);
-       if (mode == DATE_SHORT)
+       if (mode->type == DATE_SHORT)
                strbuf_addf(&timebuf, "%04d-%02d-%02d", tm->tm_year + 1900,
                                tm->tm_mon + 1, tm->tm_mday);
-       else if (mode == DATE_ISO8601)
+       else if (mode->type == DATE_ISO8601)
                strbuf_addf(&timebuf, "%04d-%02d-%02d %02d:%02d:%02d %+05d",
                                tm->tm_year + 1900,
                                tm->tm_mon + 1,
                                tm->tm_mday,
                                tm->tm_hour, tm->tm_min, tm->tm_sec,
                                tz);
-       else if (mode == DATE_ISO8601_STRICT) {
+       else if (mode->type == DATE_ISO8601_STRICT) {
                char sign = (tz >= 0) ? '+' : '-';
                tz = abs(tz);
                strbuf_addf(&timebuf, "%04d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d",
@@ -209,11 +218,13 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)
                                tm->tm_mday,
                                tm->tm_hour, tm->tm_min, tm->tm_sec,
                                sign, tz / 100, tz % 100);
-       } else if (mode == DATE_RFC2822)
+       } else if (mode->type == DATE_RFC2822)
                strbuf_addf(&timebuf, "%.3s, %d %.3s %d %02d:%02d:%02d %+05d",
                        weekday_names[tm->tm_wday], tm->tm_mday,
                        month_names[tm->tm_mon], tm->tm_year + 1900,
                        tm->tm_hour, tm->tm_min, tm->tm_sec, tz);
+       else if (mode->type == DATE_STRFTIME)
+               strbuf_addftime(&timebuf, mode->strftime_fmt, tm);
        else
                strbuf_addf(&timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d",
                                weekday_names[tm->tm_wday],
@@ -221,7 +232,7 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)
                                tm->tm_mday,
                                tm->tm_hour, tm->tm_min, tm->tm_sec,
                                tm->tm_year + 1900,
-                               (mode == DATE_LOCAL) ? 0 : ' ',
+                               (mode->type == DATE_LOCAL) ? 0 : ' ',
                                tz);
        return timebuf.buf;
 }
@@ -759,28 +770,31 @@ int parse_date(const char *date, struct strbuf *result)
        return 0;
 }
 
-enum date_mode parse_date_format(const char *format)
+void parse_date_format(const char *format, struct date_mode *mode)
 {
        if (!strcmp(format, "relative"))
-               return DATE_RELATIVE;
+               mode->type = DATE_RELATIVE;
        else if (!strcmp(format, "iso8601") ||
                 !strcmp(format, "iso"))
-               return DATE_ISO8601;
+               mode->type = DATE_ISO8601;
        else if (!strcmp(format, "iso8601-strict") ||
                 !strcmp(format, "iso-strict"))
-               return DATE_ISO8601_STRICT;
+               mode->type = DATE_ISO8601_STRICT;
        else if (!strcmp(format, "rfc2822") ||
                 !strcmp(format, "rfc"))
-               return DATE_RFC2822;
+               mode->type = DATE_RFC2822;
        else if (!strcmp(format, "short"))
-               return DATE_SHORT;
+               mode->type = DATE_SHORT;
        else if (!strcmp(format, "local"))
-               return DATE_LOCAL;
+               mode->type = DATE_LOCAL;
        else if (!strcmp(format, "default"))
-               return DATE_NORMAL;
+               mode->type = DATE_NORMAL;
        else if (!strcmp(format, "raw"))
-               return DATE_RAW;
-       else
+               mode->type = DATE_RAW;
+       else if (skip_prefix(format, "format:", &format)) {
+               mode->type = DATE_STRFTIME;
+               mode->strftime_fmt = xstrdup(format);
+       } else
                die("unknown date format %s", format);
 }
 
diff --git a/diff.c b/diff.c
index 0f17ec5506e616b0e1383ad689226c774e0df178..7deac90532234996088ae847b32ce07f5ba36339 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -3820,9 +3820,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                DIFF_OPT_SET(options, FIND_COPIES_HARDER);
        else if (!strcmp(arg, "--follow"))
                DIFF_OPT_SET(options, FOLLOW_RENAMES);
-       else if (!strcmp(arg, "--no-follow"))
+       else if (!strcmp(arg, "--no-follow")) {
                DIFF_OPT_CLR(options, FOLLOW_RENAMES);
-       else if (!strcmp(arg, "--color"))
+               DIFF_OPT_CLR(options, DEFAULT_FOLLOW_RENAMES);
+       } else if (!strcmp(arg, "--color"))
                options->use_color = 1;
        else if (skip_prefix(arg, "--color=", &arg)) {
                int value = git_config_colorbool(NULL, arg);
diff --git a/diff.h b/diff.h
index c7ad42addf8e4cd91b423d96526a5557473e5688..f7208ad103d4b81194e5b14e7e027a2d25388667 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -91,6 +91,7 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data)
 #define DIFF_OPT_DIRSTAT_BY_LINE     (1 << 28)
 #define DIFF_OPT_FUNCCONTEXT         (1 << 29)
 #define DIFF_OPT_PICKAXE_IGNORE_CASE (1 << 30)
+#define DIFF_OPT_DEFAULT_FOLLOW_RENAMES (1 << 31)
 
 #define DIFF_OPT_TST(opts, flag)    ((opts)->flags & DIFF_OPT_##flag)
 #define DIFF_OPT_TOUCHED(opts, flag)    ((opts)->touched_flags & DIFF_OPT_##flag)
diff --git a/dir.c b/dir.c
index 8209f8b8af1e1e3dbc9c166ca351b602de5e6f7a..c00c7e2b73603589ae1e3f49abcd931f4e761850 100644 (file)
--- 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))) {
                        /*
@@ -1848,7 +1847,7 @@ static const char *get_ident_string(void)
 
        if (sb.len)
                return sb.buf;
-       if (uname(&uts))
+       if (uname(&uts) < 0)
                die_errno(_("failed to get kernel name and information"));
        strbuf_addf(&sb, "Location %s, system %s %s %s", get_git_work_tree(),
                    uts.sysname, uts.release, uts.version);
@@ -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;
@@ -2185,6 +2174,8 @@ int remove_dir_recursively(struct strbuf *path, int flag)
        return remove_dir_recurse(path, flag, NULL);
 }
 
+static GIT_PATH_FUNC(git_path_info_exclude, "info/exclude")
+
 void setup_standard_excludes(struct dir_struct *dir)
 {
        const char *path;
@@ -2199,7 +2190,7 @@ void setup_standard_excludes(struct dir_struct *dir)
                                         dir->untracked ? &dir->ss_excludes_file : NULL);
 
        /* per repository user preference */
-       path = git_path("info/exclude");
+       path = git_path_info_exclude();
        if (!access_or_warn(path, R_OK, 0))
                add_excludes_from_file_1(dir, path,
                                         dir->untracked ? &dir->ss_info_exclude : NULL);
index 61c685b8d93091666891f0091d5005fe62543fc2..a533aed630c20a5e0718bcea4375875a44896416 100644 (file)
@@ -47,6 +47,7 @@ const char *askpass_program;
 const char *excludes_file;
 enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;
 int check_replace_refs = 1;
+char *git_replace_ref_base;
 enum eol core_eol = EOL_UNSET;
 enum safe_crlf safe_crlf = SAFE_CRLF_WARN;
 unsigned whitespace_rule_cfg = WS_DEFAULT_RULE;
@@ -110,6 +111,7 @@ const char * const local_repo_env[] = {
        GRAFT_ENVIRONMENT,
        INDEX_ENVIRONMENT,
        NO_REPLACE_OBJECTS_ENVIRONMENT,
+       GIT_REPLACE_REF_BASE_ENVIRONMENT,
        GIT_PREFIX_ENVIRONMENT,
        GIT_SHALLOW_FILE_ENVIRONMENT,
        GIT_COMMON_DIR_ENVIRONMENT,
@@ -156,6 +158,7 @@ static void setup_git_env(void)
        struct strbuf sb = STRBUF_INIT;
        const char *gitfile;
        const char *shallow_file;
+       const char *replace_ref_base;
 
        git_dir = getenv(GIT_DIR_ENVIRONMENT);
        if (!git_dir)
@@ -173,6 +176,9 @@ static void setup_git_env(void)
                                           "info/grafts", &git_graft_env);
        if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
                check_replace_refs = 0;
+       replace_ref_base = getenv(GIT_REPLACE_REF_BASE_ENVIRONMENT);
+       git_replace_ref_base = xstrdup(replace_ref_base ? replace_ref_base
+                                                         : "refs/replace/");
        namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));
        namespace_len = strlen(namespace);
        shallow_file = getenv(GIT_SHALLOW_FILE_ENVIRONMENT);
@@ -231,6 +237,8 @@ void set_git_work_tree(const char *new_work_tree)
        }
        git_work_tree_initialized = 1;
        work_tree = xstrdup(real_path(new_work_tree));
+       if (setenv(GIT_WORK_TREE_ENVIRONMENT, work_tree, 1))
+               die("could not set GIT_WORK_TREE to '%s'", work_tree);
 }
 
 const char *get_git_work_tree(void)
index 6378726993445694581ac36d6351f817fe488c72..6c7c3c9b669eb272f4da530aef459c9c39c4d294 100644 (file)
@@ -134,16 +134,17 @@ Format of STDIN stream:
   ts    ::= # time since the epoch in seconds, ascii base10 notation;
   tz    ::= # GIT style timezone;
 
-     # note: comments, ls and cat requests may appear anywhere
-     # in the input, except within a data command.  Any form
-     # of the data command always escapes the related input
-     # from comment processing.
+     # note: comments, get-mark, ls-tree, and cat-blob requests may
+     # appear anywhere in the input, except within a data command. Any
+     # form of the data command always escapes the related input from
+     # comment processing.
      #
      # In case it is not clear, the '#' that starts the comment
      # must be the first character on that line (an lf
      # preceded it).
      #
 
+  get_mark ::= 'get-mark' sp idnum lf;
   cat_blob ::= 'cat-blob' sp (hexsha1 | idnum) lf;
   ls_tree  ::= 'ls' sp (hexsha1 | idnum) sp path_str lf;
 
@@ -372,6 +373,7 @@ static volatile sig_atomic_t checkpoint_requested;
 static int cat_blob_fd = STDOUT_FILENO;
 
 static void parse_argv(void);
+static void parse_get_mark(const char *p);
 static void parse_cat_blob(const char *p);
 static void parse_ls(const char *p, struct branch *b);
 
@@ -405,7 +407,7 @@ static void dump_marks_helper(FILE *, uintmax_t, struct mark_set *);
 
 static void write_crash_report(const char *err)
 {
-       const char *loc = git_path("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid());
+       char *loc = git_pathdup("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid());
        FILE *rpt = fopen(loc, "w");
        struct branch *b;
        unsigned long lu;
@@ -413,6 +415,7 @@ static void write_crash_report(const char *err)
 
        if (!rpt) {
                error("can't write crash report %s: %s", loc, strerror(errno));
+               free(loc);
                return;
        }
 
@@ -421,7 +424,7 @@ static void write_crash_report(const char *err)
        fprintf(rpt, "fast-import crash report:\n");
        fprintf(rpt, "    fast-import process: %"PRIuMAX"\n", (uintmax_t) getpid());
        fprintf(rpt, "    parent process     : %"PRIuMAX"\n", (uintmax_t) getppid());
-       fprintf(rpt, "    at %s\n", show_date(time(NULL), 0, DATE_LOCAL));
+       fprintf(rpt, "    at %s\n", show_date(time(NULL), 0, DATE_MODE(LOCAL)));
        fputc('\n', rpt);
 
        fputs("fatal: ", rpt);
@@ -486,6 +489,7 @@ static void write_crash_report(const char *err)
        fputs("-------------------\n", rpt);
        fputs("END OF CRASH REPORT\n", rpt);
        fclose(rpt);
+       free(loc);
 }
 
 static void end_packfile(void);
@@ -921,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;
@@ -1692,13 +1696,13 @@ static int update_branch(struct branch *b)
        unsigned char old_sha1[20];
        struct strbuf err = STRBUF_INIT;
 
-       if (read_ref(b->name, old_sha1))
-               hashclr(old_sha1);
        if (is_null_sha1(b->sha1)) {
                if (b->delete)
-                       delete_ref(b->name, old_sha1, 0);
+                       delete_ref(b->name, NULL, 0);
                return 0;
        }
+       if (read_ref(b->name, old_sha1))
+               hashclr(old_sha1);
        if (!force_update && !is_null_sha1(old_sha1)) {
                struct commit *old_cmit, *new_cmit;
 
@@ -1907,6 +1911,10 @@ static int read_next_command(void)
                        rc->prev->next = rc;
                        cmd_tail = rc;
                }
+               if (skip_prefix(command_buf.buf, "get-mark ", &p)) {
+                       parse_get_mark(p);
+                       continue;
+               }
                if (skip_prefix(command_buf.buf, "cat-blob ", &p)) {
                        parse_cat_blob(p);
                        continue;
@@ -2588,14 +2596,12 @@ static int parse_from(struct branch *b)
 {
        const char *from;
        struct branch *s;
+       unsigned char sha1[20];
 
        if (!skip_prefix(command_buf.buf, "from ", &from))
                return 0;
 
-       if (b->branch_tree.tree) {
-               release_tree_content_recursive(b->branch_tree.tree);
-               b->branch_tree.tree = NULL;
-       }
+       hashcpy(sha1, b->branch_tree.versions[1].sha1);
 
        s = lookup_branch(from);
        if (b == s)
@@ -2610,14 +2616,16 @@ static int parse_from(struct branch *b)
                struct object_entry *oe = find_mark(idnum);
                if (oe->type != OBJ_COMMIT)
                        die("Mark :%" PRIuMAX " not a commit", idnum);
-               hashcpy(b->sha1, oe->idx.sha1);
-               if (oe->pack_id != MAX_PACK_ID) {
-                       unsigned long size;
-                       char *buf = gfi_unpack_entry(oe, &size);
-                       parse_from_commit(b, buf, size);
-                       free(buf);
-               } else
-                       parse_from_existing(b);
+               if (hashcmp(b->sha1, oe->idx.sha1)) {
+                       hashcpy(b->sha1, oe->idx.sha1);
+                       if (oe->pack_id != MAX_PACK_ID) {
+                               unsigned long size;
+                               char *buf = gfi_unpack_entry(oe, &size);
+                               parse_from_commit(b, buf, size);
+                               free(buf);
+                       } else
+                               parse_from_existing(b);
+               }
        } else if (!get_sha1(from, b->sha1)) {
                parse_from_existing(b);
                if (is_null_sha1(b->sha1))
@@ -2626,6 +2634,11 @@ static int parse_from(struct branch *b)
        else
                die("Invalid ref name or SHA1 expression: %s", from);
 
+       if (b->branch_tree.tree && hashcmp(sha1, b->branch_tree.versions[1].sha1)) {
+               release_tree_content_recursive(b->branch_tree.tree);
+               b->branch_tree.tree = NULL;
+       }
+
        read_next_command();
        return 1;
 }
@@ -2919,6 +2932,23 @@ static void cat_blob(struct object_entry *oe, unsigned char sha1[20])
                free(buf);
 }
 
+static void parse_get_mark(const char *p)
+{
+       struct object_entry *oe = oe;
+       char output[42];
+
+       /* get-mark SP <object> LF */
+       if (*p != ':')
+               die("Not a mark: %s", p);
+
+       oe = find_mark(parse_mark_ref_eol(p));
+       if (!oe)
+               die("Unknown mark: %s", command_buf.buf);
+
+       snprintf(output, sizeof(output), "%s\n", sha1_to_hex(oe->idx.sha1));
+       cat_blob_write(output, 41);
+}
+
 static void parse_cat_blob(const char *p)
 {
        struct object_entry *oe = oe;
@@ -3240,6 +3270,8 @@ static int parse_one_feature(const char *feature, int from_stream)
                option_import_marks(arg, from_stream, 1);
        } else if (skip_prefix(feature, "export-marks=", &arg)) {
                option_export_marks(arg);
+       } else if (!strcmp(feature, "get-mark")) {
+               ; /* Don't die - this feature is supported */
        } else if (!strcmp(feature, "cat-blob")) {
                ; /* Don't die - this feature is supported */
        } else if (!strcmp(feature, "relative-marks")) {
index a1367721527a6a354e80da76088c341eb1273bd5..820251a8d80518508514b728b4b2afd8a171e296 100644 (file)
@@ -948,7 +948,7 @@ static void update_shallow(struct fetch_pack_args *args,
 
        if (args->depth > 0 && alternate_shallow_file) {
                if (*alternate_shallow_file == '\0') { /* --unshallow */
-                       unlink_or_warn(git_path("shallow"));
+                       unlink_or_warn(git_path_shallow());
                        rollback_lock_file(&shallow_lock);
                } else
                        commit_lock_file(&shallow_lock);
diff --git a/fsck.c b/fsck.c
index 24b2a5f36c0c04c8bd956777eb86b23dfb0ca7fc..e41e753d6dcbb577148260a1392210d50fb0b325 100644 (file)
--- a/fsck.c
+++ b/fsck.c
@@ -8,8 +8,294 @@
 #include "fsck.h"
 #include "refs.h"
 #include "utf8.h"
+#include "sha1-array.h"
+
+#define FSCK_FATAL -1
+#define FSCK_INFO -2
+
+#define FOREACH_MSG_ID(FUNC) \
+       /* fatal errors */ \
+       FUNC(NUL_IN_HEADER, FATAL) \
+       FUNC(UNTERMINATED_HEADER, FATAL) \
+       /* errors */ \
+       FUNC(BAD_DATE, ERROR) \
+       FUNC(BAD_DATE_OVERFLOW, ERROR) \
+       FUNC(BAD_EMAIL, ERROR) \
+       FUNC(BAD_NAME, ERROR) \
+       FUNC(BAD_OBJECT_SHA1, ERROR) \
+       FUNC(BAD_PARENT_SHA1, ERROR) \
+       FUNC(BAD_TAG_OBJECT, ERROR) \
+       FUNC(BAD_TIMEZONE, ERROR) \
+       FUNC(BAD_TREE, ERROR) \
+       FUNC(BAD_TREE_SHA1, ERROR) \
+       FUNC(BAD_TYPE, ERROR) \
+       FUNC(DUPLICATE_ENTRIES, ERROR) \
+       FUNC(MISSING_AUTHOR, ERROR) \
+       FUNC(MISSING_COMMITTER, ERROR) \
+       FUNC(MISSING_EMAIL, ERROR) \
+       FUNC(MISSING_GRAFT, ERROR) \
+       FUNC(MISSING_NAME_BEFORE_EMAIL, ERROR) \
+       FUNC(MISSING_OBJECT, ERROR) \
+       FUNC(MISSING_PARENT, ERROR) \
+       FUNC(MISSING_SPACE_BEFORE_DATE, ERROR) \
+       FUNC(MISSING_SPACE_BEFORE_EMAIL, ERROR) \
+       FUNC(MISSING_TAG, ERROR) \
+       FUNC(MISSING_TAG_ENTRY, ERROR) \
+       FUNC(MISSING_TAG_OBJECT, ERROR) \
+       FUNC(MISSING_TREE, ERROR) \
+       FUNC(MISSING_TYPE, ERROR) \
+       FUNC(MISSING_TYPE_ENTRY, ERROR) \
+       FUNC(MULTIPLE_AUTHORS, ERROR) \
+       FUNC(TAG_OBJECT_NOT_TAG, ERROR) \
+       FUNC(TREE_NOT_SORTED, ERROR) \
+       FUNC(UNKNOWN_TYPE, ERROR) \
+       FUNC(ZERO_PADDED_DATE, ERROR) \
+       /* warnings */ \
+       FUNC(BAD_FILEMODE, WARN) \
+       FUNC(EMPTY_NAME, WARN) \
+       FUNC(FULL_PATHNAME, WARN) \
+       FUNC(HAS_DOT, WARN) \
+       FUNC(HAS_DOTDOT, WARN) \
+       FUNC(HAS_DOTGIT, WARN) \
+       FUNC(NULL_SHA1, WARN) \
+       FUNC(ZERO_PADDED_FILEMODE, WARN) \
+       /* infos (reported as warnings, but ignored by default) */ \
+       FUNC(BAD_TAG_NAME, INFO) \
+       FUNC(MISSING_TAGGER_ENTRY, INFO)
+
+#define MSG_ID(id, msg_type) FSCK_MSG_##id,
+enum fsck_msg_id {
+       FOREACH_MSG_ID(MSG_ID)
+       FSCK_MSG_MAX
+};
+#undef MSG_ID
+
+#define STR(x) #x
+#define MSG_ID(id, msg_type) { STR(id), NULL, FSCK_##msg_type },
+static struct {
+       const char *id_string;
+       const char *downcased;
+       int msg_type;
+} msg_id_info[FSCK_MSG_MAX + 1] = {
+       FOREACH_MSG_ID(MSG_ID)
+       { NULL, NULL, -1 }
+};
+#undef MSG_ID
+
+static int parse_msg_id(const char *text)
+{
+       int i;
+
+       if (!msg_id_info[0].downcased) {
+               /* convert id_string to lower case, without underscores. */
+               for (i = 0; i < FSCK_MSG_MAX; i++) {
+                       const char *p = msg_id_info[i].id_string;
+                       int len = strlen(p);
+                       char *q = xmalloc(len);
+
+                       msg_id_info[i].downcased = q;
+                       while (*p)
+                               if (*p == '_')
+                                       p++;
+                               else
+                                       *(q)++ = tolower(*(p)++);
+                       *q = '\0';
+               }
+       }
+
+       for (i = 0; i < FSCK_MSG_MAX; i++)
+               if (!strcmp(text, msg_id_info[i].downcased))
+                       return i;
+
+       return -1;
+}
+
+static int fsck_msg_type(enum fsck_msg_id msg_id,
+       struct fsck_options *options)
+{
+       int msg_type;
+
+       assert(msg_id >= 0 && msg_id < FSCK_MSG_MAX);
+
+       if (options->msg_type)
+               msg_type = options->msg_type[msg_id];
+       else {
+               msg_type = msg_id_info[msg_id].msg_type;
+               if (options->strict && msg_type == FSCK_WARN)
+                       msg_type = FSCK_ERROR;
+       }
+
+       return msg_type;
+}
+
+static void init_skiplist(struct fsck_options *options, const char *path)
+{
+       static struct sha1_array skiplist = SHA1_ARRAY_INIT;
+       int sorted, fd;
+       char buffer[41];
+       unsigned char sha1[20];
+
+       if (options->skiplist)
+               sorted = options->skiplist->sorted;
+       else {
+               sorted = 1;
+               options->skiplist = &skiplist;
+       }
+
+       fd = open(path, O_RDONLY);
+       if (fd < 0)
+               die("Could not open skip list: %s", path);
+       for (;;) {
+               int result = read_in_full(fd, buffer, sizeof(buffer));
+               if (result < 0)
+                       die_errno("Could not read '%s'", path);
+               if (!result)
+                       break;
+               if (get_sha1_hex(buffer, sha1) || buffer[40] != '\n')
+                       die("Invalid SHA-1: %s", buffer);
+               sha1_array_append(&skiplist, sha1);
+               if (sorted && skiplist.nr > 1 &&
+                               hashcmp(skiplist.sha1[skiplist.nr - 2],
+                                       sha1) > 0)
+                       sorted = 0;
+       }
+       close(fd);
+
+       if (sorted)
+               skiplist.sorted = 1;
+}
+
+static int parse_msg_type(const char *str)
+{
+       if (!strcmp(str, "error"))
+               return FSCK_ERROR;
+       else if (!strcmp(str, "warn"))
+               return FSCK_WARN;
+       else if (!strcmp(str, "ignore"))
+               return FSCK_IGNORE;
+       else
+               die("Unknown fsck message type: '%s'", str);
+}
+
+int is_valid_msg_type(const char *msg_id, const char *msg_type)
+{
+       if (parse_msg_id(msg_id) < 0)
+               return 0;
+       parse_msg_type(msg_type);
+       return 1;
+}
+
+void fsck_set_msg_type(struct fsck_options *options,
+               const char *msg_id, const char *msg_type)
+{
+       int id = parse_msg_id(msg_id), type;
+
+       if (id < 0)
+               die("Unhandled message id: %s", msg_id);
+       type = parse_msg_type(msg_type);
+
+       if (type != FSCK_ERROR && msg_id_info[id].msg_type == FSCK_FATAL)
+               die("Cannot demote %s to %s", msg_id, msg_type);
+
+       if (!options->msg_type) {
+               int i;
+               int *msg_type = xmalloc(sizeof(int) * FSCK_MSG_MAX);
+               for (i = 0; i < FSCK_MSG_MAX; i++)
+                       msg_type[i] = fsck_msg_type(i, options);
+               options->msg_type = msg_type;
+       }
+
+       options->msg_type[id] = type;
+}
+
+void fsck_set_msg_types(struct fsck_options *options, const char *values)
+{
+       char *buf = xstrdup(values), *to_free = buf;
+       int done = 0;
+
+       while (!done) {
+               int len = strcspn(buf, " ,|"), equal;
+
+               done = !buf[len];
+               if (!len) {
+                       buf++;
+                       continue;
+               }
+               buf[len] = '\0';
+
+               for (equal = 0;
+                    equal < len && buf[equal] != '=' && buf[equal] != ':';
+                    equal++)
+                       buf[equal] = tolower(buf[equal]);
+               buf[equal] = '\0';
+
+               if (!strcmp(buf, "skiplist")) {
+                       if (equal == len)
+                               die("skiplist requires a path");
+                       init_skiplist(options, buf + equal + 1);
+                       buf += len + 1;
+                       continue;
+               }
+
+               if (equal == len)
+                       die("Missing '=': '%s'", buf);
 
-static int fsck_walk_tree(struct tree *tree, fsck_walk_func walk, void *data)
+               fsck_set_msg_type(options, buf, buf + equal + 1);
+               buf += len + 1;
+       }
+       free(to_free);
+}
+
+static void append_msg_id(struct strbuf *sb, const char *msg_id)
+{
+       for (;;) {
+               char c = *(msg_id)++;
+
+               if (!c)
+                       break;
+               if (c != '_')
+                       strbuf_addch(sb, tolower(c));
+               else {
+                       assert(*msg_id);
+                       strbuf_addch(sb, *(msg_id)++);
+               }
+       }
+
+       strbuf_addstr(sb, ": ");
+}
+
+__attribute__((format (printf, 4, 5)))
+static int report(struct fsck_options *options, struct object *object,
+       enum fsck_msg_id id, const char *fmt, ...)
+{
+       va_list ap;
+       struct strbuf sb = STRBUF_INIT;
+       int msg_type = fsck_msg_type(id, options), result;
+
+       if (msg_type == FSCK_IGNORE)
+               return 0;
+
+       if (options->skiplist && object &&
+                       sha1_array_lookup(options->skiplist, object->sha1) >= 0)
+               return 0;
+
+       if (msg_type == FSCK_FATAL)
+               msg_type = FSCK_ERROR;
+       else if (msg_type == FSCK_INFO)
+               msg_type = FSCK_WARN;
+
+       append_msg_id(&sb, msg_id_info[id].id_string);
+
+       va_start(ap, fmt);
+       strbuf_vaddf(&sb, fmt, ap);
+       result = options->error_func(object, msg_type, sb.buf);
+       strbuf_release(&sb);
+       va_end(ap);
+
+       return result;
+}
+
+static int fsck_walk_tree(struct tree *tree, void *data, struct fsck_options *options)
 {
        struct tree_desc desc;
        struct name_entry entry;
@@ -25,9 +311,9 @@ static int fsck_walk_tree(struct tree *tree, fsck_walk_func walk, void *data)
                if (S_ISGITLINK(entry.mode))
                        continue;
                if (S_ISDIR(entry.mode))
-                       result = walk(&lookup_tree(entry.sha1)->object, OBJ_TREE, data);
+                       result = options->walk(&lookup_tree(entry.sha1)->object, OBJ_TREE, data, options);
                else if (S_ISREG(entry.mode) || S_ISLNK(entry.mode))
-                       result = walk(&lookup_blob(entry.sha1)->object, OBJ_BLOB, data);
+                       result = options->walk(&lookup_blob(entry.sha1)->object, OBJ_BLOB, data, options);
                else {
                        result = error("in tree %s: entry %s has bad mode %.6o",
                                        sha1_to_hex(tree->object.sha1), entry.path, entry.mode);
@@ -40,7 +326,7 @@ static int fsck_walk_tree(struct tree *tree, fsck_walk_func walk, void *data)
        return res;
 }
 
-static int fsck_walk_commit(struct commit *commit, fsck_walk_func walk, void *data)
+static int fsck_walk_commit(struct commit *commit, void *data, struct fsck_options *options)
 {
        struct commit_list *parents;
        int res;
@@ -49,14 +335,14 @@ static int fsck_walk_commit(struct commit *commit, fsck_walk_func walk, void *da
        if (parse_commit(commit))
                return -1;
 
-       result = walk((struct object *)commit->tree, OBJ_TREE, data);
+       result = options->walk((struct object *)commit->tree, OBJ_TREE, data, options);
        if (result < 0)
                return result;
        res = result;
 
        parents = commit->parents;
        while (parents) {
-               result = walk((struct object *)parents->item, OBJ_COMMIT, data);
+               result = options->walk((struct object *)parents->item, OBJ_COMMIT, data, options);
                if (result < 0)
                        return result;
                if (!res)
@@ -66,14 +352,14 @@ static int fsck_walk_commit(struct commit *commit, fsck_walk_func walk, void *da
        return res;
 }
 
-static int fsck_walk_tag(struct tag *tag, fsck_walk_func walk, void *data)
+static int fsck_walk_tag(struct tag *tag, void *data, struct fsck_options *options)
 {
        if (parse_tag(tag))
                return -1;
-       return walk(tag->tagged, OBJ_ANY, data);
+       return options->walk(tag->tagged, OBJ_ANY, data, options);
 }
 
-int fsck_walk(struct object *obj, fsck_walk_func walk, void *data)
+int fsck_walk(struct object *obj, void *data, struct fsck_options *options)
 {
        if (!obj)
                return -1;
@@ -81,11 +367,11 @@ int fsck_walk(struct object *obj, fsck_walk_func walk, void *data)
        case OBJ_BLOB:
                return 0;
        case OBJ_TREE:
-               return fsck_walk_tree((struct tree *)obj, walk, data);
+               return fsck_walk_tree((struct tree *)obj, data, options);
        case OBJ_COMMIT:
-               return fsck_walk_commit((struct commit *)obj, walk, data);
+               return fsck_walk_commit((struct commit *)obj, data, options);
        case OBJ_TAG:
-               return fsck_walk_tag((struct tag *)obj, walk, data);
+               return fsck_walk_tag((struct tag *)obj, data, options);
        default:
                error("Unknown object type for %s", sha1_to_hex(obj->sha1));
                return -1;
@@ -138,7 +424,7 @@ static int verify_ordered(unsigned mode1, const char *name1, unsigned mode2, con
        return c1 < c2 ? 0 : TREE_UNORDERED;
 }
 
-static int fsck_tree(struct tree *item, int strict, fsck_error error_func)
+static int fsck_tree(struct tree *item, struct fsck_options *options)
 {
        int retval;
        int has_null_sha1 = 0;
@@ -194,7 +480,7 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func)
                 * bits..
                 */
                case S_IFREG | 0664:
-                       if (!strict)
+                       if (!options->strict)
                                break;
                default:
                        has_bad_modes = 1;
@@ -219,30 +505,30 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func)
 
        retval = 0;
        if (has_null_sha1)
-               retval += error_func(&item->object, FSCK_WARN, "contains entries pointing to null sha1");
+               retval += report(options, &item->object, FSCK_MSG_NULL_SHA1, "contains entries pointing to null sha1");
        if (has_full_path)
-               retval += error_func(&item->object, FSCK_WARN, "contains full pathnames");
+               retval += report(options, &item->object, FSCK_MSG_FULL_PATHNAME, "contains full pathnames");
        if (has_empty_name)
-               retval += error_func(&item->object, FSCK_WARN, "contains empty pathname");
+               retval += report(options, &item->object, FSCK_MSG_EMPTY_NAME, "contains empty pathname");
        if (has_dot)
-               retval += error_func(&item->object, FSCK_WARN, "contains '.'");
+               retval += report(options, &item->object, FSCK_MSG_HAS_DOT, "contains '.'");
        if (has_dotdot)
-               retval += error_func(&item->object, FSCK_WARN, "contains '..'");
+               retval += report(options, &item->object, FSCK_MSG_HAS_DOTDOT, "contains '..'");
        if (has_dotgit)
-               retval += error_func(&item->object, FSCK_WARN, "contains '.git'");
+               retval += report(options, &item->object, FSCK_MSG_HAS_DOTGIT, "contains '.git'");
        if (has_zero_pad)
-               retval += error_func(&item->object, FSCK_WARN, "contains zero-padded file modes");
+               retval += report(options, &item->object, FSCK_MSG_ZERO_PADDED_FILEMODE, "contains zero-padded file modes");
        if (has_bad_modes)
-               retval += error_func(&item->object, FSCK_WARN, "contains bad file modes");
+               retval += report(options, &item->object, FSCK_MSG_BAD_FILEMODE, "contains bad file modes");
        if (has_dup_entries)
-               retval += error_func(&item->object, FSCK_ERROR, "contains duplicate file entries");
+               retval += report(options, &item->object, FSCK_MSG_DUPLICATE_ENTRIES, "contains duplicate file entries");
        if (not_properly_sorted)
-               retval += error_func(&item->object, FSCK_ERROR, "not properly sorted");
+               retval += report(options, &item->object, FSCK_MSG_TREE_NOT_SORTED, "not properly sorted");
        return retval;
 }
 
 static int verify_headers(const void *data, unsigned long size,
-                         struct object *obj, fsck_error error_func)
+                         struct object *obj, struct fsck_options *options)
 {
        const char *buffer = (const char *)data;
        unsigned long i;
@@ -250,8 +536,9 @@ static int verify_headers(const void *data, unsigned long size,
        for (i = 0; i < size; i++) {
                switch (buffer[i]) {
                case '\0':
-                       return error_func(obj, FSCK_ERROR,
-                               "unterminated header: NUL at offset %d", i);
+                       return report(options, obj,
+                               FSCK_MSG_NUL_IN_HEADER,
+                               "unterminated header: NUL at offset %ld", i);
                case '\n':
                        if (i + 1 < size && buffer[i + 1] == '\n')
                                return 0;
@@ -267,67 +554,79 @@ static int verify_headers(const void *data, unsigned long size,
        if (size && buffer[size - 1] == '\n')
                return 0;
 
-       return error_func(obj, FSCK_ERROR, "unterminated header");
+       return report(options, obj,
+               FSCK_MSG_UNTERMINATED_HEADER, "unterminated header");
 }
 
-static int fsck_ident(const char **ident, struct object *obj, fsck_error error_func)
+static int fsck_ident(const char **ident, struct object *obj, struct fsck_options *options)
 {
+       const char *p = *ident;
        char *end;
 
-       if (**ident == '<')
-               return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before email");
-       *ident += strcspn(*ident, "<>\n");
-       if (**ident == '>')
-               return error_func(obj, FSCK_ERROR, "invalid author/committer line - bad name");
-       if (**ident != '<')
-               return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing email");
-       if ((*ident)[-1] != ' ')
-               return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before email");
-       (*ident)++;
-       *ident += strcspn(*ident, "<>\n");
-       if (**ident != '>')
-               return error_func(obj, FSCK_ERROR, "invalid author/committer line - bad email");
-       (*ident)++;
-       if (**ident != ' ')
-               return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before date");
-       (*ident)++;
-       if (**ident == '0' && (*ident)[1] != ' ')
-               return error_func(obj, FSCK_ERROR, "invalid author/committer line - zero-padded date");
-       if (date_overflows(strtoul(*ident, &end, 10)))
-               return error_func(obj, FSCK_ERROR, "invalid author/committer line - date causes integer overflow");
-       if (end == *ident || *end != ' ')
-               return error_func(obj, FSCK_ERROR, "invalid author/committer line - bad date");
-       *ident = end + 1;
-       if ((**ident != '+' && **ident != '-') ||
-           !isdigit((*ident)[1]) ||
-           !isdigit((*ident)[2]) ||
-           !isdigit((*ident)[3]) ||
-           !isdigit((*ident)[4]) ||
-           ((*ident)[5] != '\n'))
-               return error_func(obj, FSCK_ERROR, "invalid author/committer line - bad time zone");
-       (*ident) += 6;
+       *ident = strchrnul(*ident, '\n');
+       if (**ident == '\n')
+               (*ident)++;
+
+       if (*p == '<')
+               return report(options, obj, FSCK_MSG_MISSING_NAME_BEFORE_EMAIL, "invalid author/committer line - missing space before email");
+       p += strcspn(p, "<>\n");
+       if (*p == '>')
+               return report(options, obj, FSCK_MSG_BAD_NAME, "invalid author/committer line - bad name");
+       if (*p != '<')
+               return report(options, obj, FSCK_MSG_MISSING_EMAIL, "invalid author/committer line - missing email");
+       if (p[-1] != ' ')
+               return report(options, obj, FSCK_MSG_MISSING_SPACE_BEFORE_EMAIL, "invalid author/committer line - missing space before email");
+       p++;
+       p += strcspn(p, "<>\n");
+       if (*p != '>')
+               return report(options, obj, FSCK_MSG_BAD_EMAIL, "invalid author/committer line - bad email");
+       p++;
+       if (*p != ' ')
+               return report(options, obj, FSCK_MSG_MISSING_SPACE_BEFORE_DATE, "invalid author/committer line - missing space before date");
+       p++;
+       if (*p == '0' && p[1] != ' ')
+               return report(options, obj, FSCK_MSG_ZERO_PADDED_DATE, "invalid author/committer line - zero-padded date");
+       if (date_overflows(strtoul(p, &end, 10)))
+               return report(options, obj, FSCK_MSG_BAD_DATE_OVERFLOW, "invalid author/committer line - date causes integer overflow");
+       if ((end == p || *end != ' '))
+               return report(options, obj, FSCK_MSG_BAD_DATE, "invalid author/committer line - bad date");
+       p = end + 1;
+       if ((*p != '+' && *p != '-') ||
+           !isdigit(p[1]) ||
+           !isdigit(p[2]) ||
+           !isdigit(p[3]) ||
+           !isdigit(p[4]) ||
+           (p[5] != '\n'))
+               return report(options, obj, FSCK_MSG_BAD_TIMEZONE, "invalid author/committer line - bad time zone");
+       p += 6;
        return 0;
 }
 
 static int fsck_commit_buffer(struct commit *commit, const char *buffer,
-       unsigned long size, fsck_error error_func)
+       unsigned long size, struct fsck_options *options)
 {
        unsigned char tree_sha1[20], sha1[20];
        struct commit_graft *graft;
-       unsigned parent_count, parent_line_count = 0;
+       unsigned parent_count, parent_line_count = 0, author_count;
        int err;
 
-       if (verify_headers(buffer, size, &commit->object, error_func))
+       if (verify_headers(buffer, size, &commit->object, options))
                return -1;
 
        if (!skip_prefix(buffer, "tree ", &buffer))
-               return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'tree' line");
-       if (get_sha1_hex(buffer, tree_sha1) || buffer[40] != '\n')
-               return error_func(&commit->object, FSCK_ERROR, "invalid 'tree' line format - bad sha1");
+               return report(options, &commit->object, FSCK_MSG_MISSING_TREE, "invalid format - expected 'tree' line");
+       if (get_sha1_hex(buffer, tree_sha1) || buffer[40] != '\n') {
+               err = report(options, &commit->object, FSCK_MSG_BAD_TREE_SHA1, "invalid 'tree' line format - bad sha1");
+               if (err)
+                       return err;
+       }
        buffer += 41;
        while (skip_prefix(buffer, "parent ", &buffer)) {
-               if (get_sha1_hex(buffer, sha1) || buffer[40] != '\n')
-                       return error_func(&commit->object, FSCK_ERROR, "invalid 'parent' line format - bad sha1");
+               if (get_sha1_hex(buffer, sha1) || buffer[40] != '\n') {
+                       err = report(options, &commit->object, FSCK_MSG_BAD_PARENT_SHA1, "invalid 'parent' line format - bad sha1");
+                       if (err)
+                               return err;
+               }
                buffer += 41;
                parent_line_count++;
        }
@@ -336,40 +635,54 @@ static int fsck_commit_buffer(struct commit *commit, const char *buffer,
        if (graft) {
                if (graft->nr_parent == -1 && !parent_count)
                        ; /* shallow commit */
-               else if (graft->nr_parent != parent_count)
-                       return error_func(&commit->object, FSCK_ERROR, "graft objects missing");
+               else if (graft->nr_parent != parent_count) {
+                       err = report(options, &commit->object, FSCK_MSG_MISSING_GRAFT, "graft objects missing");
+                       if (err)
+                               return err;
+               }
        } else {
-               if (parent_count != parent_line_count)
-                       return error_func(&commit->object, FSCK_ERROR, "parent objects missing");
+               if (parent_count != parent_line_count) {
+                       err = report(options, &commit->object, FSCK_MSG_MISSING_PARENT, "parent objects missing");
+                       if (err)
+                               return err;
+               }
+       }
+       author_count = 0;
+       while (skip_prefix(buffer, "author ", &buffer)) {
+               author_count++;
+               err = fsck_ident(&buffer, &commit->object, options);
+               if (err)
+                       return err;
        }
-       if (!skip_prefix(buffer, "author ", &buffer))
-               return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'author' line");
-       err = fsck_ident(&buffer, &commit->object, error_func);
+       if (author_count < 1)
+               err = report(options, &commit->object, FSCK_MSG_MISSING_AUTHOR, "invalid format - expected 'author' line");
+       else if (author_count > 1)
+               err = report(options, &commit->object, FSCK_MSG_MULTIPLE_AUTHORS, "invalid format - multiple 'author' lines");
        if (err)
                return err;
        if (!skip_prefix(buffer, "committer ", &buffer))
-               return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'committer' line");
-       err = fsck_ident(&buffer, &commit->object, error_func);
+               return report(options, &commit->object, FSCK_MSG_MISSING_COMMITTER, "invalid format - expected 'committer' line");
+       err = fsck_ident(&buffer, &commit->object, options);
        if (err)
                return err;
        if (!commit->tree)
-               return error_func(&commit->object, FSCK_ERROR, "could not load commit's tree %s", sha1_to_hex(tree_sha1));
+               return report(options, &commit->object, FSCK_MSG_BAD_TREE, "could not load commit's tree %s", sha1_to_hex(tree_sha1));
 
        return 0;
 }
 
 static int fsck_commit(struct commit *commit, const char *data,
-       unsigned long size, fsck_error error_func)
+       unsigned long size, struct fsck_options *options)
 {
        const char *buffer = data ?  data : get_commit_buffer(commit, &size);
-       int ret = fsck_commit_buffer(commit, buffer, size, error_func);
+       int ret = fsck_commit_buffer(commit, buffer, size, options);
        if (!data)
                unuse_commit_buffer(commit, buffer);
        return ret;
 }
 
 static int fsck_tag_buffer(struct tag *tag, const char *data,
-       unsigned long size, fsck_error error_func)
+       unsigned long size, struct fsck_options *options)
 {
        unsigned char sha1[20];
        int ret = 0;
@@ -385,65 +698,75 @@ static int fsck_tag_buffer(struct tag *tag, const char *data,
                buffer = to_free =
                        read_sha1_file(tag->object.sha1, &type, &size);
                if (!buffer)
-                       return error_func(&tag->object, FSCK_ERROR,
+                       return report(options, &tag->object,
+                               FSCK_MSG_MISSING_TAG_OBJECT,
                                "cannot read tag object");
 
                if (type != OBJ_TAG) {
-                       ret = error_func(&tag->object, FSCK_ERROR,
+                       ret = report(options, &tag->object,
+                               FSCK_MSG_TAG_OBJECT_NOT_TAG,
                                "expected tag got %s",
                            typename(type));
                        goto done;
                }
        }
 
-       if (verify_headers(buffer, size, &tag->object, error_func))
+       if (verify_headers(buffer, size, &tag->object, options))
                goto done;
 
        if (!skip_prefix(buffer, "object ", &buffer)) {
-               ret = error_func(&tag->object, FSCK_ERROR, "invalid format - expected 'object' line");
+               ret = report(options, &tag->object, FSCK_MSG_MISSING_OBJECT, "invalid format - expected 'object' line");
                goto done;
        }
        if (get_sha1_hex(buffer, sha1) || buffer[40] != '\n') {
-               ret = error_func(&tag->object, FSCK_ERROR, "invalid 'object' line format - bad sha1");
-               goto done;
+               ret = report(options, &tag->object, FSCK_MSG_BAD_OBJECT_SHA1, "invalid 'object' line format - bad sha1");
+               if (ret)
+                       goto done;
        }
        buffer += 41;
 
        if (!skip_prefix(buffer, "type ", &buffer)) {
-               ret = error_func(&tag->object, FSCK_ERROR, "invalid format - expected 'type' line");
+               ret = report(options, &tag->object, FSCK_MSG_MISSING_TYPE_ENTRY, "invalid format - expected 'type' line");
                goto done;
        }
        eol = strchr(buffer, '\n');
        if (!eol) {
-               ret = error_func(&tag->object, FSCK_ERROR, "invalid format - unexpected end after 'type' line");
+               ret = report(options, &tag->object, FSCK_MSG_MISSING_TYPE, "invalid format - unexpected end after 'type' line");
                goto done;
        }
        if (type_from_string_gently(buffer, eol - buffer, 1) < 0)
-               ret = error_func(&tag->object, FSCK_ERROR, "invalid 'type' value");
+               ret = report(options, &tag->object, FSCK_MSG_BAD_TYPE, "invalid 'type' value");
        if (ret)
                goto done;
        buffer = eol + 1;
 
        if (!skip_prefix(buffer, "tag ", &buffer)) {
-               ret = error_func(&tag->object, FSCK_ERROR, "invalid format - expected 'tag' line");
+               ret = report(options, &tag->object, FSCK_MSG_MISSING_TAG_ENTRY, "invalid format - expected 'tag' line");
                goto done;
        }
        eol = strchr(buffer, '\n');
        if (!eol) {
-               ret = error_func(&tag->object, FSCK_ERROR, "invalid format - unexpected end after 'type' line");
+               ret = report(options, &tag->object, FSCK_MSG_MISSING_TAG, "invalid format - unexpected end after 'type' line");
                goto done;
        }
        strbuf_addf(&sb, "refs/tags/%.*s", (int)(eol - buffer), buffer);
-       if (check_refname_format(sb.buf, 0))
-               error_func(&tag->object, FSCK_WARN, "invalid 'tag' name: %.*s",
+       if (check_refname_format(sb.buf, 0)) {
+               ret = report(options, &tag->object, FSCK_MSG_BAD_TAG_NAME,
+                          "invalid 'tag' name: %.*s",
                           (int)(eol - buffer), buffer);
+               if (ret)
+                       goto done;
+       }
        buffer = eol + 1;
 
-       if (!skip_prefix(buffer, "tagger ", &buffer))
+       if (!skip_prefix(buffer, "tagger ", &buffer)) {
                /* early tags do not contain 'tagger' lines; warn only */
-               error_func(&tag->object, FSCK_WARN, "invalid format - expected 'tagger' line");
+               ret = report(options, &tag->object, FSCK_MSG_MISSING_TAGGER_ENTRY, "invalid format - expected 'tagger' line");
+               if (ret)
+                       goto done;
+       }
        else
-               ret = fsck_ident(&buffer, &tag->object, error_func);
+               ret = fsck_ident(&buffer, &tag->object, options);
 
 done:
        strbuf_release(&sb);
@@ -452,49 +775,43 @@ static int fsck_tag_buffer(struct tag *tag, const char *data,
 }
 
 static int fsck_tag(struct tag *tag, const char *data,
-       unsigned long size, fsck_error error_func)
+       unsigned long size, struct fsck_options *options)
 {
        struct object *tagged = tag->tagged;
 
        if (!tagged)
-               return error_func(&tag->object, FSCK_ERROR, "could not load tagged object");
+               return report(options, &tag->object, FSCK_MSG_BAD_TAG_OBJECT, "could not load tagged object");
 
-       return fsck_tag_buffer(tag, data, size, error_func);
+       return fsck_tag_buffer(tag, data, size, options);
 }
 
 int fsck_object(struct object *obj, void *data, unsigned long size,
-       int strict, fsck_error error_func)
+       struct fsck_options *options)
 {
        if (!obj)
-               return error_func(obj, FSCK_ERROR, "no valid object to fsck");
+               return report(options, obj, FSCK_MSG_BAD_OBJECT_SHA1, "no valid object to fsck");
 
        if (obj->type == OBJ_BLOB)
                return 0;
        if (obj->type == OBJ_TREE)
-               return fsck_tree((struct tree *) obj, strict, error_func);
+               return fsck_tree((struct tree *) obj, options);
        if (obj->type == OBJ_COMMIT)
                return fsck_commit((struct commit *) obj, (const char *) data,
-                       size, error_func);
+                       size, options);
        if (obj->type == OBJ_TAG)
                return fsck_tag((struct tag *) obj, (const char *) data,
-                       size, error_func);
+                       size, options);
 
-       return error_func(obj, FSCK_ERROR, "unknown type '%d' (internal fsck error)",
+       return report(options, obj, FSCK_MSG_UNKNOWN_TYPE, "unknown type '%d' (internal fsck error)",
                          obj->type);
 }
 
-int fsck_error_function(struct object *obj, int type, const char *fmt, ...)
+int fsck_error_function(struct object *obj, int msg_type, const char *message)
 {
-       va_list ap;
-       struct strbuf sb = STRBUF_INIT;
-
-       strbuf_addf(&sb, "object %s:", sha1_to_hex(obj->sha1));
-
-       va_start(ap, fmt);
-       strbuf_vaddf(&sb, fmt, ap);
-       va_end(ap);
-
-       error("%s", sb.buf);
-       strbuf_release(&sb);
+       if (msg_type == FSCK_WARN) {
+               warning("object %s: %s", sha1_to_hex(obj->sha1), message);
+               return 0;
+       }
+       error("object %s: %s", sha1_to_hex(obj->sha1), message);
        return 1;
 }
diff --git a/fsck.h b/fsck.h
index d1e6387a44e223b6d9438c7c35dc9efaa52f7d48..dded84b5f9fceda7b2e9b3f13f4ccf464f6c7116 100644 (file)
--- a/fsck.h
+++ b/fsck.h
@@ -3,6 +3,14 @@
 
 #define FSCK_ERROR 1
 #define FSCK_WARN 2
+#define FSCK_IGNORE 3
+
+struct fsck_options;
+
+void fsck_set_msg_type(struct fsck_options *options,
+               const char *msg_id, const char *msg_type);
+void fsck_set_msg_types(struct fsck_options *options, const char *values);
+int is_valid_msg_type(const char *msg_id, const char *msg_type);
 
 /*
  * callback function for fsck_walk
  *     <0      error signaled and abort
  *     >0      error signaled and do not abort
  */
-typedef int (*fsck_walk_func)(struct object *obj, int type, void *data);
+typedef int (*fsck_walk_func)(struct object *obj, int type, void *data, struct fsck_options *options);
 
 /* callback for fsck_object, type is FSCK_ERROR or FSCK_WARN */
-typedef int (*fsck_error)(struct object *obj, int type, const char *err, ...);
+typedef int (*fsck_error)(struct object *obj, int type, const char *message);
+
+int fsck_error_function(struct object *obj, int type, const char *message);
+
+struct fsck_options {
+       fsck_walk_func walk;
+       fsck_error error_func;
+       unsigned strict:1;
+       int *msg_type;
+       struct sha1_array *skiplist;
+};
 
-__attribute__((format (printf, 3, 4)))
-int fsck_error_function(struct object *obj, int type, const char *fmt, ...);
+#define FSCK_OPTIONS_DEFAULT { NULL, fsck_error_function, 0, NULL }
+#define FSCK_OPTIONS_STRICT { NULL, fsck_error_function, 1, NULL }
 
 /* descend in all linked child objects
  * the return value is:
@@ -27,9 +45,9 @@ int fsck_error_function(struct object *obj, int type, const char *fmt, ...);
  *    >0       return value of the first signaled error >0 (in the case of no other errors)
  *    0                everything OK
  */
-int fsck_walk(struct object *obj, fsck_walk_func walk, void *data);
+int fsck_walk(struct object *obj, void *data, struct fsck_options *options);
 /* If NULL is passed for data, we assume the object is local and read it. */
 int fsck_object(struct object *obj, void *data, unsigned long size,
-       int strict, fsck_error error_func);
+       struct fsck_options *options);
 
 #endif
index 7378ba287f0c449810250fb7b019f69e4f6a3497..a268a2c52c0ed444b073d0411ca0dcc3f210ef51 100644 (file)
--- a/gettext.c
+++ b/gettext.c
@@ -162,6 +162,7 @@ void git_setup_gettext(void)
                podir = GIT_LOCALE_PATH;
        bindtextdomain("git", podir);
        setlocale(LC_MESSAGES, "");
+       setlocale(LC_TIME, "");
        init_gettext_charset("git");
        textdomain("git");
 }
diff --git a/git-am.sh b/git-am.sh
deleted file mode 100755 (executable)
index 3af351f..0000000
--- a/git-am.sh
+++ /dev/null
@@ -1,973 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005, 2006 Junio C Hamano
-
-SUBDIRECTORY_OK=Yes
-OPTIONS_KEEPDASHDASH=
-OPTIONS_STUCKLONG=t
-OPTIONS_SPEC="\
-git am [options] [(<mbox>|<Maildir>)...]
-git am [options] (--continue | --skip | --abort)
---
-i,interactive   run interactively
-b,binary*       (historical option -- no-op)
-3,3way          allow fall back on 3way merging if needed
-q,quiet         be quiet
-s,signoff       add a Signed-off-by line to the commit message
-u,utf8          recode into utf8 (default)
-k,keep          pass -k flag to git-mailinfo
-keep-non-patch  pass -b flag to git-mailinfo
-m,message-id    pass -m flag to git-mailinfo
-keep-cr         pass --keep-cr flag to git-mailsplit for mbox format
-no-keep-cr      do not pass --keep-cr flag to git-mailsplit independent of am.keepcr
-c,scissors      strip everything before a scissors line
-whitespace=     pass it through git-apply
-ignore-space-change pass it through git-apply
-ignore-whitespace pass it through git-apply
-directory=      pass it through git-apply
-exclude=        pass it through git-apply
-include=        pass it through git-apply
-C=              pass it through git-apply
-p=              pass it through git-apply
-patch-format=   format the patch(es) are in
-reject          pass it through git-apply
-resolvemsg=     override error message when patch failure occurs
-continue        continue applying patches after resolving a conflict
-r,resolved      synonyms for --continue
-skip            skip the current patch
-abort           restore the original branch and abort the patching operation.
-committer-date-is-author-date    lie about committer date
-ignore-date     use current timestamp for author date
-rerere-autoupdate update the index with reused conflict resolution if possible
-S,gpg-sign?     GPG-sign commits
-rebasing*       (internal use for git-rebase)"
-
-. git-sh-setup
-. git-sh-i18n
-prefix=$(git rev-parse --show-prefix)
-set_reflog_action am
-require_work_tree
-cd_to_toplevel
-
-git var GIT_COMMITTER_IDENT >/dev/null ||
-       die "$(gettext "You need to set your committer info first")"
-
-if git rev-parse --verify -q HEAD >/dev/null
-then
-       HAS_HEAD=yes
-else
-       HAS_HEAD=
-fi
-
-cmdline="git am"
-if test '' != "$interactive"
-then
-       cmdline="$cmdline -i"
-fi
-if test '' != "$threeway"
-then
-       cmdline="$cmdline -3"
-fi
-
-empty_tree=4b825dc642cb6eb9a060e54bf8d69288fbee4904
-
-sq () {
-       git rev-parse --sq-quote "$@"
-}
-
-stop_here () {
-    echo "$1" >"$dotest/next"
-    git rev-parse --verify -q HEAD >"$dotest/abort-safety"
-    exit 1
-}
-
-safe_to_abort () {
-       if test -f "$dotest/dirtyindex"
-       then
-               return 1
-       fi
-
-       if ! test -f "$dotest/abort-safety"
-       then
-               return 0
-       fi
-
-       abort_safety=$(cat "$dotest/abort-safety")
-       if test "z$(git rev-parse --verify -q HEAD)" = "z$abort_safety"
-       then
-               return 0
-       fi
-       gettextln "You seem to have moved HEAD since the last 'am' failure.
-Not rewinding to ORIG_HEAD" >&2
-       return 1
-}
-
-stop_here_user_resolve () {
-    if [ -n "$resolvemsg" ]; then
-           printf '%s\n' "$resolvemsg"
-           stop_here $1
-    fi
-    eval_gettextln "When you have resolved this problem, run \"\$cmdline --continue\".
-If you prefer to skip this patch, run \"\$cmdline --skip\" instead.
-To restore the original branch and stop patching, run \"\$cmdline --abort\"."
-
-    stop_here $1
-}
-
-go_next () {
-       rm -f "$dotest/$msgnum" "$dotest/msg" "$dotest/msg-clean" \
-               "$dotest/patch" "$dotest/info"
-       echo "$next" >"$dotest/next"
-       this=$next
-}
-
-cannot_fallback () {
-       echo "$1"
-       gettextln "Cannot fall back to three-way merge."
-       exit 1
-}
-
-fall_back_3way () {
-    O_OBJECT=$(cd "$GIT_OBJECT_DIRECTORY" && pwd)
-
-    rm -fr "$dotest"/patch-merge-*
-    mkdir "$dotest/patch-merge-tmp-dir"
-
-    # First see if the patch records the index info that we can use.
-    cmd="git apply $git_apply_opt --build-fake-ancestor" &&
-    cmd="$cmd "'"$dotest/patch-merge-tmp-index" "$dotest/patch"' &&
-    eval "$cmd" &&
-    GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
-    git write-tree >"$dotest/patch-merge-base+" ||
-    cannot_fallback "$(gettext "Repository lacks necessary blobs to fall back on 3-way merge.")"
-
-    say "$(gettext "Using index info to reconstruct a base tree...")"
-
-    cmd='GIT_INDEX_FILE="$dotest/patch-merge-tmp-index"'
-
-    if test -z "$GIT_QUIET"
-    then
-       eval "$cmd git diff-index --cached --diff-filter=AM --name-status HEAD"
-    fi
-
-    cmd="$cmd git apply --cached $git_apply_opt"' <"$dotest/patch"'
-    if eval "$cmd"
-    then
-       mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base"
-       mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index"
-    else
-       cannot_fallback "$(gettext "Did you hand edit your patch?
-It does not apply to blobs recorded in its index.")"
-    fi
-
-    test -f "$dotest/patch-merge-index" &&
-    his_tree=$(GIT_INDEX_FILE="$dotest/patch-merge-index" git write-tree) &&
-    orig_tree=$(cat "$dotest/patch-merge-base") &&
-    rm -fr "$dotest"/patch-merge-* || exit 1
-
-    say "$(gettext "Falling back to patching base and 3-way merge...")"
-
-    # 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.
-
-    eval GITHEAD_$his_tree='"$FIRSTLINE"'
-    export GITHEAD_$his_tree
-    if test -n "$GIT_QUIET"
-    then
-           GIT_MERGE_VERBOSITY=0 && export GIT_MERGE_VERBOSITY
-    fi
-    our_tree=$(git rev-parse --verify -q HEAD || echo $empty_tree)
-    git-merge-recursive $orig_tree -- $our_tree $his_tree || {
-           git rerere $allow_rerere_autoupdate
-           die "$(gettext "Failed to merge in the changes.")"
-    }
-    unset GITHEAD_$his_tree
-}
-
-clean_abort () {
-       test $# = 0 || echo >&2 "$@"
-       rm -fr "$dotest"
-       exit 1
-}
-
-patch_format=
-
-check_patch_format () {
-       # early return if patch_format was set from the command line
-       if test -n "$patch_format"
-       then
-               return 0
-       fi
-
-       # we default to mbox format if input is from stdin and for
-       # directories
-       if test $# = 0 || test "x$1" = "x-" || test -d "$1"
-       then
-               patch_format=mbox
-               return 0
-       fi
-
-       # otherwise, check the first few non-blank lines of the first
-       # patch to try to detect its format
-       {
-               # Start from first line containing non-whitespace
-               l1=
-               while test -z "$l1"
-               do
-                       read l1 || break
-               done
-               read l2
-               read l3
-               case "$l1" in
-               "From "* | "From: "*)
-                       patch_format=mbox
-                       ;;
-               '# This series applies on GIT commit'*)
-                       patch_format=stgit-series
-                       ;;
-               "# HG changeset patch")
-                       patch_format=hg
-                       ;;
-               *)
-                       # if the second line is empty and the third is
-                       # a From, Author or Date entry, this is very
-                       # likely an StGIT patch
-                       case "$l2,$l3" in
-                       ,"From: "* | ,"Author: "* | ,"Date: "*)
-                               patch_format=stgit
-                               ;;
-                       *)
-                               ;;
-                       esac
-                       ;;
-               esac
-               if test -z "$patch_format" &&
-                       test -n "$l1" &&
-                       test -n "$l2" &&
-                       test -n "$l3"
-               then
-                       # This begins with three non-empty lines.  Is this a
-                       # piece of e-mail a-la RFC2822?  Grab all the headers,
-                       # discarding the indented remainder of folded lines,
-                       # and see if it looks like that they all begin with the
-                       # header field names...
-                       tr -d '\015' <"$1" |
-                       sed -n -e '/^$/q' -e '/^[       ]/d' -e p |
-                       sane_egrep -v '^[!-9;-~]+:' >/dev/null ||
-                       patch_format=mbox
-               fi
-       } < "$1" || clean_abort
-}
-
-split_patches () {
-       case "$patch_format" in
-       mbox)
-               if test t = "$keepcr"
-               then
-                   keep_cr=--keep-cr
-               else
-                   keep_cr=
-               fi
-               git mailsplit -d"$prec" -o"$dotest" -b $keep_cr -- "$@" > "$dotest/last" ||
-               clean_abort
-               ;;
-       stgit-series)
-               if test $# -ne 1
-               then
-                       clean_abort "$(gettext "Only one StGIT patch series can be applied at once")"
-               fi
-               series_dir=$(dirname "$1")
-               series_file="$1"
-               shift
-               {
-                       set x
-                       while read filename
-                       do
-                               set "$@" "$series_dir/$filename"
-                       done
-                       # remove the safety x
-                       shift
-                       # remove the arg coming from the first-line comment
-                       shift
-               } < "$series_file" || clean_abort
-               # set the patch format appropriately
-               patch_format=stgit
-               # now handle the actual StGIT patches
-               split_patches "$@"
-               ;;
-       stgit)
-               this=0
-               for stgit in "$@"
-               do
-                       this=$(expr "$this" + 1)
-                       msgnum=$(printf "%0${prec}d" $this)
-                       # Perl version of StGIT parse_patch. The first nonemptyline
-                       # not starting with Author, From or Date is the
-                       # subject, and the body starts with the next nonempty
-                       # line not starting with Author, From or Date
-                       @@PERL@@ -ne 'BEGIN { $subject = 0 }
-                               if ($subject > 1) { print ; }
-                               elsif (/^\s+$/) { next ; }
-                               elsif (/^Author:/) { s/Author/From/ ; print ;}
-                               elsif (/^(From|Date)/) { print ; }
-                               elsif ($subject) {
-                                       $subject = 2 ;
-                                       print "\n" ;
-                                       print ;
-                               } else {
-                                       print "Subject: ", $_ ;
-                                       $subject = 1;
-                               }
-                       ' < "$stgit" > "$dotest/$msgnum" || clean_abort
-               done
-               echo "$this" > "$dotest/last"
-               this=
-               msgnum=
-               ;;
-       hg)
-               this=0
-               for hg in "$@"
-               do
-                       this=$(( $this + 1 ))
-                       msgnum=$(printf "%0${prec}d" $this)
-                       # hg stores changeset metadata in #-commented lines preceding
-                       # the commit message and diff(s). The only metadata we care about
-                       # are the User and Date (Node ID and Parent are hashes which are
-                       # only relevant to the hg repository and thus not useful to us)
-                       # Since we cannot guarantee that the commit message is in
-                       # git-friendly format, we put no Subject: line and just consume
-                       # all of the message as the body
-                       LANG=C LC_ALL=C @@PERL@@ -M'POSIX qw(strftime)' -ne 'BEGIN { $subject = 0 }
-                               if ($subject) { print ; }
-                               elsif (/^\# User /) { s/\# User/From:/ ; print ; }
-                               elsif (/^\# Date /) {
-                                       my ($hashsign, $str, $time, $tz) = split ;
-                                       $tz = sprintf "%+05d", (0-$tz)/36;
-                                       print "Date: " .
-                                             strftime("%a, %d %b %Y %H:%M:%S ",
-                                                      localtime($time))
-                                             . "$tz\n";
-                               } elsif (/^\# /) { next ; }
-                               else {
-                                       print "\n", $_ ;
-                                       $subject = 1;
-                               }
-                       ' <"$hg" >"$dotest/$msgnum" || clean_abort
-               done
-               echo "$this" >"$dotest/last"
-               this=
-               msgnum=
-               ;;
-       *)
-               if test -n "$patch_format"
-               then
-                       clean_abort "$(eval_gettext "Patch format \$patch_format is not supported.")"
-               else
-                       clean_abort "$(gettext "Patch format detection failed.")"
-               fi
-               ;;
-       esac
-}
-
-prec=4
-dotest="$GIT_DIR/rebase-apply"
-sign= utf8=t keep= keepcr= skip= interactive= resolved= rebasing= abort=
-messageid= resolvemsg= resume= scissors= no_inbody_headers=
-git_apply_opt=
-committer_date_is_author_date=
-ignore_date=
-allow_rerere_autoupdate=
-gpg_sign_opt=
-threeway=
-
-if test "$(git config --bool --get am.messageid)" = true
-then
-    messageid=t
-fi
-
-if test "$(git config --bool --get am.keepcr)" = true
-then
-    keepcr=t
-fi
-
-while test $# != 0
-do
-       case "$1" in
-       -i|--interactive)
-               interactive=t ;;
-       -b|--binary)
-               gettextln >&2 "The -b/--binary option has been a no-op for long time, and
-it will be removed. Please do not use it anymore."
-               ;;
-       -3|--3way)
-               threeway=t ;;
-       -s|--signoff)
-               sign=t ;;
-       -u|--utf8)
-               utf8=t ;; # this is now default
-       --no-utf8)
-               utf8= ;;
-       -m|--message-id)
-               messageid=t ;;
-       --no-message-id)
-               messageid=f ;;
-       -k|--keep)
-               keep=t ;;
-       --keep-non-patch)
-               keep=b ;;
-       -c|--scissors)
-               scissors=t ;;
-       --no-scissors)
-               scissors=f ;;
-       -r|--resolved|--continue)
-               resolved=t ;;
-       --skip)
-               skip=t ;;
-       --abort)
-               abort=t ;;
-       --rebasing)
-               rebasing=t threeway=t ;;
-       --resolvemsg=*)
-               resolvemsg="${1#--resolvemsg=}" ;;
-       --whitespace=*|--directory=*|--exclude=*|--include=*)
-               git_apply_opt="$git_apply_opt $(sq "$1")" ;;
-       -C*|-p*)
-               git_apply_opt="$git_apply_opt $(sq "$1")" ;;
-       --patch-format=*)
-               patch_format="${1#--patch-format=}" ;;
-       --reject|--ignore-whitespace|--ignore-space-change)
-               git_apply_opt="$git_apply_opt $1" ;;
-       --committer-date-is-author-date)
-               committer_date_is_author_date=t ;;
-       --ignore-date)
-               ignore_date=t ;;
-       --rerere-autoupdate|--no-rerere-autoupdate)
-               allow_rerere_autoupdate="$1" ;;
-       -q|--quiet)
-               GIT_QUIET=t ;;
-       --keep-cr)
-               keepcr=t ;;
-       --no-keep-cr)
-               keepcr=f ;;
-       --gpg-sign)
-               gpg_sign_opt=-S ;;
-       --gpg-sign=*)
-               gpg_sign_opt="-S${1#--gpg-sign=}" ;;
-       --)
-               shift; break ;;
-       *)
-               usage ;;
-       esac
-       shift
-done
-
-# If the dotest directory exists, but we have finished applying all the
-# patches in them, clear it out.
-if test -d "$dotest" &&
-   test -f "$dotest/last" &&
-   test -f "$dotest/next" &&
-   last=$(cat "$dotest/last") &&
-   next=$(cat "$dotest/next") &&
-   test $# != 0 &&
-   test "$next" -gt "$last"
-then
-   rm -fr "$dotest"
-fi
-
-if test -d "$dotest" && test -f "$dotest/last" && test -f "$dotest/next"
-then
-       case "$#,$skip$resolved$abort" in
-       0,*t*)
-               # Explicit resume command and we do not have file, so
-               # we are happy.
-               : ;;
-       0,)
-               # No file input but without resume parameters; catch
-               # user error to feed us a patch from standard input
-               # when there is already $dotest.  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.
-               test -t 0
-               ;;
-       *)
-               false
-               ;;
-       esac ||
-       die "$(eval_gettext "previous rebase directory \$dotest still exists but mbox given.")"
-       resume=yes
-
-       case "$skip,$abort" in
-       t,t)
-               die "$(gettext "Please make up your mind. --skip or --abort?")"
-               ;;
-       t,)
-               git rerere clear
-               head_tree=$(git rev-parse --verify -q HEAD || echo $empty_tree) &&
-               git read-tree --reset -u $head_tree $head_tree &&
-               index_tree=$(git write-tree) &&
-               git read-tree -m -u $index_tree $head_tree
-               git read-tree $head_tree
-               ;;
-       ,t)
-               if test -f "$dotest/rebasing"
-               then
-                       exec git rebase --abort
-               fi
-               git rerere clear
-               if safe_to_abort
-               then
-                       head_tree=$(git rev-parse --verify -q HEAD || echo $empty_tree) &&
-                       git read-tree --reset -u $head_tree $head_tree &&
-                       index_tree=$(git write-tree) &&
-                       orig_head=$(git rev-parse --verify -q ORIG_HEAD || echo $empty_tree) &&
-                       git read-tree -m -u $index_tree $orig_head
-                       if git rev-parse --verify -q ORIG_HEAD >/dev/null 2>&1
-                       then
-                               git reset ORIG_HEAD
-                       else
-                               git read-tree $empty_tree
-                               curr_branch=$(git symbolic-ref HEAD 2>/dev/null) &&
-                               git update-ref -d $curr_branch
-                       fi
-               fi
-               rm -fr "$dotest"
-               exit ;;
-       esac
-       rm -f "$dotest/dirtyindex"
-else
-       # Possible stray $dotest directory in the independent-run
-       # case; in the --rebasing case, it is upto the caller
-       # (git-rebase--am) to take care of stray directories.
-       if test -d "$dotest" && test -z "$rebasing"
-       then
-               case "$skip,$resolved,$abort" in
-               ,,t)
-                       rm -fr "$dotest"
-                       exit 0
-                       ;;
-               *)
-                       die "$(eval_gettext "Stray \$dotest directory found.
-Use \"git am --abort\" to remove it.")"
-                       ;;
-               esac
-       fi
-
-       # Make sure we are not given --skip, --continue, or --abort
-       test "$skip$resolved$abort" = "" ||
-               die "$(gettext "Resolve operation not in progress, we are not resuming.")"
-
-       # Start afresh.
-       mkdir -p "$dotest" || exit
-
-       if test -n "$prefix" && test $# != 0
-       then
-               first=t
-               for arg
-               do
-                       test -n "$first" && {
-                               set x
-                               first=
-                       }
-                       if is_absolute_path "$arg"
-                       then
-                               set "$@" "$arg"
-                       else
-                               set "$@" "$prefix$arg"
-                       fi
-               done
-               shift
-       fi
-
-       check_patch_format "$@"
-
-       split_patches "$@"
-
-       # -i can and must be given when resuming; everything
-       # else is kept
-       echo " $git_apply_opt" >"$dotest/apply-opt"
-       echo "$threeway" >"$dotest/threeway"
-       echo "$sign" >"$dotest/sign"
-       echo "$utf8" >"$dotest/utf8"
-       echo "$keep" >"$dotest/keep"
-       echo "$messageid" >"$dotest/messageid"
-       echo "$scissors" >"$dotest/scissors"
-       echo "$no_inbody_headers" >"$dotest/no_inbody_headers"
-       echo "$GIT_QUIET" >"$dotest/quiet"
-       echo 1 >"$dotest/next"
-       if test -n "$rebasing"
-       then
-               : >"$dotest/rebasing"
-       else
-               : >"$dotest/applying"
-               if test -n "$HAS_HEAD"
-               then
-                       git update-ref ORIG_HEAD HEAD
-               else
-                       git update-ref -d ORIG_HEAD >/dev/null 2>&1
-               fi
-       fi
-fi
-
-git update-index -q --refresh
-
-case "$resolved" in
-'')
-       case "$HAS_HEAD" in
-       '')
-               files=$(git ls-files) ;;
-       ?*)
-               files=$(git diff-index --cached --name-only HEAD --) ;;
-       esac || exit
-       if test "$files"
-       then
-               test -n "$HAS_HEAD" && : >"$dotest/dirtyindex"
-               die "$(eval_gettext "Dirty index: cannot apply patches (dirty: \$files)")"
-       fi
-esac
-
-# Now, decide what command line options we will give to the git
-# commands we invoke, based on the result of parsing command line
-# options and previous invocation state stored in $dotest/ files.
-
-if test "$(cat "$dotest/utf8")" = t
-then
-       utf8=-u
-else
-       utf8=-n
-fi
-keep=$(cat "$dotest/keep")
-case "$keep" in
-t)
-       keep=-k ;;
-b)
-       keep=-b ;;
-*)
-       keep= ;;
-esac
-case "$(cat "$dotest/messageid")" in
-t)
-       messageid=-m ;;
-f)
-       messageid= ;;
-esac
-case "$(cat "$dotest/scissors")" in
-t)
-       scissors=--scissors ;;
-f)
-       scissors=--no-scissors ;;
-esac
-if test "$(cat "$dotest/no_inbody_headers")" = t
-then
-       no_inbody_headers=--no-inbody-headers
-else
-       no_inbody_headers=
-fi
-if test "$(cat "$dotest/quiet")" = t
-then
-       GIT_QUIET=t
-fi
-if test "$(cat "$dotest/threeway")" = t
-then
-       threeway=t
-fi
-git_apply_opt=$(cat "$dotest/apply-opt")
-if test "$(cat "$dotest/sign")" = t
-then
-       SIGNOFF=$(git var GIT_COMMITTER_IDENT | sed -e '
-                       s/>.*/>/
-                       s/^/Signed-off-by: /'
-               )
-else
-       SIGNOFF=
-fi
-
-last=$(cat "$dotest/last")
-this=$(cat "$dotest/next")
-if test "$skip" = t
-then
-       this=$(expr "$this" + 1)
-       resume=
-fi
-
-while test "$this" -le "$last"
-do
-       msgnum=$(printf "%0${prec}d" $this)
-       next=$(expr "$this" + 1)
-       test -f "$dotest/$msgnum" || {
-               resume=
-               go_next
-               continue
-       }
-
-       # If we are not resuming, parse and extract the patch information
-       # into separate files:
-       #  - info records the authorship and title
-       #  - msg is the rest of commit log message
-       #  - patch is the patch body.
-       #
-       # When we are resuming, these files are either already prepared
-       # by the user, or the user can tell us to do so by --continue flag.
-       case "$resume" in
-       '')
-               if test -f "$dotest/rebasing"
-               then
-                       commit=$(sed -e 's/^From \([0-9a-f]*\) .*/\1/' \
-                               -e q "$dotest/$msgnum") &&
-                       test "$(git cat-file -t "$commit")" = commit ||
-                               stop_here $this
-                       git cat-file commit "$commit" |
-                       sed -e '1,/^$/d' >"$dotest/msg-clean"
-                       echo "$commit" >"$dotest/original-commit"
-                       get_author_ident_from_commit "$commit" >"$dotest/author-script"
-                       git diff-tree --root --binary --full-index "$commit" >"$dotest/patch"
-               else
-                       git mailinfo $keep $no_inbody_headers $messageid $scissors $utf8 "$dotest/msg" "$dotest/patch" \
-                               <"$dotest/$msgnum" >"$dotest/info" ||
-                               stop_here $this
-
-                       # skip pine's internal folder data
-                       sane_grep '^Author: Mail System Internal Data$' \
-                               <"$dotest"/info >/dev/null &&
-                               go_next && continue
-
-                       test -s "$dotest/patch" || {
-                               eval_gettextln "Patch is empty.  Was it split wrong?
-If you would prefer to skip this patch, instead run \"\$cmdline --skip\".
-To restore the original branch and stop patching run \"\$cmdline --abort\"."
-                               stop_here $this
-                       }
-                       rm -f "$dotest/original-commit" "$dotest/author-script"
-                       {
-                               sed -n '/^Subject/ s/Subject: //p' "$dotest/info"
-                               echo
-                               cat "$dotest/msg"
-                       } |
-                       git stripspace > "$dotest/msg-clean"
-               fi
-               ;;
-       esac
-
-       if test -f "$dotest/author-script"
-       then
-               eval $(cat "$dotest/author-script")
-       else
-               GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$dotest/info")"
-               GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$dotest/info")"
-               GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$dotest/info")"
-       fi
-
-       if test -z "$GIT_AUTHOR_EMAIL"
-       then
-               gettextln "Patch does not have a valid e-mail address."
-               stop_here $this
-       fi
-
-       export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
-
-       case "$resume" in
-       '')
-           if test '' != "$SIGNOFF"
-           then
-               LAST_SIGNED_OFF_BY=$(
-                   sed -ne '/^Signed-off-by: /p' \
-                   "$dotest/msg-clean" |
-                   sed -ne '$p'
-               )
-               ADD_SIGNOFF=$(
-                   test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || {
-                   test '' = "$LAST_SIGNED_OFF_BY" && echo
-                   echo "$SIGNOFF"
-               })
-           else
-               ADD_SIGNOFF=
-           fi
-           {
-               if test -s "$dotest/msg-clean"
-               then
-                       cat "$dotest/msg-clean"
-               fi
-               if test '' != "$ADD_SIGNOFF"
-               then
-                       echo "$ADD_SIGNOFF"
-               fi
-           } >"$dotest/final-commit"
-           ;;
-       *)
-               case "$resolved$interactive" in
-               tt)
-                       # This is used only for interactive view option.
-                       git diff-index -p --cached HEAD -- >"$dotest/patch"
-                       ;;
-               esac
-       esac
-
-       resume=
-       if test "$interactive" = t
-       then
-           test -t 0 ||
-           die "$(gettext "cannot be interactive without stdin connected to a terminal.")"
-           action=again
-           while test "$action" = again
-           do
-               gettextln "Commit Body is:"
-               echo "--------------------------"
-               cat "$dotest/final-commit"
-               echo "--------------------------"
-               # TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
-               # in your translation. The program will only accept English
-               # input at this point.
-               gettext "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
-               read reply
-               case "$reply" in
-               [yY]*) action=yes ;;
-               [aA]*) action=yes interactive= ;;
-               [nN]*) action=skip ;;
-               [eE]*) git_editor "$dotest/final-commit"
-                      action=again ;;
-               [vV]*) action=again
-                      git_pager "$dotest/patch" ;;
-               *)     action=again ;;
-               esac
-           done
-       else
-           action=yes
-       fi
-
-       if test $action = skip
-       then
-               go_next
-               continue
-       fi
-
-       hook="$(git rev-parse --git-path hooks/applypatch-msg)"
-       if test -x "$hook"
-       then
-               "$hook" "$dotest/final-commit" || stop_here $this
-       fi
-
-       if test -f "$dotest/final-commit"
-       then
-               FIRSTLINE=$(sed 1q "$dotest/final-commit")
-       else
-               FIRSTLINE=""
-       fi
-
-       say "$(eval_gettext "Applying: \$FIRSTLINE")"
-
-       case "$resolved" in
-       '')
-               # When we are allowed to fall back to 3-way later, don't give
-               # false errors during the initial attempt.
-               squelch=
-               if test "$threeway" = t
-               then
-                       squelch='>/dev/null 2>&1 '
-               fi
-               eval "git apply $squelch$git_apply_opt"' --index "$dotest/patch"'
-               apply_status=$?
-               ;;
-       t)
-               # Resolved means the user did all the hard work, and
-               # we do not have to do any patch application.  Just
-               # trust what the user has in the index file and the
-               # working tree.
-               resolved=
-               git diff-index --quiet --cached HEAD -- && {
-                       gettextln "No changes - did you forget to use 'git add'?
-If there is nothing left to stage, chances are that something else
-already introduced the same changes; you might want to skip this patch."
-                       stop_here_user_resolve $this
-               }
-               unmerged=$(git ls-files -u)
-               if test -n "$unmerged"
-               then
-                       gettextln "You still have unmerged paths in your index
-did you forget to use 'git add'?"
-                       stop_here_user_resolve $this
-               fi
-               apply_status=0
-               git rerere
-               ;;
-       esac
-
-       if test $apply_status != 0 && test "$threeway" = t
-       then
-               if (fall_back_3way)
-               then
-                   # Applying the patch to an earlier tree and merging the
-                   # result may have produced the same tree as ours.
-                   git diff-index --quiet --cached HEAD -- && {
-                       say "$(gettext "No changes -- Patch already applied.")"
-                       go_next
-                       continue
-                   }
-                   # clear apply_status -- we have successfully merged.
-                   apply_status=0
-               fi
-       fi
-       if test $apply_status != 0
-       then
-               eval_gettextln 'Patch failed at $msgnum $FIRSTLINE'
-               if test "$(git config --bool advice.amworkdir)" != false
-               then
-                       eval_gettextln 'The copy of the patch that failed is found in:
-   $dotest/patch'
-               fi
-               stop_here_user_resolve $this
-       fi
-
-       hook="$(git rev-parse --git-path hooks/pre-applypatch)"
-       if test -x "$hook"
-       then
-               "$hook" || stop_here $this
-       fi
-
-       tree=$(git write-tree) &&
-       commit=$(
-               if test -n "$ignore_date"
-               then
-                       GIT_AUTHOR_DATE=
-               fi
-               parent=$(git rev-parse --verify -q HEAD) ||
-               say >&2 "$(gettext "applying to an empty history")"
-
-               if test -n "$committer_date_is_author_date"
-               then
-                       GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
-                       export GIT_COMMITTER_DATE
-               fi &&
-               git commit-tree ${parent:+-p} $parent ${gpg_sign_opt:+"$gpg_sign_opt"} $tree  \
-                       <"$dotest/final-commit"
-       ) &&
-       git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent ||
-       stop_here $this
-
-       if test -f "$dotest/original-commit"; then
-               echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritten"
-       fi
-
-       hook="$(git rev-parse --git-path hooks/post-applypatch)"
-       test -x "$hook" && "$hook"
-
-       go_next
-done
-
-if test -s "$dotest"/rewritten; then
-    git notes copy --for-rewrite=rebase < "$dotest"/rewritten
-    hook="$(git rev-parse --git-path hooks/post-rewrite)"
-    if test -x "$hook"; then
-       "$hook" rebase < "$dotest"/rewritten
-    fi
-fi
-
-# If am was called with --rebasing (from git-rebase--am), it's up to
-# the caller to take care of housekeeping.
-if ! test -f "$dotest/rebasing"
-then
-       rm -fr "$dotest"
-       git gc --auto
-fi
index ae3fec22c48a296cdc1962def9030aa5bb7ce68a..ea63223ab3b5d4f8fb0a9af45db4278aef010fb7 100755 (executable)
@@ -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 "$@" ;;
index 076461e8c8d3cd2051e84060c2ca405c3a3577ac..f649e81f1107722f4c7d051201920ae2a0e7846a 100644 (file)
@@ -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-pull.sh b/git-pull.sh
deleted file mode 100755 (executable)
index a814bf6..0000000
+++ /dev/null
@@ -1,381 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Junio C Hamano
-#
-# Fetch one or more remote refs and merge it/them into the current HEAD.
-
-SUBDIRECTORY_OK=Yes
-OPTIONS_KEEPDASHDASH=
-OPTIONS_STUCKLONG=Yes
-OPTIONS_SPEC="\
-git pull [options] [<repository> [<refspec>...]]
-
-Fetch one or more remote refs and integrate it/them with the current HEAD.
---
-v,verbose                  be more verbose
-q,quiet                    be more quiet
-progress                   force progress reporting
-
-  Options related to merging
-r,rebase?false|true|preserve incorporate changes by rebasing rather than merging
-n!                         do not show a diffstat at the end of the merge
-stat                       show a diffstat at the end of the merge
-summary                    (synonym to --stat)
-log?n                      add (at most <n>) entries from shortlog to merge commit message
-squash                     create a single commit instead of doing a merge
-commit                     perform a commit if the merge succeeds (default)
-e,edit                       edit message before committing
-ff                         allow fast-forward
-ff-only!                   abort if fast-forward is not possible
-verify-signatures          verify that the named commit has a valid GPG signature
-s,strategy=strategy        merge strategy to use
-X,strategy-option=option   option for selected merge strategy
-S,gpg-sign?key-id          GPG sign commit
-
-  Options related to fetching
-all                        fetch from all remotes
-a,append                   append to .git/FETCH_HEAD instead of overwriting
-upload-pack=path           path to upload pack on remote end
-f,force                    force overwrite of local branch
-t,tags                     fetch all tags and associated objects
-p,prune                    prune remote-tracking branches no longer on remote
-recurse-submodules?on-demand control recursive fetching of submodules
-dry-run                    dry run
-k,keep                     keep downloaded pack
-depth=depth                deepen history of shallow clone
-unshallow                  convert to a complete repository
-update-shallow             accept refs that update .git/shallow
-refmap=refmap              specify fetch refmap
-"
-test $# -gt 0 && args="$*"
-. git-sh-setup
-. git-sh-i18n
-set_reflog_action "pull${args+ $args}"
-require_work_tree_exists
-cd_to_toplevel
-
-
-die_conflict () {
-    git diff-index --cached --name-status -r --ignore-submodules HEAD --
-    if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then
-       die "$(gettext "Pull is not possible because you have unmerged files.
-Please, fix them up in the work tree, and then use 'git add/rm <file>'
-as appropriate to mark resolution and make a commit.")"
-    else
-       die "$(gettext "Pull is not possible because you have unmerged files.")"
-    fi
-}
-
-die_merge () {
-    if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then
-       die "$(gettext "You have not concluded your merge (MERGE_HEAD exists).
-Please, commit your changes before you can merge.")"
-    else
-       die "$(gettext "You have not concluded your merge (MERGE_HEAD exists).")"
-    fi
-}
-
-test -z "$(git ls-files -u)" || die_conflict
-test -f "$GIT_DIR/MERGE_HEAD" && die_merge
-
-bool_or_string_config () {
-       git config --bool "$1" 2>/dev/null || git config "$1"
-}
-
-strategy_args= diffstat= no_commit= squash= no_ff= ff_only=
-log_arg= verbosity= progress= recurse_submodules= verify_signatures=
-merge_args= edit= rebase_args= all= append= upload_pack= force= tags= prune=
-keep= depth= unshallow= update_shallow= refmap=
-curr_branch=$(git symbolic-ref -q HEAD)
-curr_branch_short="${curr_branch#refs/heads/}"
-rebase=$(bool_or_string_config branch.$curr_branch_short.rebase)
-if test -z "$rebase"
-then
-       rebase=$(bool_or_string_config pull.rebase)
-fi
-
-# Setup default fast-forward options via `pull.ff`
-pull_ff=$(bool_or_string_config pull.ff)
-case "$pull_ff" in
-true)
-       no_ff=--ff
-       ;;
-false)
-       no_ff=--no-ff
-       ;;
-only)
-       ff_only=--ff-only
-       ;;
-esac
-
-
-dry_run=
-while :
-do
-       case "$1" in
-       -q|--quiet)
-               verbosity="$verbosity -q" ;;
-       -v|--verbose)
-               verbosity="$verbosity -v" ;;
-       --progress)
-               progress=--progress ;;
-       --no-progress)
-               progress=--no-progress ;;
-       -n|--no-stat|--no-summary)
-               diffstat=--no-stat ;;
-       --stat|--summary)
-               diffstat=--stat ;;
-       --log|--log=*|--no-log)
-               log_arg="$1" ;;
-       --no-commit)
-               no_commit=--no-commit ;;
-       --commit)
-               no_commit=--commit ;;
-       -e|--edit)
-               edit=--edit ;;
-       --no-edit)
-               edit=--no-edit ;;
-       --squash)
-               squash=--squash ;;
-       --no-squash)
-               squash=--no-squash ;;
-       --ff)
-               no_ff=--ff ;;
-       --no-ff)
-               no_ff=--no-ff ;;
-       --ff-only)
-               ff_only=--ff-only ;;
-       -s*|--strategy=*)
-               strategy_args="$strategy_args $1"
-               ;;
-       -X*|--strategy-option=*)
-               merge_args="$merge_args $(git rev-parse --sq-quote "$1")"
-               ;;
-       -r*|--rebase=*)
-               rebase="${1#*=}"
-               ;;
-       --rebase)
-               rebase=true
-               ;;
-       --no-rebase)
-               rebase=false
-               ;;
-       --recurse-submodules)
-               recurse_submodules=--recurse-submodules
-               ;;
-       --recurse-submodules=*)
-               recurse_submodules="$1"
-               ;;
-       --no-recurse-submodules)
-               recurse_submodules=--no-recurse-submodules
-               ;;
-       --verify-signatures)
-               verify_signatures=--verify-signatures
-               ;;
-       --no-verify-signatures)
-               verify_signatures=--no-verify-signatures
-               ;;
-       --gpg-sign|-S)
-               gpg_sign_args=-S
-               ;;
-       --gpg-sign=*)
-               gpg_sign_args=$(git rev-parse --sq-quote "-S${1#--gpg-sign=}")
-               ;;
-       -S*)
-               gpg_sign_args=$(git rev-parse --sq-quote "$1")
-               ;;
-       --dry-run)
-               dry_run=--dry-run
-               ;;
-       --all|--no-all)
-               all=$1 ;;
-       -a|--append|--no-append)
-               append=$1 ;;
-       --upload-pack=*|--no-upload-pack)
-               upload_pack=$1 ;;
-       -f|--force|--no-force)
-               force="$force $1" ;;
-       -t|--tags|--no-tags)
-               tags=$1 ;;
-       -p|--prune|--no-prune)
-               prune=$1 ;;
-       -k|--keep|--no-keep)
-               keep=$1 ;;
-       --depth=*|--no-depth)
-               depth=$1 ;;
-       --unshallow|--no-unshallow)
-               unshallow=$1 ;;
-       --update-shallow|--no-update-shallow)
-               update_shallow=$1 ;;
-       --refmap=*|--no-refmap)
-               refmap=$1 ;;
-       -h|--help-all)
-               usage
-               ;;
-       --)
-               shift
-               break
-               ;;
-       *)
-               usage
-               ;;
-       esac
-       shift
-done
-
-case "$rebase" in
-preserve)
-       rebase=true
-       rebase_args=--preserve-merges
-       ;;
-true|false|'')
-       ;;
-*)
-       echo "Invalid value for --rebase, should be true, false, or preserve"
-       usage
-       exit 1
-       ;;
-esac
-
-error_on_no_merge_candidates () {
-       exec >&2
-
-       if test true = "$rebase"
-       then
-               op_type=rebase
-               op_prep=against
-       else
-               op_type=merge
-               op_prep=with
-       fi
-
-       upstream=$(git config "branch.$curr_branch_short.merge")
-       remote=$(git config "branch.$curr_branch_short.remote")
-
-       if [ $# -gt 1 ]; then
-               if [ "$rebase" = true ]; then
-                       printf "There is no candidate for rebasing against "
-               else
-                       printf "There are no candidates for merging "
-               fi
-               echo "among the refs that you just fetched."
-               echo "Generally this means that you provided a wildcard refspec which had no"
-               echo "matches on the remote end."
-       elif [ $# -gt 0 ] && [ "$1" != "$remote" ]; then
-               echo "You asked to pull from the remote '$1', but did not specify"
-               echo "a branch. Because this is not the default configured remote"
-               echo "for your current branch, you must specify a branch on the command line."
-       elif [ -z "$curr_branch" -o -z "$upstream" ]; then
-               . git-parse-remote
-               error_on_missing_default_upstream "pull" $op_type $op_prep \
-                       "git pull <remote> <branch>"
-       else
-               echo "Your configuration specifies to $op_type $op_prep the ref '${upstream#refs/heads/}'"
-               echo "from the remote, but no such ref was fetched."
-       fi
-       exit 1
-}
-
-test true = "$rebase" && {
-       if ! git rev-parse -q --verify HEAD >/dev/null
-       then
-               # On an unborn branch
-               if test -f "$(git rev-parse --git-path index)"
-               then
-                       die "$(gettext "updating an unborn branch with changes added to the index")"
-               fi
-       else
-               require_clean_work_tree "pull with rebase" "Please commit or stash them."
-       fi
-       oldremoteref= &&
-       test -n "$curr_branch" &&
-       . git-parse-remote &&
-       remoteref="$(get_remote_merge_branch "$@" 2>/dev/null)" &&
-       oldremoteref=$(git merge-base --fork-point "$remoteref" $curr_branch 2>/dev/null)
-}
-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 \
-$refmap --update-head-ok "$@" || exit 1
-test -z "$dry_run" || exit 0
-
-curr_head=$(git rev-parse -q --verify HEAD)
-if test -n "$orig_head" && test "$curr_head" != "$orig_head"
-then
-       # The fetch involved updating the current branch.
-
-       # The working tree and the index file is still based on the
-       # $orig_head commit, but we are merging into $curr_head.
-       # First update the working tree to match $curr_head.
-
-       eval_gettextln "Warning: fetch updated the current branch head.
-Warning: fast-forwarding your working tree from
-Warning: commit \$orig_head." >&2
-       git update-index -q --refresh
-       git read-tree -u -m "$orig_head" "$curr_head" ||
-               die "$(eval_gettext "Cannot fast-forward your working tree.
-After making sure that you saved anything precious from
-$ git diff \$orig_head
-output, run
-$ git reset --hard
-to recover.")"
-
-fi
-
-merge_head=$(sed -e '/ not-for-merge   /d' \
-       -e 's/  .*//' "$GIT_DIR"/FETCH_HEAD | \
-       tr '\012' ' ')
-
-case "$merge_head" in
-'')
-       error_on_no_merge_candidates "$@"
-       ;;
-?*' '?*)
-       if test -z "$orig_head"
-       then
-               die "$(gettext "Cannot merge multiple branches into empty head")"
-       fi
-       if test true = "$rebase"
-       then
-               die "$(gettext "Cannot rebase onto multiple branches")"
-       fi
-       ;;
-esac
-
-# Pulling into unborn branch: a shorthand for branching off
-# FETCH_HEAD, for lazy typers.
-if test -z "$orig_head"
-then
-       # Two-way merge: we claim the index is based on an empty tree,
-       # and try to fast-forward to HEAD.  This ensures we will not
-       # lose index/worktree changes that the user already made on
-       # the unborn branch.
-       empty_tree=4b825dc642cb6eb9a060e54bf8d69288fbee4904
-       git read-tree -m -u $empty_tree $merge_head &&
-       git update-ref -m "initial pull" HEAD $merge_head "$curr_head"
-       exit
-fi
-
-if test true = "$rebase"
-then
-       o=$(git show-branch --merge-base $curr_branch $merge_head $oldremoteref)
-       if test "$oldremoteref" = "$o"
-       then
-               unset oldremoteref
-       fi
-fi
-
-case "$rebase" in
-true)
-       eval="git-rebase $diffstat $strategy_args $merge_args $rebase_args $verbosity"
-       eval="$eval $gpg_sign_args"
-       eval="$eval --onto $merge_head ${oldremoteref:-$merge_head}"
-       ;;
-*)
-       eval="git-merge $diffstat $no_commit $verify_signatures $edit $squash $no_ff $ff_only"
-       eval="$eval $log_arg $strategy_args $merge_args $verbosity $progress"
-       eval="$eval $gpg_sign_args"
-       eval="$eval FETCH_HEAD"
-       ;;
-esac
-eval "exec $eval"
index 5ff0f1c81ac8e72ce6a2358ca2fced0650571030..f01637b1fdeb7f021b74171135ac0e7dba566542 100644 (file)
@@ -152,11 +152,21 @@ Commands:
  s, squash = use commit, but meld into previous commit
  f, fixup = like "squash", but discard this commit's log message
  x, exec = run command (the rest of the line) using shell
+ d, drop = remove commit
 
 These lines can be re-ordered; they are executed from top to bottom.
 
+EOF
+       if test $(get_missing_commit_check_level) = error
+       then
+               git stripspace --comment-lines >>"$todo" <<\EOF
+Do not remove any line. Use 'drop' explicitly to remove a commit.
+EOF
+       else
+               git stripspace --comment-lines >>"$todo" <<\EOF
 If you remove a line here THAT COMMIT WILL BE LOST.
 EOF
+       fi
 }
 
 make_patch () {
@@ -505,7 +515,7 @@ do_next () {
        rm -f "$msg" "$author_script" "$amend" "$state_dir"/stopped-sha || exit
        read -r command sha1 rest < "$todo"
        case "$command" in
-       "$comment_char"*|''|noop)
+       "$comment_char"*|''|noop|drop|d)
                mark_action_done
                ;;
        pick|p)
@@ -740,10 +750,15 @@ collapse_todo_ids() {
 # "pick sha1 fixup!/squash! msg" appears in it so that the latter
 # comes immediately after the former, and change "pick" to
 # "fixup"/"squash".
+#
+# Note that if the config has specified a custom instruction format
+# each log message will be re-retrieved in order to normalize the
+# autosquash arrangement
 rearrange_squash () {
        # extract fixup!/squash! lines and resolve any referenced sha1's
        while read -r pick sha1 message
        do
+               test -z "${format}" || message=$(git log -n 1 --format="%s" ${sha1})
                case "$message" in
                "squash! "*|"fixup! "*)
                        action="${message%%!*}"
@@ -785,6 +800,7 @@ rearrange_squash () {
                *" $sha1 "*) continue ;;
                esac
                printf '%s\n' "$pick $sha1 $message"
+               test -z "${format}" || message=$(git log -n 1 --format="%s" ${sha1})
                used="$used$sha1 "
                while read -r squash action msg_prefix msg_content
                do
@@ -802,8 +818,13 @@ rearrange_squash () {
                                case "$message" in "$msg_content"*) emit=1;; esac ;;
                        esac
                        if test $emit = 1; then
-                               real_prefix=$(echo "$msg_prefix" | sed "s/,/! /g")
-                               printf '%s\n' "$action $squash ${real_prefix}$msg_content"
+                               if test -n "${format}"
+                               then
+                                       msg_content=$(git log -n 1 --format="${format}" ${squash})
+                               else
+                                       msg_content="$(echo "$msg_prefix" | sed "s/,/! /g")$msg_content"
+                               fi
+                               printf '%s\n' "$action $squash $msg_content"
                                used="$used$squash "
                        fi
                done <"$1.sq"
@@ -833,6 +854,180 @@ add_exec_commands () {
        mv "$1.new" "$1"
 }
 
+# Check if the SHA-1 passed as an argument is a
+# correct one, if not then print $2 in "$todo".badsha
+# $1: the SHA-1 to test
+# $2: the line to display if incorrect SHA-1
+check_commit_sha () {
+       badsha=0
+       if test -z $1
+       then
+               badsha=1
+       else
+               sha1_verif="$(git rev-parse --verify --quiet $1^{commit})"
+               if test -z $sha1_verif
+               then
+                       badsha=1
+               fi
+       fi
+
+       if test $badsha -ne 0
+       then
+               warn "Warning: the SHA-1 is missing or isn't" \
+                       "a commit in the following line:"
+               warn " - $2"
+               warn
+       fi
+
+       return $badsha
+}
+
+# prints the bad commits and bad commands
+# from the todolist in stdin
+check_bad_cmd_and_sha () {
+       retval=0
+       git stripspace --strip-comments |
+       (
+               while read -r line
+               do
+                       IFS=' '
+                       set -- $line
+                       command=$1
+                       sha1=$2
+
+                       case $command in
+                       ''|noop|x|"exec")
+                               # Doesn't expect a SHA-1
+                               ;;
+                       pick|p|drop|d|reword|r|edit|e|squash|s|fixup|f)
+                               if ! check_commit_sha $sha1 "$line"
+                               then
+                                       retval=1
+                               fi
+                               ;;
+                       *)
+                               warn "Warning: the command isn't recognized" \
+                                       "in the following line:"
+                               warn " - $line"
+                               warn
+                               retval=1
+                               ;;
+                       esac
+               done
+
+               return $retval
+       )
+}
+
+# Print the list of the SHA-1 of the commits
+# from stdin to stdout
+todo_list_to_sha_list () {
+       git stripspace --strip-comments |
+       while read -r command sha1 rest
+       do
+               case $command in
+               "$comment_char"*|''|noop|x|"exec")
+                       ;;
+               *)
+                       long_sha=$(git rev-list --no-walk "$sha1" 2>/dev/null)
+                       printf "%s\n" "$long_sha"
+                       ;;
+               esac
+       done
+}
+
+# Use warn for each line in stdin
+warn_lines () {
+       while read -r line
+       do
+               warn " - $line"
+       done
+}
+
+# Switch to the branch in $into and notify it in the reflog
+checkout_onto () {
+       GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name"
+       output git checkout $onto || die_abort "could not detach HEAD"
+       git update-ref ORIG_HEAD $orig_head
+}
+
+get_missing_commit_check_level () {
+       check_level=$(git config --get rebase.missingCommitsCheck)
+       check_level=${check_level:-ignore}
+       # Don't be case sensitive
+       printf '%s' "$check_level" | tr 'A-Z' 'a-z'
+}
+
+# Check if the user dropped some commits by mistake
+# Behaviour determined by rebase.missingCommitsCheck.
+# Check if there is an unrecognized command or a
+# bad SHA-1 in a command.
+check_todo_list () {
+       raise_error=f
+
+       check_level=$(get_missing_commit_check_level)
+
+       case "$check_level" in
+       warn|error)
+               # Get the SHA-1 of the commits
+               todo_list_to_sha_list <"$todo".backup >"$todo".oldsha1
+               todo_list_to_sha_list <"$todo" >"$todo".newsha1
+
+               # Sort the SHA-1 and compare them
+               sort -u "$todo".oldsha1 >"$todo".oldsha1+
+               mv "$todo".oldsha1+ "$todo".oldsha1
+               sort -u "$todo".newsha1 >"$todo".newsha1+
+               mv "$todo".newsha1+ "$todo".newsha1
+               comm -2 -3 "$todo".oldsha1 "$todo".newsha1 >"$todo".miss
+
+               # Warn about missing commits
+               if test -s "$todo".miss
+               then
+                       test "$check_level" = error && raise_error=t
+
+                       warn "Warning: some commits may have been dropped" \
+                               "accidentally."
+                       warn "Dropped commits (newer to older):"
+
+                       # Make the list user-friendly and display
+                       opt="--no-walk=sorted --format=oneline --abbrev-commit --stdin"
+                       git rev-list $opt <"$todo".miss | warn_lines
+
+                       warn "To avoid this message, use \"drop\" to" \
+                               "explicitly remove a commit."
+                       warn
+                       warn "Use 'git config rebase.missingCommitsCheck' to change" \
+                               "the level of warnings."
+                       warn "The possible behaviours are: ignore, warn, error."
+                       warn
+               fi
+               ;;
+       ignore)
+               ;;
+       *)
+               warn "Unrecognized setting $check_level for option" \
+                       "rebase.missingCommitsCheck. Ignoring."
+               ;;
+       esac
+
+       if ! check_bad_cmd_and_sha <"$todo"
+       then
+               raise_error=t
+       fi
+
+       if test $raise_error = t
+       then
+               # Checkout before the first commit of the
+               # rebase: this way git rebase --continue
+               # will work correctly as it expects HEAD to be
+               # placed before the commit of the next action
+               checkout_onto
+
+               warn "You can fix this with 'git rebase --edit-todo'."
+               die "Or you can abort the rebase with 'git rebase --abort'."
+       fi
+}
+
 # The whole contents of this file is run by dot-sourcing it from
 # inside a shell function.  It used to be that "return"s we see
 # below were not inside any function, and expected to return
@@ -981,7 +1176,10 @@ else
        revisions=$onto...$orig_head
        shortrevisions=$shorthead
 fi
-git rev-list $merges_option --pretty=oneline --reverse --left-right --topo-order \
+format=$(git config --get rebase.instructionFormat)
+# the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse
+git rev-list $merges_option --format="%m%H ${format:-%s}" \
+       --reverse --left-right --topo-order \
        $revisions ${restrict_revision+^$restrict_revision} | \
        sed -n "s/^>//p" |
 while read -r sha1 rest
@@ -1080,13 +1278,13 @@ git_sequence_editor "$todo" ||
 has_action "$todo" ||
        return 2
 
+check_todo_list
+
 expand_todo_ids
 
 test -d "$rewritten" || test -n "$force_rebase" || skip_unnecessary_picks
 
-GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name"
-output git checkout $onto || die_abort "could not detach HEAD"
-git update-ref ORIG_HEAD $orig_head
+checkout_onto
 do_rest
 
 }
index ae9f8698c5a4842c2c0d63db51dde067ddeeb1c9..b660cc238223b101762017c7effda67de6f70113 100755 (executable)
@@ -460,25 +460,11 @@ sub read_config {
 ($repoauthor) = Git::ident_person(@repo, 'author');
 ($repocommitter) = Git::ident_person(@repo, 'committer');
 
-# Verify the user input
-
-foreach my $entry (@initial_to) {
-       die "Comma in --to entry: $entry'\n" unless $entry !~ m/,/;
-}
-
-foreach my $entry (@initial_cc) {
-       die "Comma in --cc entry: $entry'\n" unless $entry !~ m/,/;
-}
-
-foreach my $entry (@bcclist) {
-       die "Comma in --bcclist entry: $entry'\n" unless $entry !~ m/,/;
-}
-
 sub parse_address_line {
        if ($have_mail_address) {
                return map { $_->format } Mail::Address->parse($_[0]);
        } else {
-               return split_addrs($_[0]);
+               return Git::parse_mailboxes($_[0]);
        }
 }
 
@@ -561,8 +547,6 @@ sub parse_sendmail_aliases {
        }
 }
 
-($sender) = expand_aliases($sender) if defined $sender;
-
 # is_format_patch_arg($f) returns 0 if $f names a patch, or 1 if
 # $f is a revision list specification to be passed to format-patch.
 sub is_format_patch_arg {
@@ -807,7 +791,10 @@ sub file_declares_8bit_cte {
        }
 }
 
-if (!defined $sender) {
+if (defined $sender) {
+       $sender =~ s/^\s+|\s+$//g;
+       ($sender) = expand_aliases($sender);
+} else {
        $sender = $repoauthor || $repocommitter || '';
 }
 
@@ -839,12 +826,9 @@ sub expand_one_alias {
        return $aliases{$alias} ? expand_aliases(@{$aliases{$alias}}) : $alias;
 }
 
-@initial_to = expand_aliases(@initial_to);
-@initial_to = validate_address_list(sanitize_address_list(@initial_to));
-@initial_cc = expand_aliases(@initial_cc);
-@initial_cc = validate_address_list(sanitize_address_list(@initial_cc));
-@bcclist = expand_aliases(@bcclist);
-@bcclist = validate_address_list(sanitize_address_list(@bcclist));
+@initial_to = process_address_list(@initial_to);
+@initial_cc = process_address_list(@initial_cc);
+@bcclist = process_address_list(@bcclist);
 
 if ($thread && !defined $initial_reply_to && $prompting) {
        $initial_reply_to = ask(
@@ -1037,15 +1021,17 @@ sub sanitize_address {
                return $recipient;
        }
 
+       # remove non-escaped quotes
+       $recipient_name =~ s/(^|[^\\])"/$1/g;
+
        # rfc2047 is needed if a non-ascii char is included
        if ($recipient_name =~ /[^[:ascii:]]/) {
-               $recipient_name =~ s/^"(.*)"$/$1/;
                $recipient_name = quote_rfc2047($recipient_name);
        }
 
        # double quotes are needed if specials or CTLs are included
        elsif ($recipient_name =~ /[][()<>@,;:\\".\000-\037\177]/) {
-               $recipient_name =~ s/(["\\\r])/\\$1/g;
+               $recipient_name =~ s/([\\\r])/\\$1/g;
                $recipient_name = qq["$recipient_name"];
        }
 
@@ -1057,6 +1043,14 @@ sub sanitize_address_list {
        return (map { sanitize_address($_) } @_);
 }
 
+sub process_address_list {
+       my @addr_list = map { parse_address_line($_) } @_;
+       @addr_list = expand_aliases(@addr_list);
+       @addr_list = sanitize_address_list(@addr_list);
+       @addr_list = validate_address_list(@addr_list);
+       return @addr_list;
+}
+
 # Returns the local Fully Qualified Domain Name (FQDN) if available.
 #
 # Tightly configured MTAa require that a caller sends a real DNS
@@ -1566,8 +1560,8 @@ sub send_message {
                ($confirm =~ /^(?:auto|compose)$/ && $compose && $message_num == 1));
        $needs_confirm = "inform" if ($needs_confirm && $confirm_unconfigured && @cc);
 
-       @to = validate_address_list(sanitize_address_list(@to));
-       @cc = validate_address_list(sanitize_address_list(@cc));
+       @to = process_address_list(@to);
+       @cc = process_address_list(@cc);
 
        @to = (@initial_to, @to);
        @cc = (@initial_cc, @cc);
index 8e9e2cd7d5697c1f2b7ccb8753d384d9f4461bef..1d5ba7a4f935fd08572c235b7c5d4390eb8d6528 100755 (executable)
@@ -183,9 +183,7 @@ store_stash () {
                stash_msg="Created via \"git stash store\"."
        fi
 
-       # Make sure the reflog for stash is kept.
-       : >>"$(git rev-parse --git-path logs/$ref_stash)"
-       git update-ref -m "$stash_msg" $ref_stash $w_commit
+       git update-ref --create-reflog -m "$stash_msg" $ref_stash $w_commit
        ret=$?
        test $ret != 0 && test -z $quiet &&
        die "$(eval_gettext "Cannot update \$ref_stash with \$w_commit")"
@@ -262,7 +260,7 @@ save_stash () {
                say "$(gettext "No local changes to save")"
                exit 0
        fi
-       test -f "$(git rev-parse --git-path logs/$ref_stash)" ||
+       git reflog exists $ref_stash ||
                clear_stash || die "$(gettext "Cannot initialize stash")"
 
        create_stash "$stash_msg" $untracked
diff --git a/git.c b/git.c
index fe94066aeebb0d71ea140c3d67ac4ad232db0799..5feba410cab6d95e3b9ff9745db56a9d045f0c20 100644 (file)
--- 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 },
@@ -445,6 +446,7 @@ static struct cmd_struct commands[] = {
        { "pickaxe", cmd_blame, RUN_SETUP },
        { "prune", cmd_prune, RUN_SETUP },
        { "prune-packed", cmd_prune_packed, RUN_SETUP },
+       { "pull", cmd_pull, RUN_SETUP | NEED_WORK_TREE },
        { "push", cmd_push, RUN_SETUP },
        { "read-tree", cmd_read_tree, RUN_SETUP },
        { "receive-pack", cmd_receive_pack },
index 68b0c814f789f39151d380b1d318d6f80090d524..3dc2fe397e32d79713780596f0ef4666c14b5955 100644 (file)
@@ -60,6 +60,43 @@ void parse_gpg_output(struct signature_check *sigc)
        }
 }
 
+int check_signature(const char *payload, size_t plen, const char *signature,
+       size_t slen, struct signature_check *sigc)
+{
+       struct strbuf gpg_output = STRBUF_INIT;
+       struct strbuf gpg_status = STRBUF_INIT;
+       int status;
+
+       sigc->result = 'N';
+
+       status = verify_signed_buffer(payload, plen, signature, slen,
+                                     &gpg_output, &gpg_status);
+       if (status && !gpg_output.len)
+               goto out;
+       sigc->payload = xmemdupz(payload, plen);
+       sigc->gpg_output = strbuf_detach(&gpg_output, NULL);
+       sigc->gpg_status = strbuf_detach(&gpg_status, NULL);
+       parse_gpg_output(sigc);
+
+ out:
+       strbuf_release(&gpg_status);
+       strbuf_release(&gpg_output);
+
+       return sigc->result != 'G' && sigc->result != 'U';
+}
+
+void print_signature_buffer(const struct signature_check *sigc, unsigned flags)
+{
+       const char *output = flags & GPG_VERIFY_RAW ?
+               sigc->gpg_status : sigc->gpg_output;
+
+       if (flags & GPG_VERIFY_VERBOSE && sigc->payload)
+               fputs(sigc->payload, stdout);
+
+       if (output)
+               fputs(output, stderr);
+}
+
 /*
  * Look at GPG signed content (e.g. a signed tag object), whose
  * payload is followed by a detached signature on it.  Return the
index 87a4f2e3fad92b4622a40ef5612db293bf4c5dd4..ea68885ad5b73aa63acc936eee52a61252420892 100644 (file)
@@ -1,6 +1,9 @@
 #ifndef GPG_INTERFACE_H
 #define GPG_INTERFACE_H
 
+#define GPG_VERIFY_VERBOSE     1
+#define GPG_VERIFY_RAW         2
+
 struct signature_check {
        char *payload;
        char *gpg_output;
@@ -27,5 +30,8 @@ extern int verify_signed_buffer(const char *payload, size_t payload_size, const
 extern int git_gpg_config(const char *, const char *, void *);
 extern void set_signing_key(const char *);
 extern const char *get_signing_key(void);
+extern int check_signature(const char *payload, size_t plen,
+       const char *signature, size_t slen, struct signature_check *sigc);
+void print_signature_buffer(const struct signature_check *sigc, unsigned flags);
 
 #endif
index 501bf797c0658e33aa8215562c44f0dec9eea75e..bac40ef6e6e3566a0ceda81303a6ebb769ac520d 100644 (file)
@@ -92,7 +92,7 @@ static void hdr_int(const char *name, uintmax_t value)
 
 static void hdr_date(const char *name, unsigned long when)
 {
-       const char *value = show_date(when, 0, DATE_RFC2822);
+       const char *value = show_date(when, 0, DATE_MODE(RFC2822));
        hdr_str(name, value);
 }
 
@@ -164,7 +164,7 @@ static void send_strbuf(const char *type, struct strbuf *buf)
 
 static void send_local_file(const char *the_type, const char *name)
 {
-       const char *p = git_path("%s", name);
+       char *p = git_pathdup("%s", name);
        size_t buf_alloc = 8192;
        char *buf = xmalloc(buf_alloc);
        int fd;
@@ -191,6 +191,7 @@ static void send_local_file(const char *the_type, const char *name)
        }
        close(fd);
        free(buf);
+       free(p);
 }
 
 static void get_text_file(char *name)
diff --git a/http.c b/http.c
index e9c6fdd835ea4bd4fe24ad374fbef69d6e9a8ba2..8cd59f7f36a86b4d355bc6a3ff123c822bdc0078 100644 (file)
--- 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;
 }
index 01beb11f65b518e382fa01f18f03c5a9f2ddf529..7b1b57aaf6e48a430f664fa8deafb75a0cfcc576 100644 (file)
@@ -97,11 +97,12 @@ static int add_ref_decoration(const char *refname, const struct object_id *oid,
 
        assert(cb_data == NULL);
 
-       if (starts_with(refname, "refs/replace/")) {
+       if (starts_with(refname, git_replace_ref_base)) {
                struct object_id original_oid;
                if (!check_replace_refs)
                        return 0;
-               if (get_oid_hex(refname + 13, &original_oid)) {
+               if (get_oid_hex(refname + strlen(git_replace_ref_base),
+                               &original_oid)) {
                        warning("invalid replace ref %s", refname);
                        return 0;
                }
@@ -639,7 +640,7 @@ void show_log(struct rev_info *opt)
                         */
                        show_reflog_message(opt->reflog_info,
                                            opt->commit_format == CMIT_FMT_ONELINE,
-                                           opt->date_mode,
+                                           &opt->date_mode,
                                            opt->date_mode_explicit);
                        if (opt->commit_format == CMIT_FMT_ONELINE)
                                return;
index 0b2b82c41fc043a48e9bf458c4f75f992ead7175..b3d1dab51fb9e7aea4af05211326a1a91b1279d7 100644 (file)
@@ -295,7 +295,7 @@ static void write_buf_to_worktree(const unsigned char *obj,
                                  const char *buf, unsigned long size)
 {
        int fd;
-       const char *path = git_path(NOTES_MERGE_WORKTREE "/%s", sha1_to_hex(obj));
+       char *path = git_pathdup(NOTES_MERGE_WORKTREE "/%s", sha1_to_hex(obj));
        if (safe_create_leading_directories_const(path))
                die_errno("unable to create directory for '%s'", path);
        if (file_exists(path))
@@ -320,6 +320,7 @@ static void write_buf_to_worktree(const unsigned char *obj,
        }
 
        close(fd);
+       free(path);
 }
 
 static void write_note_to_worktree(const unsigned char *obj,
index be8c413cfebb414bcbb7815cf4fe42bc31444d65..5ab6ed6b0875ff703e3d55abbd0e9d8b0aa1a3c7 100644 (file)
@@ -4,6 +4,7 @@
 #include "commit.h"
 #include "color.h"
 #include "string-list.h"
+#include "argv-array.h"
 
 /*----- some often used options -----*/
 
@@ -134,3 +135,71 @@ int parse_opt_noop_cb(const struct option *opt, const char *arg, int unset)
 {
        return 0;
 }
+
+/**
+ * Recreates the command-line option in the strbuf.
+ */
+static int recreate_opt(struct strbuf *sb, const struct option *opt,
+               const char *arg, int unset)
+{
+       strbuf_reset(sb);
+
+       if (opt->long_name) {
+               strbuf_addstr(sb, unset ? "--no-" : "--");
+               strbuf_addstr(sb, opt->long_name);
+               if (arg) {
+                       strbuf_addch(sb, '=');
+                       strbuf_addstr(sb, arg);
+               }
+       } else if (opt->short_name && !unset) {
+               strbuf_addch(sb, '-');
+               strbuf_addch(sb, opt->short_name);
+               if (arg)
+                       strbuf_addstr(sb, arg);
+       } else
+               return -1;
+
+       return 0;
+}
+
+/**
+ * For an option opt, recreates the command-line option in opt->value which
+ * must be an char* initialized to NULL. This is useful when we need to pass
+ * the command-line option to another command. Since any previous value will be
+ * overwritten, this callback should only be used for options where the last
+ * one wins.
+ */
+int parse_opt_passthru(const struct option *opt, const char *arg, int unset)
+{
+       static struct strbuf sb = STRBUF_INIT;
+       char **opt_value = opt->value;
+
+       if (recreate_opt(&sb, opt, arg, unset) < 0)
+               return -1;
+
+       if (*opt_value)
+               free(*opt_value);
+
+       *opt_value = strbuf_detach(&sb, NULL);
+
+       return 0;
+}
+
+/**
+ * For an option opt, recreate the command-line option, appending it to
+ * opt->value which must be a argv_array. This is useful when we need to pass
+ * the command-line option, which can be specified multiple times, to another
+ * command.
+ */
+int parse_opt_passthru_argv(const struct option *opt, const char *arg, int unset)
+{
+       static struct strbuf sb = STRBUF_INIT;
+       struct argv_array *opt_value = opt->value;
+
+       if (recreate_opt(&sb, opt, arg, unset) < 0)
+               return -1;
+
+       argv_array_push(opt_value, sb.buf);
+
+       return 0;
+}
index 80106c06bcc68782baad6afa5954b829ab17ba8d..3eceba4463b7e837cc12ee810811c8c5b8b253e7 100644 (file)
@@ -180,6 +180,23 @@ static int get_value(struct parse_opt_ctx_t *p,
                        return opterror(opt, "expects a numerical value", flags);
                return 0;
 
+       case OPTION_MAGNITUDE:
+               if (unset) {
+                       *(unsigned long *)opt->value = 0;
+                       return 0;
+               }
+               if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+                       *(unsigned long *)opt->value = opt->defval;
+                       return 0;
+               }
+               if (get_arg(p, opt, flags, &arg))
+                       return -1;
+               if (!git_parse_ulong(arg, opt->value))
+                       return opterror(opt,
+                               "expects a non-negative integer value with an optional k/m/g suffix",
+                               flags);
+               return 0;
+
        default:
                die("should not happen, someone must be hit on the forehead");
        }
index c71e9da4f820ab8e72ff95afb656faece7f1a252..3f1cc3aee0faaed923f736393db45877de14edc3 100644 (file)
@@ -16,6 +16,7 @@ enum parse_opt_type {
        /* options with arguments (usually) */
        OPTION_STRING,
        OPTION_INTEGER,
+       OPTION_MAGNITUDE,
        OPTION_CALLBACK,
        OPTION_LOWLEVEL_CALLBACK,
        OPTION_FILENAME
@@ -126,9 +127,11 @@ 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), \
+                                     N_("n"), (h), PARSE_OPT_NONEG }
 #define OPT_STRING(s, l, v, a, h)   { OPTION_STRING,  (s), (l), (v), (a), (h) }
 #define OPT_STRING_LIST(s, l, v, a, h) \
                                    { OPTION_CALLBACK, (s), (l), (v), (a), \
@@ -224,6 +227,8 @@ extern int parse_opt_with_commit(const struct option *, const char *, int);
 extern int parse_opt_tertiary(const struct option *, const char *, int);
 extern int parse_opt_string_list(const struct option *, const char *, int);
 extern int parse_opt_noop_cb(const struct option *, const char *, int);
+extern int parse_opt_passthru(const struct option *, const char *, int);
+extern int parse_opt_passthru_argv(const struct option *, const char *, int);
 
 #define OPT__VERBOSE(var, h)  OPT_COUNTUP('v', "verbose", (var), (h))
 #define OPT__QUIET(var, h)    OPT_COUNTUP('q', "quiet",   (var), (h))
@@ -242,5 +247,9 @@ extern int parse_opt_noop_cb(const struct option *, const char *, int);
        OPT_COLOR_FLAG(0, "color", (var), (h))
 #define OPT_COLUMN(s, l, v, h) \
        { OPTION_CALLBACK, (s), (l), (v), N_("style"), (h), PARSE_OPT_OPTARG, parseopt_column_callback }
+#define OPT_PASSTHRU(s, l, v, a, h, f) \
+       { OPTION_CALLBACK, (s), (l), (v), (a), (h), (f), parse_opt_passthru }
+#define OPT_PASSTHRU_ARGV(s, l, v, a, h, f) \
+       { OPTION_CALLBACK, (s), (l), (v), (a), (h), (f), parse_opt_passthru_argv }
 
 #endif
diff --git a/path.c b/path.c
index 10f4cbf6b78607870461f21dd1cd0f7a2776bc49..95acbafa6883b4418f19a208cb9889f5642f3925 100644 (file)
--- a/path.c
+++ b/path.c
@@ -224,11 +224,10 @@ const char *mkpath(const char *fmt, ...)
        return cleanup_path(pathname->buf);
 }
 
-const char *git_path_submodule(const char *path, const char *fmt, ...)
+static void do_submodule_path(struct strbuf *buf, const char *path,
+                             const char *fmt, va_list args)
 {
-       struct strbuf *buf = get_pathname();
        const char *git_dir;
-       va_list args;
 
        strbuf_addstr(buf, path);
        if (buf->len && buf->buf[buf->len - 1] != '/')
@@ -242,11 +241,27 @@ const char *git_path_submodule(const char *path, const char *fmt, ...)
        }
        strbuf_addch(buf, '/');
 
-       va_start(args, fmt);
        strbuf_vaddf(buf, fmt, args);
-       va_end(args);
        strbuf_cleanup_path(buf);
-       return buf->buf;
+}
+
+char *git_pathdup_submodule(const char *path, const char *fmt, ...)
+{
+       va_list args;
+       struct strbuf buf = STRBUF_INIT;
+       va_start(args, fmt);
+       do_submodule_path(&buf, path, fmt, args);
+       va_end(args);
+       return strbuf_detach(&buf, NULL);
+}
+
+void strbuf_git_path_submodule(struct strbuf *buf, const char *path,
+                              const char *fmt, ...)
+{
+       va_list args;
+       va_start(args, fmt);
+       do_submodule_path(buf, path, fmt, args);
+       va_end(args);
 }
 
 int validate_headref(const char *path)
@@ -918,3 +933,13 @@ char *xdg_config_home(const char *filename)
                return mkpathdup("%s/.config/git/%s", home, filename);
        return NULL;
 }
+
+GIT_PATH_FUNC(git_path_cherry_pick_head, "CHERRY_PICK_HEAD")
+GIT_PATH_FUNC(git_path_revert_head, "REVERT_HEAD")
+GIT_PATH_FUNC(git_path_squash_msg, "SQUASH_MSG")
+GIT_PATH_FUNC(git_path_merge_msg, "MERGE_MSG")
+GIT_PATH_FUNC(git_path_merge_rr, "MERGE_RR")
+GIT_PATH_FUNC(git_path_merge_mode, "MERGE_MODE")
+GIT_PATH_FUNC(git_path_merge_head, "MERGE_HEAD")
+GIT_PATH_FUNC(git_path_fetch_head, "FETCH_HEAD")
+GIT_PATH_FUNC(git_path_shallow, "shallow")
index 9026a7bb980a984086a62536f46ec3837588f277..19ef081103a56e3c723749dc172ccc356a9b795c 100644 (file)
@@ -864,6 +864,73 @@ sub ident_person {
        return "$ident[0] <$ident[1]>";
 }
 
+=item parse_mailboxes
+
+Return an array of mailboxes extracted from a string.
+
+=cut
+
+sub parse_mailboxes {
+       my $re_comment = qr/\((?:[^)]*)\)/;
+       my $re_quote = qr/"(?:[^\"\\]|\\.)*"/;
+       my $re_word = qr/(?:[^]["\s()<>:;@\\,.]|\\.)+/;
+
+       # divide the string in tokens of the above form
+       my $re_token = qr/(?:$re_quote|$re_word|$re_comment|\S)/;
+       my @tokens = map { $_ =~ /\s*($re_token)\s*/g } @_;
+
+       # add a delimiter to simplify treatment for the last mailbox
+       push @tokens, ",";
+
+       my (@addr_list, @phrase, @address, @comment, @buffer) = ();
+       foreach my $token (@tokens) {
+               if ($token =~ /^[,;]$/) {
+                       # if buffer still contains undeterminated strings
+                       # append it at the end of @address or @phrase
+                       if (@address) {
+                               push @address, @buffer;
+                       } else {
+                               push @phrase, @buffer;
+                       }
+
+                       my $str_phrase = join ' ', @phrase;
+                       my $str_address = join '', @address;
+                       my $str_comment = join ' ', @comment;
+
+                       # quote are necessary if phrase contains
+                       # special characters
+                       if ($str_phrase =~ /[][()<>:;@\\,.\000-\037\177]/) {
+                               $str_phrase =~ s/(^|[^\\])"/$1/g;
+                               $str_phrase = qq["$str_phrase"];
+                       }
+
+                       # add "<>" around the address if necessary
+                       if ($str_address ne "" && $str_phrase ne "") {
+                               $str_address = qq[<$str_address>];
+                       }
+
+                       my $str_mailbox = "$str_phrase $str_address $str_comment";
+                       $str_mailbox =~ s/^\s*|\s*$//g;
+                       push @addr_list, $str_mailbox if ($str_mailbox);
+
+                       @phrase = @address = @comment = @buffer = ();
+               } elsif ($token =~ /^\(/) {
+                       push @comment, $token;
+               } elsif ($token eq "<") {
+                       push @phrase, (splice @address), (splice @buffer);
+               } elsif ($token eq ">") {
+                       push @address, (splice @buffer);
+               } elsif ($token eq "@") {
+                       push @address, (splice @buffer), "@";
+               } elsif ($token eq ".") {
+                       push @address, (splice @buffer), ".";
+               } else {
+                       push @buffer, $token;
+               }
+       }
+
+       return @addr_list;
+}
 
 =item hash_object ( TYPE, FILENAME )
 
index 187a2293e7d9a36ba294fe775efcf2b87f4e9233..08a1427c0d4f6743182b235ec9d9c710c3c99b5b 100644 (file)
@@ -4,16 +4,51 @@
 char packet_buffer[LARGE_PACKET_MAX];
 static const char *packet_trace_prefix = "git";
 static struct trace_key trace_packet = TRACE_KEY_INIT(PACKET);
+static struct trace_key trace_pack = TRACE_KEY_INIT(PACKFILE);
 
 void packet_trace_identity(const char *prog)
 {
        packet_trace_prefix = xstrdup(prog);
 }
 
+static int packet_trace_pack(const char *buf, unsigned int len, int sideband)
+{
+       if (!sideband) {
+               trace_verbatim(&trace_pack, buf, len);
+               return 1;
+       } else if (len && *buf == '\1') {
+               trace_verbatim(&trace_pack, buf + 1, len - 1);
+               return 1;
+       } else {
+               /* it's another non-pack sideband */
+               return 0;
+       }
+}
+
 static void packet_trace(const char *buf, unsigned int len, int write)
 {
        int i;
        struct strbuf out;
+       static int in_pack, sideband;
+
+       if (!trace_want(&trace_packet) && !trace_want(&trace_pack))
+               return;
+
+       if (in_pack) {
+               if (packet_trace_pack(buf, len, sideband))
+                       return;
+       } else if (starts_with(buf, "PACK") || starts_with(buf, "\1PACK")) {
+               in_pack = 1;
+               sideband = *buf == '\1';
+               packet_trace_pack(buf, len, sideband);
+
+               /*
+                * Make a note in the human-readable trace that the pack data
+                * started.
+                */
+               buf = "PACK ...";
+               len = strlen(buf);
+       }
 
        if (!trace_want(&trace_packet))
                return;
@@ -24,22 +59,15 @@ static void packet_trace(const char *buf, unsigned int len, int write)
        strbuf_addf(&out, "packet: %12s%c ",
                    packet_trace_prefix, write ? '>' : '<');
 
-       if ((len >= 4 && starts_with(buf, "PACK")) ||
-           (len >= 5 && starts_with(buf+1, "PACK"))) {
-               strbuf_addstr(&out, "PACK ...");
-               trace_disable(&trace_packet);
-       }
-       else {
-               /* XXX we should really handle printable utf8 */
-               for (i = 0; i < len; i++) {
-                       /* suppress newlines */
-                       if (buf[i] == '\n')
-                               continue;
-                       if (buf[i] >= 0x20 && buf[i] <= 0x7e)
-                               strbuf_addch(&out, buf[i]);
-                       else
-                               strbuf_addf(&out, "\\%o", buf[i]);
-               }
+       /* XXX we should really handle printable utf8 */
+       for (i = 0; i < len; i++) {
+               /* suppress newlines */
+               if (buf[i] == '\n')
+                       continue;
+               if (buf[i] >= 0x20 && buf[i] <= 0x7e)
+                       strbuf_addch(&out, buf[i]);
+               else
+                       strbuf_addf(&out, "\\%o", buf[i]);
        }
 
        strbuf_addch(&out, '\n');
index 7b493041814dc8514bde0778497d39868c8455d7..151c2ae3128bb7c1a0ae13166539cb79e6edaeb8 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -399,7 +399,7 @@ static void add_rfc2047(struct strbuf *sb, const char *line, size_t len,
 }
 
 const char *show_ident_date(const struct ident_split *ident,
-                           enum date_mode mode)
+                           const struct date_mode *mode)
 {
        unsigned long date = 0;
        long tz = 0;
@@ -489,15 +489,15 @@ void pp_user_info(struct pretty_print_context *pp,
        switch (pp->fmt) {
        case CMIT_FMT_MEDIUM:
                strbuf_addf(sb, "Date:   %s\n",
-                           show_ident_date(&ident, pp->date_mode));
+                           show_ident_date(&ident, &pp->date_mode));
                break;
        case CMIT_FMT_EMAIL:
                strbuf_addf(sb, "Date: %s\n",
-                           show_ident_date(&ident, DATE_RFC2822));
+                           show_ident_date(&ident, DATE_MODE(RFC2822)));
                break;
        case CMIT_FMT_FULLER:
                strbuf_addf(sb, "%sDate: %s\n", what,
-                           show_ident_date(&ident, pp->date_mode));
+                           show_ident_date(&ident, &pp->date_mode));
                break;
        default:
                /* notin' */
@@ -671,7 +671,8 @@ static int mailmap_name(const char **email, size_t *email_len,
 }
 
 static size_t format_person_part(struct strbuf *sb, char part,
-                                const char *msg, int len, enum date_mode dmode)
+                                const char *msg, int len,
+                                const struct date_mode *dmode)
 {
        /* currently all placeholders have same length */
        const int placeholder_len = 2;
@@ -711,16 +712,16 @@ static size_t format_person_part(struct strbuf *sb, char part,
                strbuf_addstr(sb, show_ident_date(&s, dmode));
                return placeholder_len;
        case 'D':       /* date, RFC2822 style */
-               strbuf_addstr(sb, show_ident_date(&s, DATE_RFC2822));
+               strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(RFC2822)));
                return placeholder_len;
        case 'r':       /* date, relative */
-               strbuf_addstr(sb, show_ident_date(&s, DATE_RELATIVE));
+               strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(RELATIVE)));
                return placeholder_len;
        case 'i':       /* date, ISO 8601-like */
-               strbuf_addstr(sb, show_ident_date(&s, DATE_ISO8601));
+               strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(ISO8601)));
                return placeholder_len;
        case 'I':       /* date, ISO 8601 strict */
-               strbuf_addstr(sb, show_ident_date(&s, DATE_ISO8601_STRICT));
+               strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(ISO8601_STRICT)));
                return placeholder_len;
        }
 
@@ -933,7 +934,7 @@ static void rewrap_message_tail(struct strbuf *sb,
 static int format_reflog_person(struct strbuf *sb,
                                char part,
                                struct reflog_walk_info *log,
-                               enum date_mode dmode)
+                               const struct date_mode *dmode)
 {
        const char *ident;
 
@@ -1185,7 +1186,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
                        if (c->pretty_ctx->reflog_info)
                                get_reflog_selector(sb,
                                                    c->pretty_ctx->reflog_info,
-                                                   c->pretty_ctx->date_mode,
+                                                   &c->pretty_ctx->date_mode,
                                                    c->pretty_ctx->date_mode_explicit,
                                                    (placeholder[1] == 'd'));
                        return 2;
@@ -1200,7 +1201,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
                        return format_reflog_person(sb,
                                                    placeholder[1],
                                                    c->pretty_ctx->reflog_info,
-                                                   c->pretty_ctx->date_mode);
+                                                   &c->pretty_ctx->date_mode);
                }
                return 0;       /* unknown %g placeholder */
        case 'N':
@@ -1251,11 +1252,11 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
        case 'a':       /* author ... */
                return format_person_part(sb, placeholder[1],
                                   msg + c->author.off, c->author.len,
-                                  c->pretty_ctx->date_mode);
+                                  &c->pretty_ctx->date_mode);
        case 'c':       /* committer ... */
                return format_person_part(sb, placeholder[1],
                                   msg + c->committer.off, c->committer.len,
-                                  c->pretty_ctx->date_mode);
+                                  &c->pretty_ctx->date_mode);
        case 'e':       /* encoding */
                if (c->commit_encoding)
                        strbuf_addstr(sb, c->commit_encoding);
diff --git a/ref-filter.c b/ref-filter.c
new file mode 100644 (file)
index 0000000..f38dee4
--- /dev/null
@@ -0,0 +1,1106 @@
+#include "builtin.h"
+#include "cache.h"
+#include "parse-options.h"
+#include "refs.h"
+#include "wildmatch.h"
+#include "commit.h"
+#include "remote.h"
+#include "color.h"
+#include "tag.h"
+#include "quote.h"
+#include "ref-filter.h"
+
+typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
+
+static struct {
+       const char *name;
+       cmp_type cmp_type;
+} valid_atom[] = {
+       { "refname" },
+       { "objecttype" },
+       { "objectsize", FIELD_ULONG },
+       { "objectname" },
+       { "tree" },
+       { "parent" },
+       { "numparent", FIELD_ULONG },
+       { "object" },
+       { "type" },
+       { "tag" },
+       { "author" },
+       { "authorname" },
+       { "authoremail" },
+       { "authordate", FIELD_TIME },
+       { "committer" },
+       { "committername" },
+       { "committeremail" },
+       { "committerdate", FIELD_TIME },
+       { "tagger" },
+       { "taggername" },
+       { "taggeremail" },
+       { "taggerdate", FIELD_TIME },
+       { "creator" },
+       { "creatordate", FIELD_TIME },
+       { "subject" },
+       { "body" },
+       { "contents" },
+       { "contents:subject" },
+       { "contents:body" },
+       { "contents:signature" },
+       { "upstream" },
+       { "push" },
+       { "symref" },
+       { "flag" },
+       { "HEAD" },
+       { "color" },
+};
+
+/*
+ * An atom is a valid field atom listed above, possibly prefixed with
+ * a "*" to denote deref_tag().
+ *
+ * We parse given format string and sort specifiers, and make a list
+ * of properties that we need to extract out of objects.  ref_array_item
+ * structure will hold an array of values extracted that can be
+ * indexed with the "atom number", which is an index into this
+ * array.
+ */
+static const char **used_atom;
+static cmp_type *used_atom_type;
+static int used_atom_cnt, need_tagged, need_symref;
+static int need_color_reset_at_eol;
+
+/*
+ * Used to parse format string and sort specifiers
+ */
+int parse_ref_filter_atom(const char *atom, const char *ep)
+{
+       const char *sp;
+       int i, at;
+
+       sp = atom;
+       if (*sp == '*' && sp < ep)
+               sp++; /* deref */
+       if (ep <= sp)
+               die("malformed field name: %.*s", (int)(ep-atom), atom);
+
+       /* Do we have the atom already used elsewhere? */
+       for (i = 0; i < used_atom_cnt; i++) {
+               int len = strlen(used_atom[i]);
+               if (len == ep - atom && !memcmp(used_atom[i], atom, len))
+                       return i;
+       }
+
+       /* Is the atom a valid one? */
+       for (i = 0; i < ARRAY_SIZE(valid_atom); i++) {
+               int len = strlen(valid_atom[i].name);
+               /*
+                * If the atom name has a colon, strip it and everything after
+                * it off - it specifies the format for this entry, and
+                * shouldn't be used for checking against the valid_atom
+                * table.
+                */
+               const char *formatp = strchr(sp, ':');
+               if (!formatp || ep < formatp)
+                       formatp = ep;
+               if (len == formatp - sp && !memcmp(valid_atom[i].name, sp, len))
+                       break;
+       }
+
+       if (ARRAY_SIZE(valid_atom) <= i)
+               die("unknown field name: %.*s", (int)(ep-atom), atom);
+
+       /* Add it in, including the deref prefix */
+       at = used_atom_cnt;
+       used_atom_cnt++;
+       REALLOC_ARRAY(used_atom, used_atom_cnt);
+       REALLOC_ARRAY(used_atom_type, used_atom_cnt);
+       used_atom[at] = xmemdupz(atom, ep - atom);
+       used_atom_type[at] = valid_atom[i].cmp_type;
+       if (*atom == '*')
+               need_tagged = 1;
+       if (!strcmp(used_atom[at], "symref"))
+               need_symref = 1;
+       return at;
+}
+
+/*
+ * In a format string, find the next occurrence of %(atom).
+ */
+static const char *find_next(const char *cp)
+{
+       while (*cp) {
+               if (*cp == '%') {
+                       /*
+                        * %( is the start of an atom;
+                        * %% is a quoted per-cent.
+                        */
+                       if (cp[1] == '(')
+                               return cp;
+                       else if (cp[1] == '%')
+                               cp++; /* skip over two % */
+                       /* otherwise this is a singleton, literal % */
+               }
+               cp++;
+       }
+       return NULL;
+}
+
+/*
+ * Make sure the format string is well formed, and parse out
+ * the used atoms.
+ */
+int verify_ref_format(const char *format)
+{
+       const char *cp, *sp;
+
+       need_color_reset_at_eol = 0;
+       for (cp = format; *cp && (sp = find_next(cp)); ) {
+               const char *color, *ep = strchr(sp, ')');
+               int at;
+
+               if (!ep)
+                       return error("malformed format string %s", sp);
+               /* sp points at "%(" and ep points at the closing ")" */
+               at = parse_ref_filter_atom(sp + 2, ep);
+               cp = ep + 1;
+
+               if (skip_prefix(used_atom[at], "color:", &color))
+                       need_color_reset_at_eol = !!strcmp(color, "reset");
+       }
+       return 0;
+}
+
+/*
+ * Given an object name, read the object data and size, and return a
+ * "struct object".  If the object data we are returning is also borrowed
+ * by the "struct object" representation, set *eaten as well---it is a
+ * signal from parse_object_buffer to us not to free the buffer.
+ */
+static void *get_obj(const unsigned char *sha1, struct object **obj, unsigned long *sz, int *eaten)
+{
+       enum object_type type;
+       void *buf = read_sha1_file(sha1, &type, sz);
+
+       if (buf)
+               *obj = parse_object_buffer(sha1, type, *sz, buf, eaten);
+       else
+               *obj = NULL;
+       return buf;
+}
+
+static int grab_objectname(const char *name, const unsigned char *sha1,
+                           struct atom_value *v)
+{
+       if (!strcmp(name, "objectname")) {
+               char *s = xmalloc(41);
+               strcpy(s, sha1_to_hex(sha1));
+               v->s = s;
+               return 1;
+       }
+       if (!strcmp(name, "objectname:short")) {
+               v->s = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV));
+               return 1;
+       }
+       return 0;
+}
+
+/* See grab_values */
+static void grab_common_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+       int i;
+
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+               if (!strcmp(name, "objecttype"))
+                       v->s = typename(obj->type);
+               else if (!strcmp(name, "objectsize")) {
+                       char *s = xmalloc(40);
+                       sprintf(s, "%lu", sz);
+                       v->ul = sz;
+                       v->s = s;
+               }
+               else if (deref)
+                       grab_objectname(name, obj->sha1, v);
+       }
+}
+
+/* See grab_values */
+static void grab_tag_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+       int i;
+       struct tag *tag = (struct tag *) obj;
+
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+               if (!strcmp(name, "tag"))
+                       v->s = tag->tag;
+               else if (!strcmp(name, "type") && tag->tagged)
+                       v->s = typename(tag->tagged->type);
+               else if (!strcmp(name, "object") && tag->tagged) {
+                       char *s = xmalloc(41);
+                       strcpy(s, sha1_to_hex(tag->tagged->sha1));
+                       v->s = s;
+               }
+       }
+}
+
+/* See grab_values */
+static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+       int i;
+       struct commit *commit = (struct commit *) obj;
+
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+               if (!strcmp(name, "tree")) {
+                       char *s = xmalloc(41);
+                       strcpy(s, sha1_to_hex(commit->tree->object.sha1));
+                       v->s = s;
+               }
+               if (!strcmp(name, "numparent")) {
+                       char *s = xmalloc(40);
+                       v->ul = commit_list_count(commit->parents);
+                       sprintf(s, "%lu", v->ul);
+                       v->s = s;
+               }
+               else if (!strcmp(name, "parent")) {
+                       int num = commit_list_count(commit->parents);
+                       int i;
+                       struct commit_list *parents;
+                       char *s = xmalloc(41 * num + 1);
+                       v->s = s;
+                       for (i = 0, parents = commit->parents;
+                            parents;
+                            parents = parents->next, i = i + 41) {
+                               struct commit *parent = parents->item;
+                               strcpy(s+i, sha1_to_hex(parent->object.sha1));
+                               if (parents->next)
+                                       s[i+40] = ' ';
+                       }
+                       if (!i)
+                               *s = '\0';
+               }
+       }
+}
+
+static const char *find_wholine(const char *who, int wholen, const char *buf, unsigned long sz)
+{
+       const char *eol;
+       while (*buf) {
+               if (!strncmp(buf, who, wholen) &&
+                   buf[wholen] == ' ')
+                       return buf + wholen + 1;
+               eol = strchr(buf, '\n');
+               if (!eol)
+                       return "";
+               eol++;
+               if (*eol == '\n')
+                       return ""; /* end of header */
+               buf = eol;
+       }
+       return "";
+}
+
+static const char *copy_line(const char *buf)
+{
+       const char *eol = strchrnul(buf, '\n');
+       return xmemdupz(buf, eol - buf);
+}
+
+static const char *copy_name(const char *buf)
+{
+       const char *cp;
+       for (cp = buf; *cp && *cp != '\n'; cp++) {
+               if (!strncmp(cp, " <", 2))
+                       return xmemdupz(buf, cp - buf);
+       }
+       return "";
+}
+
+static const char *copy_email(const char *buf)
+{
+       const char *email = strchr(buf, '<');
+       const char *eoemail;
+       if (!email)
+               return "";
+       eoemail = strchr(email, '>');
+       if (!eoemail)
+               return "";
+       return xmemdupz(email, eoemail + 1 - email);
+}
+
+static char *copy_subject(const char *buf, unsigned long len)
+{
+       char *r = xmemdupz(buf, len);
+       int i;
+
+       for (i = 0; i < len; i++)
+               if (r[i] == '\n')
+                       r[i] = ' ';
+
+       return r;
+}
+
+static void grab_date(const char *buf, struct atom_value *v, const char *atomname)
+{
+       const char *eoemail = strstr(buf, "> ");
+       char *zone;
+       unsigned long timestamp;
+       long tz;
+       struct date_mode date_mode = { DATE_NORMAL };
+       const char *formatp;
+
+       /*
+        * We got here because atomname ends in "date" or "date<something>";
+        * it's not possible that <something> is not ":<format>" because
+        * parse_ref_filter_atom() wouldn't have allowed it, so we can assume that no
+        * ":" means no format is specified, and use the default.
+        */
+       formatp = strchr(atomname, ':');
+       if (formatp != NULL) {
+               formatp++;
+               parse_date_format(formatp, &date_mode);
+       }
+
+       if (!eoemail)
+               goto bad;
+       timestamp = strtoul(eoemail + 2, &zone, 10);
+       if (timestamp == ULONG_MAX)
+               goto bad;
+       tz = strtol(zone, NULL, 10);
+       if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE)
+               goto bad;
+       v->s = xstrdup(show_date(timestamp, tz, &date_mode));
+       v->ul = timestamp;
+       return;
+ bad:
+       v->s = "";
+       v->ul = 0;
+}
+
+/* See grab_values */
+static void grab_person(const char *who, struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+       int i;
+       int wholen = strlen(who);
+       const char *wholine = NULL;
+
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+               if (strncmp(who, name, wholen))
+                       continue;
+               if (name[wholen] != 0 &&
+                   strcmp(name + wholen, "name") &&
+                   strcmp(name + wholen, "email") &&
+                   !starts_with(name + wholen, "date"))
+                       continue;
+               if (!wholine)
+                       wholine = find_wholine(who, wholen, buf, sz);
+               if (!wholine)
+                       return; /* no point looking for it */
+               if (name[wholen] == 0)
+                       v->s = copy_line(wholine);
+               else if (!strcmp(name + wholen, "name"))
+                       v->s = copy_name(wholine);
+               else if (!strcmp(name + wholen, "email"))
+                       v->s = copy_email(wholine);
+               else if (starts_with(name + wholen, "date"))
+                       grab_date(wholine, v, name);
+       }
+
+       /*
+        * For a tag or a commit object, if "creator" or "creatordate" is
+        * requested, do something special.
+        */
+       if (strcmp(who, "tagger") && strcmp(who, "committer"))
+               return; /* "author" for commit object is not wanted */
+       if (!wholine)
+               wholine = find_wholine(who, wholen, buf, sz);
+       if (!wholine)
+               return;
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+
+               if (starts_with(name, "creatordate"))
+                       grab_date(wholine, v, name);
+               else if (!strcmp(name, "creator"))
+                       v->s = copy_line(wholine);
+       }
+}
+
+static void find_subpos(const char *buf, unsigned long sz,
+                       const char **sub, unsigned long *sublen,
+                       const char **body, unsigned long *bodylen,
+                       unsigned long *nonsiglen,
+                       const char **sig, unsigned long *siglen)
+{
+       const char *eol;
+       /* skip past header until we hit empty line */
+       while (*buf && *buf != '\n') {
+               eol = strchrnul(buf, '\n');
+               if (*eol)
+                       eol++;
+               buf = eol;
+       }
+       /* skip any empty lines */
+       while (*buf == '\n')
+               buf++;
+
+       /* parse signature first; we might not even have a subject line */
+       *sig = buf + parse_signature(buf, strlen(buf));
+       *siglen = strlen(*sig);
+
+       /* subject is first non-empty line */
+       *sub = buf;
+       /* subject goes to first empty line */
+       while (buf < *sig && *buf && *buf != '\n') {
+               eol = strchrnul(buf, '\n');
+               if (*eol)
+                       eol++;
+               buf = eol;
+       }
+       *sublen = buf - *sub;
+       /* drop trailing newline, if present */
+       if (*sublen && (*sub)[*sublen - 1] == '\n')
+               *sublen -= 1;
+
+       /* skip any empty lines */
+       while (*buf == '\n')
+               buf++;
+       *body = buf;
+       *bodylen = strlen(buf);
+       *nonsiglen = *sig - buf;
+}
+
+/* See grab_values */
+static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+       int i;
+       const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;
+       unsigned long sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0;
+
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+               if (strcmp(name, "subject") &&
+                   strcmp(name, "body") &&
+                   strcmp(name, "contents") &&
+                   strcmp(name, "contents:subject") &&
+                   strcmp(name, "contents:body") &&
+                   strcmp(name, "contents:signature"))
+                       continue;
+               if (!subpos)
+                       find_subpos(buf, sz,
+                                   &subpos, &sublen,
+                                   &bodypos, &bodylen, &nonsiglen,
+                                   &sigpos, &siglen);
+
+               if (!strcmp(name, "subject"))
+                       v->s = copy_subject(subpos, sublen);
+               else if (!strcmp(name, "contents:subject"))
+                       v->s = copy_subject(subpos, sublen);
+               else if (!strcmp(name, "body"))
+                       v->s = xmemdupz(bodypos, bodylen);
+               else if (!strcmp(name, "contents:body"))
+                       v->s = xmemdupz(bodypos, nonsiglen);
+               else if (!strcmp(name, "contents:signature"))
+                       v->s = xmemdupz(sigpos, siglen);
+               else if (!strcmp(name, "contents"))
+                       v->s = xstrdup(subpos);
+       }
+}
+
+/*
+ * We want to have empty print-string for field requests
+ * that do not apply (e.g. "authordate" for a tag object)
+ */
+static void fill_missing_values(struct atom_value *val)
+{
+       int i;
+       for (i = 0; i < used_atom_cnt; i++) {
+               struct atom_value *v = &val[i];
+               if (v->s == NULL)
+                       v->s = "";
+       }
+}
+
+/*
+ * val is a list of atom_value to hold returned values.  Extract
+ * the values for atoms in used_atom array out of (obj, buf, sz).
+ * when deref is false, (obj, buf, sz) is the object that is
+ * pointed at by the ref itself; otherwise it is the object the
+ * ref (which is a tag) refers to.
+ */
+static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+       grab_common_values(val, deref, obj, buf, sz);
+       switch (obj->type) {
+       case OBJ_TAG:
+               grab_tag_values(val, deref, obj, buf, sz);
+               grab_sub_body_contents(val, deref, obj, buf, sz);
+               grab_person("tagger", val, deref, obj, buf, sz);
+               break;
+       case OBJ_COMMIT:
+               grab_commit_values(val, deref, obj, buf, sz);
+               grab_sub_body_contents(val, deref, obj, buf, sz);
+               grab_person("author", val, deref, obj, buf, sz);
+               grab_person("committer", val, deref, obj, buf, sz);
+               break;
+       case OBJ_TREE:
+               /* grab_tree_values(val, deref, obj, buf, sz); */
+               break;
+       case OBJ_BLOB:
+               /* grab_blob_values(val, deref, obj, buf, sz); */
+               break;
+       default:
+               die("Eh?  Object of type %d?", obj->type);
+       }
+}
+
+static inline char *copy_advance(char *dst, const char *src)
+{
+       while (*src)
+               *dst++ = *src++;
+       return dst;
+}
+
+/*
+ * Parse the object referred by ref, and grab needed value.
+ */
+static void populate_value(struct ref_array_item *ref)
+{
+       void *buf;
+       struct object *obj;
+       int eaten, i;
+       unsigned long size;
+       const unsigned char *tagged;
+
+       ref->value = xcalloc(used_atom_cnt, sizeof(struct atom_value));
+
+       if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) {
+               unsigned char unused1[20];
+               ref->symref = resolve_refdup(ref->refname, RESOLVE_REF_READING,
+                                            unused1, NULL);
+               if (!ref->symref)
+                       ref->symref = "";
+       }
+
+       /* Fill in specials first */
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &ref->value[i];
+               int deref = 0;
+               const char *refname;
+               const char *formatp;
+               struct branch *branch = NULL;
+
+               if (*name == '*') {
+                       deref = 1;
+                       name++;
+               }
+
+               if (starts_with(name, "refname"))
+                       refname = ref->refname;
+               else if (starts_with(name, "symref"))
+                       refname = ref->symref ? ref->symref : "";
+               else if (starts_with(name, "upstream")) {
+                       const char *branch_name;
+                       /* only local branches may have an upstream */
+                       if (!skip_prefix(ref->refname, "refs/heads/",
+                                        &branch_name))
+                               continue;
+                       branch = branch_get(branch_name);
+
+                       refname = branch_get_upstream(branch, NULL);
+                       if (!refname)
+                               continue;
+               } else if (starts_with(name, "push")) {
+                       const char *branch_name;
+                       if (!skip_prefix(ref->refname, "refs/heads/",
+                                        &branch_name))
+                               continue;
+                       branch = branch_get(branch_name);
+
+                       refname = branch_get_push(branch, NULL);
+                       if (!refname)
+                               continue;
+               } else if (starts_with(name, "color:")) {
+                       char color[COLOR_MAXLEN] = "";
+
+                       if (color_parse(name + 6, color) < 0)
+                               die(_("unable to parse format"));
+                       v->s = xstrdup(color);
+                       continue;
+               } else if (!strcmp(name, "flag")) {
+                       char buf[256], *cp = buf;
+                       if (ref->flag & REF_ISSYMREF)
+                               cp = copy_advance(cp, ",symref");
+                       if (ref->flag & REF_ISPACKED)
+                               cp = copy_advance(cp, ",packed");
+                       if (cp == buf)
+                               v->s = "";
+                       else {
+                               *cp = '\0';
+                               v->s = xstrdup(buf + 1);
+                       }
+                       continue;
+               } else if (!deref && grab_objectname(name, ref->objectname, v)) {
+                       continue;
+               } else if (!strcmp(name, "HEAD")) {
+                       const char *head;
+                       unsigned char sha1[20];
+
+                       head = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
+                                                 sha1, NULL);
+                       if (!strcmp(ref->refname, head))
+                               v->s = "*";
+                       else
+                               v->s = " ";
+                       continue;
+               } else
+                       continue;
+
+               formatp = strchr(name, ':');
+               if (formatp) {
+                       int num_ours, num_theirs;
+
+                       formatp++;
+                       if (!strcmp(formatp, "short"))
+                               refname = shorten_unambiguous_ref(refname,
+                                                     warn_ambiguous_refs);
+                       else if (!strcmp(formatp, "track") &&
+                                (starts_with(name, "upstream") ||
+                                 starts_with(name, "push"))) {
+                               char buf[40];
+
+                               if (stat_tracking_info(branch, &num_ours,
+                                                      &num_theirs, NULL))
+                                       continue;
+
+                               if (!num_ours && !num_theirs)
+                                       v->s = "";
+                               else if (!num_ours) {
+                                       sprintf(buf, "[behind %d]", num_theirs);
+                                       v->s = xstrdup(buf);
+                               } else if (!num_theirs) {
+                                       sprintf(buf, "[ahead %d]", num_ours);
+                                       v->s = xstrdup(buf);
+                               } else {
+                                       sprintf(buf, "[ahead %d, behind %d]",
+                                               num_ours, num_theirs);
+                                       v->s = xstrdup(buf);
+                               }
+                               continue;
+                       } else if (!strcmp(formatp, "trackshort") &&
+                                  (starts_with(name, "upstream") ||
+                                   starts_with(name, "push"))) {
+                               assert(branch);
+
+                               if (stat_tracking_info(branch, &num_ours,
+                                                       &num_theirs, NULL))
+                                       continue;
+
+                               if (!num_ours && !num_theirs)
+                                       v->s = "=";
+                               else if (!num_ours)
+                                       v->s = "<";
+                               else if (!num_theirs)
+                                       v->s = ">";
+                               else
+                                       v->s = "<>";
+                               continue;
+                       } else
+                               die("unknown %.*s format %s",
+                                   (int)(formatp - name), name, formatp);
+               }
+
+               if (!deref)
+                       v->s = refname;
+               else {
+                       int len = strlen(refname);
+                       char *s = xmalloc(len + 4);
+                       sprintf(s, "%s^{}", refname);
+                       v->s = s;
+               }
+       }
+
+       for (i = 0; i < used_atom_cnt; i++) {
+               struct atom_value *v = &ref->value[i];
+               if (v->s == NULL)
+                       goto need_obj;
+       }
+       return;
+
+ need_obj:
+       buf = get_obj(ref->objectname, &obj, &size, &eaten);
+       if (!buf)
+               die("missing object %s for %s",
+                   sha1_to_hex(ref->objectname), ref->refname);
+       if (!obj)
+               die("parse_object_buffer failed on %s for %s",
+                   sha1_to_hex(ref->objectname), ref->refname);
+
+       grab_values(ref->value, 0, obj, buf, size);
+       if (!eaten)
+               free(buf);
+
+       /*
+        * If there is no atom that wants to know about tagged
+        * object, we are done.
+        */
+       if (!need_tagged || (obj->type != OBJ_TAG))
+               return;
+
+       /*
+        * If it is a tag object, see if we use a value that derefs
+        * the object, and if we do grab the object it refers to.
+        */
+       tagged = ((struct tag *)obj)->tagged->sha1;
+
+       /*
+        * NEEDSWORK: This derefs tag only once, which
+        * is good to deal with chains of trust, but
+        * is not consistent with what deref_tag() does
+        * which peels the onion to the core.
+        */
+       buf = get_obj(tagged, &obj, &size, &eaten);
+       if (!buf)
+               die("missing object %s for %s",
+                   sha1_to_hex(tagged), ref->refname);
+       if (!obj)
+               die("parse_object_buffer failed on %s for %s",
+                   sha1_to_hex(tagged), ref->refname);
+       grab_values(ref->value, 1, obj, buf, size);
+       if (!eaten)
+               free(buf);
+}
+
+/*
+ * Given a ref, return the value for the atom.  This lazily gets value
+ * out of the object by calling populate value.
+ */
+static void get_ref_atom_value(struct ref_array_item *ref, int atom, struct atom_value **v)
+{
+       if (!ref->value) {
+               populate_value(ref);
+               fill_missing_values(ref->value);
+       }
+       *v = &ref->value[atom];
+}
+
+/*
+ * Return 1 if the refname matches one of the patterns, otherwise 0.
+ * A pattern can be path prefix (e.g. a refname "refs/heads/master"
+ * matches a pattern "refs/heads/") or a wildcard (e.g. the same ref
+ * matches "refs/heads/m*",too).
+ */
+static int match_name_as_path(const char **pattern, const char *refname)
+{
+       int namelen = strlen(refname);
+       for (; *pattern; pattern++) {
+               const char *p = *pattern;
+               int plen = strlen(p);
+
+               if ((plen <= namelen) &&
+                   !strncmp(refname, p, plen) &&
+                   (refname[plen] == '\0' ||
+                    refname[plen] == '/' ||
+                    p[plen-1] == '/'))
+                       return 1;
+               if (!wildmatch(p, refname, WM_PATHNAME, NULL))
+                       return 1;
+       }
+       return 0;
+}
+
+/* Allocate space for a new ref_array_item and copy the objectname and flag to it */
+static struct ref_array_item *new_ref_array_item(const char *refname,
+                                                const unsigned char *objectname,
+                                                int flag)
+{
+       size_t len = strlen(refname);
+       struct ref_array_item *ref = xcalloc(1, sizeof(struct ref_array_item) + len + 1);
+       memcpy(ref->refname, refname, len);
+       ref->refname[len] = '\0';
+       hashcpy(ref->objectname, objectname);
+       ref->flag = flag;
+
+       return ref;
+}
+
+/*
+ * A call-back given to for_each_ref().  Filter refs and keep them for
+ * later object processing.
+ */
+static int ref_filter_handler(const char *refname, const struct object_id *oid, int flag, void *cb_data)
+{
+       struct ref_filter_cbdata *ref_cbdata = cb_data;
+       struct ref_filter *filter = ref_cbdata->filter;
+       struct ref_array_item *ref;
+
+       if (flag & REF_BAD_NAME) {
+               warning("ignoring ref with broken name %s", refname);
+               return 0;
+       }
+
+       if (flag & REF_ISBROKEN) {
+               warning("ignoring broken ref %s", refname);
+               return 0;
+       }
+
+       if (*filter->name_patterns && !match_name_as_path(filter->name_patterns, refname))
+               return 0;
+
+       /*
+        * We do not open the object yet; sort may only need refname
+        * to do its job and the resulting list may yet to be pruned
+        * by maxcount logic.
+        */
+       ref = new_ref_array_item(refname, oid->hash, flag);
+
+       REALLOC_ARRAY(ref_cbdata->array->items, ref_cbdata->array->nr + 1);
+       ref_cbdata->array->items[ref_cbdata->array->nr++] = ref;
+       return 0;
+}
+
+/*  Free memory allocated for a ref_array_item */
+static void free_array_item(struct ref_array_item *item)
+{
+       free((char *)item->symref);
+       free(item);
+}
+
+/* Free all memory allocated for ref_array */
+void ref_array_clear(struct ref_array *array)
+{
+       int i;
+
+       for (i = 0; i < array->nr; i++)
+               free_array_item(array->items[i]);
+       free(array->items);
+       array->items = NULL;
+       array->nr = array->alloc = 0;
+}
+
+/*
+ * API for filtering a set of refs. Based on the type of refs the user
+ * has requested, we iterate through those refs and apply filters
+ * as per the given ref_filter structure and finally store the
+ * filtered refs in the ref_array structure.
+ */
+int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int type)
+{
+       struct ref_filter_cbdata ref_cbdata;
+
+       ref_cbdata.array = array;
+       ref_cbdata.filter = filter;
+
+       if (type & (FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN))
+               return for_each_rawref(ref_filter_handler, &ref_cbdata);
+       else if (type & FILTER_REFS_ALL)
+               return for_each_ref(ref_filter_handler, &ref_cbdata);
+       else
+               die("filter_refs: invalid type");
+       return 0;
+}
+
+static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, struct ref_array_item *b)
+{
+       struct atom_value *va, *vb;
+       int cmp;
+       cmp_type cmp_type = used_atom_type[s->atom];
+
+       get_ref_atom_value(a, s->atom, &va);
+       get_ref_atom_value(b, s->atom, &vb);
+       switch (cmp_type) {
+       case FIELD_STR:
+               cmp = strcmp(va->s, vb->s);
+               break;
+       default:
+               if (va->ul < vb->ul)
+                       cmp = -1;
+               else if (va->ul == vb->ul)
+                       cmp = 0;
+               else
+                       cmp = 1;
+               break;
+       }
+       return (s->reverse) ? -cmp : cmp;
+}
+
+static struct ref_sorting *ref_sorting;
+static int compare_refs(const void *a_, const void *b_)
+{
+       struct ref_array_item *a = *((struct ref_array_item **)a_);
+       struct ref_array_item *b = *((struct ref_array_item **)b_);
+       struct ref_sorting *s;
+
+       for (s = ref_sorting; s; s = s->next) {
+               int cmp = cmp_ref_sorting(s, a, b);
+               if (cmp)
+                       return cmp;
+       }
+       return 0;
+}
+
+void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array)
+{
+       ref_sorting = sorting;
+       qsort(array->items, array->nr, sizeof(struct ref_array_item *), compare_refs);
+}
+
+static void print_value(struct atom_value *v, int quote_style)
+{
+       struct strbuf sb = STRBUF_INIT;
+       switch (quote_style) {
+       case QUOTE_NONE:
+               fputs(v->s, stdout);
+               break;
+       case QUOTE_SHELL:
+               sq_quote_buf(&sb, v->s);
+               break;
+       case QUOTE_PERL:
+               perl_quote_buf(&sb, v->s);
+               break;
+       case QUOTE_PYTHON:
+               python_quote_buf(&sb, v->s);
+               break;
+       case QUOTE_TCL:
+               tcl_quote_buf(&sb, v->s);
+               break;
+       }
+       if (quote_style != QUOTE_NONE) {
+               fputs(sb.buf, stdout);
+               strbuf_release(&sb);
+       }
+}
+
+static int hex1(char ch)
+{
+       if ('0' <= ch && ch <= '9')
+               return ch - '0';
+       else if ('a' <= ch && ch <= 'f')
+               return ch - 'a' + 10;
+       else if ('A' <= ch && ch <= 'F')
+               return ch - 'A' + 10;
+       return -1;
+}
+static int hex2(const char *cp)
+{
+       if (cp[0] && cp[1])
+               return (hex1(cp[0]) << 4) | hex1(cp[1]);
+       else
+               return -1;
+}
+
+static void emit(const char *cp, const char *ep)
+{
+       while (*cp && (!ep || cp < ep)) {
+               if (*cp == '%') {
+                       if (cp[1] == '%')
+                               cp++;
+                       else {
+                               int ch = hex2(cp + 1);
+                               if (0 <= ch) {
+                                       putchar(ch);
+                                       cp += 3;
+                                       continue;
+                               }
+                       }
+               }
+               putchar(*cp);
+               cp++;
+       }
+}
+
+void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style)
+{
+       const char *cp, *sp, *ep;
+
+       for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
+               struct atom_value *atomv;
+
+               ep = strchr(sp, ')');
+               if (cp < sp)
+                       emit(cp, sp);
+               get_ref_atom_value(info, parse_ref_filter_atom(sp + 2, ep), &atomv);
+               print_value(atomv, quote_style);
+       }
+       if (*cp) {
+               sp = cp + strlen(cp);
+               emit(cp, sp);
+       }
+       if (need_color_reset_at_eol) {
+               struct atom_value resetv;
+               char color[COLOR_MAXLEN] = "";
+
+               if (color_parse("reset", color) < 0)
+                       die("BUG: couldn't parse 'reset' as a color");
+               resetv.s = color;
+               print_value(&resetv, quote_style);
+       }
+       putchar('\n');
+}
+
+/*  If no sorting option is given, use refname to sort as default */
+struct ref_sorting *ref_default_sorting(void)
+{
+       static const char cstr_name[] = "refname";
+
+       struct ref_sorting *sorting = xcalloc(1, sizeof(*sorting));
+
+       sorting->next = NULL;
+       sorting->atom = parse_ref_filter_atom(cstr_name, cstr_name + strlen(cstr_name));
+       return sorting;
+}
+
+int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset)
+{
+       struct ref_sorting **sorting_tail = opt->value;
+       struct ref_sorting *s;
+       int len;
+
+       if (!arg) /* should --no-sort void the list ? */
+               return -1;
+
+       s = xcalloc(1, sizeof(*s));
+       s->next = *sorting_tail;
+       *sorting_tail = s;
+
+       if (*arg == '-') {
+               s->reverse = 1;
+               arg++;
+       }
+       len = strlen(arg);
+       s->atom = parse_ref_filter_atom(arg, arg+len);
+       return 0;
+}
diff --git a/ref-filter.h b/ref-filter.h
new file mode 100644 (file)
index 0000000..6997984
--- /dev/null
@@ -0,0 +1,74 @@
+#ifndef REF_FILTER_H
+#define REF_FILTER_H
+
+#include "sha1-array.h"
+#include "refs.h"
+#include "commit.h"
+#include "parse-options.h"
+
+/* Quoting styles */
+#define QUOTE_NONE 0
+#define QUOTE_SHELL 1
+#define QUOTE_PERL 2
+#define QUOTE_PYTHON 4
+#define QUOTE_TCL 8
+
+#define FILTER_REFS_INCLUDE_BROKEN 0x1
+#define FILTER_REFS_ALL 0x2
+
+struct atom_value {
+       const char *s;
+       unsigned long ul; /* used for sorting when not FIELD_STR */
+};
+
+struct ref_sorting {
+       struct ref_sorting *next;
+       int atom; /* index into used_atom array (internal) */
+       unsigned reverse : 1;
+};
+
+struct ref_array_item {
+       unsigned char objectname[20];
+       int flag;
+       const char *symref;
+       struct atom_value *value;
+       char refname[FLEX_ARRAY];
+};
+
+struct ref_array {
+       int nr, alloc;
+       struct ref_array_item **items;
+};
+
+struct ref_filter {
+       const char **name_patterns;
+};
+
+struct ref_filter_cbdata {
+       struct ref_array *array;
+       struct ref_filter *filter;
+};
+
+/*
+ * API for filtering a set of refs. Based on the type of refs the user
+ * has requested, we iterate through those refs and apply filters
+ * as per the given ref_filter structure and finally store the
+ * filtered refs in the ref_array structure.
+ */
+int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int type);
+/*  Clear all memory allocated to ref_array */
+void ref_array_clear(struct ref_array *array);
+/*  Parse format string and sort specifiers */
+int parse_ref_filter_atom(const char *atom, const char *ep);
+/*  Used to verify if the given format is correct and to parse out the used atoms */
+int verify_ref_format(const char *format);
+/*  Sort the given ref_array as per the ref_sorting provided */
+void ref_array_sort(struct ref_sorting *sort, struct ref_array *array);
+/*  Print the ref using the given format and quote_style */
+void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style);
+/*  Callback function for parsing the sort option */
+int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset);
+/*  Default sort option based on refname */
+struct ref_sorting *ref_default_sorting(void);
+
+#endif /*  REF_FILTER_H  */
index 222de762eb2b1893d63da838251cbad9c902880b..f8e743a23bef06bbf144339dc79f2186ec40805b 100644 (file)
@@ -249,7 +249,7 @@ void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit)
 
 void get_reflog_selector(struct strbuf *sb,
                         struct reflog_walk_info *reflog_info,
-                        enum date_mode dmode, int force_date,
+                        const struct date_mode *dmode, int force_date,
                         int shorten)
 {
        struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
@@ -311,7 +311,7 @@ const char *get_reflog_ident(struct reflog_walk_info *reflog_info)
 }
 
 void show_reflog_message(struct reflog_walk_info *reflog_info, int oneline,
-                        enum date_mode dmode, int force_date)
+                        const struct date_mode *dmode, int force_date)
 {
        if (reflog_info && reflog_info->last_commit_reflog) {
                struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
index a9bd60e32d24bf61cbc2ea879b5bfd0e0e5c5c61..27886f793e75de484b2a3c168e6024cf4139aae3 100644 (file)
@@ -11,13 +11,13 @@ extern int add_reflog_for_walk(struct reflog_walk_info *info,
 extern void fake_reflog_parent(struct reflog_walk_info *info,
                struct commit *commit);
 extern void show_reflog_message(struct reflog_walk_info *info, int,
-                               enum date_mode, int force_date);
+                               const struct date_mode *, int force_date);
 extern void get_reflog_message(struct strbuf *sb,
                struct reflog_walk_info *reflog_info);
 extern const char *get_reflog_ident(struct reflog_walk_info *reflog_info);
 extern void get_reflog_selector(struct strbuf *sb,
                struct reflog_walk_info *reflog_info,
-               enum date_mode dmode, int force_date,
+               const struct date_mode *dmode, int force_date,
                int shorten);
 
 #endif
diff --git a/refs.c b/refs.c
index 7ac05cf21a25802f8e16d45ef75e37b7de2cda8a..835801905b597769aa5f3b5dd216075c263e6df5 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -19,12 +19,14 @@ struct ref_lock {
  * 1: End-of-component
  * 2: ., look for a preceding . to reject .. in refs
  * 3: {, look for a preceding @ to reject @{ in refs
- * 4: A bad character: ASCII control characters, "~", "^", ":" or SP
+ * 4: A bad character: ASCII control characters, and
+ *    ":", "?", "[", "\", "^", "~", SP, or TAB
+ * 5: *, reject unless REFNAME_REFSPEC_PATTERN is set
  */
 static unsigned char refname_disposition[256] = {
        1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
        4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
-       4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 2, 1,
+       4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 2, 1,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 4, 0,
@@ -62,6 +64,11 @@ static unsigned char refname_disposition[256] = {
  */
 #define REF_NEEDS_COMMIT 0x20
 
+/*
+ * 0x40 is REF_FORCE_CREATE_REFLOG, so skip it if you're adding a
+ * value to ref_update::flags
+ */
+
 /*
  * Try to read one refname component from the front of refname.
  * Return the length of the component found, or -1 if the component is
@@ -70,12 +77,14 @@ static unsigned char refname_disposition[256] = {
  *
  * - any path component of it begins with ".", or
  * - it has double dots "..", or
- * - it has ASCII control character, "~", "^", ":" or SP, anywhere, or
- * - it ends with a "/".
- * - it ends with ".lock"
- * - it contains a "\" (backslash)
+ * - it has ASCII control characters, or
+ * - it has ":", "?", "[", "\", "^", "~", SP, or TAB anywhere, or
+ * - it has "*" anywhere unless REFNAME_REFSPEC_PATTERN is set, or
+ * - it ends with a "/", or
+ * - it ends with ".lock", or
+ * - it contains a "@{" portion
  */
-static int check_refname_component(const char *refname, int flags)
+static int check_refname_component(const char *refname, int *flags)
 {
        const char *cp;
        char last = '\0';
@@ -96,6 +105,16 @@ static int check_refname_component(const char *refname, int flags)
                        break;
                case 4:
                        return -1;
+               case 5:
+                       if (!(*flags & REFNAME_REFSPEC_PATTERN))
+                               return -1; /* refspec can't be a pattern */
+
+                       /*
+                        * Unset the pattern flag so that we only accept
+                        * a single asterisk for one side of refspec.
+                        */
+                       *flags &= ~ REFNAME_REFSPEC_PATTERN;
+                       break;
                }
                last = ch;
        }
@@ -120,18 +139,10 @@ int check_refname_format(const char *refname, int flags)
 
        while (1) {
                /* We are at the start of a path component. */
-               component_len = check_refname_component(refname, flags);
-               if (component_len <= 0) {
-                       if ((flags & REFNAME_REFSPEC_PATTERN) &&
-                                       refname[0] == '*' &&
-                                       (refname[1] == '\0' || refname[1] == '/')) {
-                               /* Accept one wildcard as a full refname component. */
-                               flags &= ~REFNAME_REFSPEC_PATTERN;
-                               component_len = 1;
-                       } else {
-                               return -1;
-                       }
-               }
+               component_len = check_refname_component(refname, &flags);
+               if (component_len <= 0)
+                       return -1;
+
                component_count++;
                if (refname[component_len] == '\0')
                        break;
@@ -1277,12 +1288,12 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)
  */
 static struct packed_ref_cache *get_packed_ref_cache(struct ref_cache *refs)
 {
-       const char *packed_refs_file;
+       char *packed_refs_file;
 
        if (*refs->name)
-               packed_refs_file = git_path_submodule(refs->name, "packed-refs");
+               packed_refs_file = git_pathdup_submodule(refs->name, "packed-refs");
        else
-               packed_refs_file = git_path("packed-refs");
+               packed_refs_file = git_pathdup("packed-refs");
 
        if (refs->packed &&
            !stat_validity_check(&refs->packed->validity, packed_refs_file))
@@ -1301,6 +1312,7 @@ static struct packed_ref_cache *get_packed_ref_cache(struct ref_cache *refs)
                        fclose(f);
                }
        }
+       free(packed_refs_file);
        return refs->packed;
 }
 
@@ -1314,7 +1326,13 @@ static struct ref_dir *get_packed_refs(struct ref_cache *refs)
        return get_packed_ref_dir(get_packed_ref_cache(refs));
 }
 
-void add_packed_ref(const char *refname, const unsigned char *sha1)
+/*
+ * Add a reference to the in-memory packed reference cache.  This may
+ * only be called while the packed-refs file is locked (see
+ * lock_packed_refs()).  To actually write the packed-refs file, call
+ * commit_packed_refs().
+ */
+static void add_packed_ref(const char *refname, const unsigned char *sha1)
 {
        struct packed_ref_cache *packed_ref_cache =
                get_packed_ref_cache(&ref_cache);
@@ -1334,19 +1352,23 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir)
 {
        struct ref_cache *refs = dir->ref_cache;
        DIR *d;
-       const char *path;
        struct dirent *de;
        int dirnamelen = strlen(dirname);
        struct strbuf refname;
+       struct strbuf path = STRBUF_INIT;
+       size_t path_baselen;
 
        if (*refs->name)
-               path = git_path_submodule(refs->name, "%s", dirname);
+               strbuf_git_path_submodule(&path, refs->name, "%s", dirname);
        else
-               path = git_path("%s", dirname);
+               strbuf_git_path(&path, "%s", dirname);
+       path_baselen = path.len;
 
-       d = opendir(path);
-       if (!d)
+       d = opendir(path.buf);
+       if (!d) {
+               strbuf_release(&path);
                return;
+       }
 
        strbuf_init(&refname, dirnamelen + 257);
        strbuf_add(&refname, dirname, dirnamelen);
@@ -1355,17 +1377,14 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir)
                unsigned char sha1[20];
                struct stat st;
                int flag;
-               const char *refdir;
 
                if (de->d_name[0] == '.')
                        continue;
                if (ends_with(de->d_name, ".lock"))
                        continue;
                strbuf_addstr(&refname, de->d_name);
-               refdir = *refs->name
-                       ? git_path_submodule(refs->name, "%s", refname.buf)
-                       : git_path("%s", refname.buf);
-               if (stat(refdir, &st) < 0) {
+               strbuf_addstr(&path, de->d_name);
+               if (stat(path.buf, &st) < 0) {
                        ; /* silently ignore */
                } else if (S_ISDIR(st.st_mode)) {
                        strbuf_addch(&refname, '/');
@@ -1412,8 +1431,10 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir)
                                         create_ref_entry(refname.buf, sha1, flag, 0));
                }
                strbuf_setlen(&refname, dirnamelen);
+               strbuf_setlen(&path, path_baselen);
        }
        strbuf_release(&refname);
+       strbuf_release(&path);
        closedir(d);
 }
 
@@ -1464,14 +1485,15 @@ static int resolve_gitlink_ref_recursive(struct ref_cache *refs,
 {
        int fd, len;
        char buffer[128], *p;
-       const char *path;
+       char *path;
 
        if (recursion > MAXDEPTH || strlen(refname) > MAXREFLEN)
                return -1;
        path = *refs->name
-               ? git_path_submodule(refs->name, "%s", refname)
-               : git_path("%s", refname);
+               ? git_pathdup_submodule(refs->name, "%s", refname)
+               : git_pathdup("%s", refname);
        fd = open(path, O_RDONLY);
+       free(path);
        if (fd < 0)
                return resolve_gitlink_packed_ref(refs, refname, sha1);
 
@@ -1741,9 +1763,11 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
        return ret;
 }
 
-char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags)
+char *resolve_refdup(const char *refname, int resolve_flags,
+                    unsigned char *sha1, int *flags)
 {
-       return xstrdup_or_null(resolve_ref_unsafe(ref, resolve_flags, sha1, flags));
+       return xstrdup_or_null(resolve_ref_unsafe(refname, resolve_flags,
+                                                 sha1, flags));
 }
 
 /* The argument to filter_refs */
@@ -2122,7 +2146,8 @@ int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *c
 
 int for_each_replace_ref(each_ref_fn fn, void *cb_data)
 {
-       return do_for_each_ref(&ref_cache, "refs/replace/", fn, 13, 0, cb_data);
+       return do_for_each_ref(&ref_cache, git_replace_ref_base, fn,
+                              strlen(git_replace_ref_base), 0, cb_data);
 }
 
 int head_ref_namespaced(each_ref_fn fn, void *cb_data)
@@ -2265,25 +2290,14 @@ static int verify_lock(struct ref_lock *lock,
        return 0;
 }
 
-static int remove_empty_directories(const char *file)
+static int remove_empty_directories(struct strbuf *path)
 {
-       /* we want to create a file but there is a directory there;
+       /*
+        * we want to create a file but there is a directory there;
         * if that is an empty directory (or a directory that contains
         * only empty directories), remove them.
         */
-       struct strbuf path;
-       int result, save_errno;
-
-       strbuf_init(&path, 20);
-       strbuf_addstr(&path, file);
-
-       result = remove_dir_recursively(&path, REMOVE_DIR_EMPTY_ONLY);
-       save_errno = errno;
-
-       strbuf_release(&path);
-       errno = save_errno;
-
-       return result;
+       return remove_dir_recursively(path, REMOVE_DIR_EMPTY_ONLY);
 }
 
 /*
@@ -2383,7 +2397,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
                                            unsigned int flags, int *type_p,
                                            struct strbuf *err)
 {
-       const char *ref_file;
+       struct strbuf ref_file = STRBUF_INIT;
+       struct strbuf orig_ref_file = STRBUF_INIT;
        const char *orig_refname = refname;
        struct ref_lock *lock;
        int last_errno = 0;
@@ -2407,20 +2422,19 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
        refname = resolve_ref_unsafe(refname, resolve_flags,
                                     lock->old_oid.hash, &type);
        if (!refname && errno == EISDIR) {
-               /* we are trying to lock foo but we used to
+               /*
+                * we are trying to lock foo but we used to
                 * have foo/bar which now does not exist;
                 * it is normal for the empty directory 'foo'
                 * to remain.
                 */
-               ref_file = git_path("%s", orig_refname);
-               if (remove_empty_directories(ref_file)) {
+               strbuf_git_path(&orig_ref_file, "%s", orig_refname);
+               if (remove_empty_directories(&orig_ref_file)) {
                        last_errno = errno;
-
                        if (!verify_refname_available(orig_refname, extras, skip,
                                                      get_loose_refs(&ref_cache), err))
                                strbuf_addf(err, "there are still refs under '%s'",
                                            orig_refname);
-
                        goto error_return;
                }
                refname = resolve_ref_unsafe(orig_refname, resolve_flags,
@@ -2460,10 +2474,10 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
        }
        lock->ref_name = xstrdup(refname);
        lock->orig_ref_name = xstrdup(orig_refname);
-       ref_file = git_path("%s", refname);
+       strbuf_git_path(&ref_file, "%s", refname);
 
  retry:
-       switch (safe_create_leading_directories_const(ref_file)) {
+       switch (safe_create_leading_directories_const(ref_file.buf)) {
        case SCLD_OK:
                break; /* success */
        case SCLD_VANISHED:
@@ -2472,11 +2486,12 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
                /* fall through */
        default:
                last_errno = errno;
-               strbuf_addf(err, "unable to create directory for %s", ref_file);
+               strbuf_addf(err, "unable to create directory for %s",
+                           ref_file.buf);
                goto error_return;
        }
 
-       if (hold_lock_file_for_update(lock->lk, ref_file, lflags) < 0) {
+       if (hold_lock_file_for_update(lock->lk, ref_file.buf, lflags) < 0) {
                last_errno = errno;
                if (errno == ENOENT && --attempts_remaining > 0)
                        /*
@@ -2486,7 +2501,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
                         */
                        goto retry;
                else {
-                       unable_to_lock_message(ref_file, errno, err);
+                       unable_to_lock_message(ref_file.buf, errno, err);
                        goto error_return;
                }
        }
@@ -2494,12 +2509,17 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
                last_errno = errno;
                goto error_return;
        }
-       return lock;
+       goto out;
 
  error_return:
        unlock_ref(lock);
+       lock = NULL;
+
+ out:
+       strbuf_release(&ref_file);
+       strbuf_release(&orig_ref_file);
        errno = last_errno;
-       return NULL;
+       return lock;
 }
 
 /*
@@ -2530,8 +2550,12 @@ static int write_packed_entry_fn(struct ref_entry *entry, void *cb_data)
        return 0;
 }
 
-/* This should return a meaningful errno on failure */
-int lock_packed_refs(int flags)
+/*
+ * Lock the packed-refs file for writing. Flags is passed to
+ * hold_lock_file_for_update(). Return 0 on success. On errors, set
+ * errno appropriately and return a nonzero value.
+ */
+static int lock_packed_refs(int flags)
 {
        static int timeout_configured = 0;
        static int timeout_value = 1000;
@@ -2561,10 +2585,12 @@ int lock_packed_refs(int flags)
 }
 
 /*
- * Commit the packed refs changes.
- * On error we must make sure that errno contains a meaningful value.
+ * Write the current version of the packed refs cache from memory to
+ * disk. The packed-refs file must already be locked for writing (see
+ * lock_packed_refs()). Return zero on success. On errors, set errno
+ * and return a nonzero value
  */
-int commit_packed_refs(void)
+static int commit_packed_refs(void)
 {
        struct packed_ref_cache *packed_ref_cache =
                get_packed_ref_cache(&ref_cache);
@@ -2593,7 +2619,12 @@ int commit_packed_refs(void)
        return error;
 }
 
-void rollback_packed_refs(void)
+/*
+ * Rollback the lockfile for the packed-refs file, and discard the
+ * in-memory packed reference cache.  (The packed-refs file will be
+ * read anew if it is needed again after this function is called.)
+ */
+static void rollback_packed_refs(void)
 {
        struct packed_ref_cache *packed_ref_cache =
                get_packed_ref_cache(&ref_cache);
@@ -2751,7 +2782,14 @@ int pack_refs(unsigned int flags)
        return 0;
 }
 
-int repack_without_refs(struct string_list *refnames, struct strbuf *err)
+/*
+ * Rewrite the packed-refs file, omitting any refs listed in
+ * 'refnames'. On error, leave packed-refs unchanged, write an error
+ * message to 'err', and return a nonzero value.
+ *
+ * The refs in 'refnames' needn't be sorted. `err` must not be NULL.
+ */
+static int repack_without_refs(struct string_list *refnames, struct strbuf *err)
 {
        struct ref_dir *packed;
        struct string_list_item *refname;
@@ -2816,15 +2854,15 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)
        return 0;
 }
 
-int delete_ref(const char *refname, const unsigned char *sha1, unsigned int flags)
+int delete_ref(const char *refname, const unsigned char *old_sha1,
+              unsigned int flags)
 {
        struct ref_transaction *transaction;
        struct strbuf err = STRBUF_INIT;
 
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
-           ref_transaction_delete(transaction, refname,
-                                  (sha1 && !is_null_sha1(sha1)) ? sha1 : NULL,
+           ref_transaction_delete(transaction, refname, old_sha1,
                                   flags, NULL, &err) ||
            ref_transaction_commit(transaction, &err)) {
                error("%s", err.buf);
@@ -2837,6 +2875,44 @@ int delete_ref(const char *refname, const unsigned char *sha1, unsigned int flag
        return 0;
 }
 
+int delete_refs(struct string_list *refnames)
+{
+       struct strbuf err = STRBUF_INIT;
+       int i, result = 0;
+
+       if (!refnames->nr)
+               return 0;
+
+       result = repack_without_refs(refnames, &err);
+       if (result) {
+               /*
+                * If we failed to rewrite the packed-refs file, then
+                * it is unsafe to try to remove loose refs, because
+                * doing so might expose an obsolete packed value for
+                * a reference that might even point at an object that
+                * has been garbage collected.
+                */
+               if (refnames->nr == 1)
+                       error(_("could not delete reference %s: %s"),
+                             refnames->items[0].string, err.buf);
+               else
+                       error(_("could not delete references: %s"), err.buf);
+
+               goto out;
+       }
+
+       for (i = 0; i < refnames->nr; i++) {
+               const char *refname = refnames->items[i].string;
+
+               if (delete_ref(refname, NULL, 0))
+                       result |= error(_("could not remove reference %s"), refname);
+       }
+
+out:
+       strbuf_release(&err);
+       return result;
+}
+
 /*
  * People using contrib's git-new-workdir have .git/logs/refs ->
  * /some/other/path/.git/logs/refs, and that may live on another device.
@@ -2849,9 +2925,13 @@ int delete_ref(const char *refname, const unsigned char *sha1, unsigned int flag
 static int rename_tmp_log(const char *newrefname)
 {
        int attempts_remaining = 4;
+       struct strbuf path = STRBUF_INIT;
+       int ret = -1;
 
  retry:
-       switch (safe_create_leading_directories_const(git_path("logs/%s", newrefname))) {
+       strbuf_reset(&path);
+       strbuf_git_path(&path, "logs/%s", newrefname);
+       switch (safe_create_leading_directories_const(path.buf)) {
        case SCLD_OK:
                break; /* success */
        case SCLD_VANISHED:
@@ -2860,19 +2940,19 @@ static int rename_tmp_log(const char *newrefname)
                /* fall through */
        default:
                error("unable to create directory for %s", newrefname);
-               return -1;
+               goto out;
        }
 
-       if (rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newrefname))) {
+       if (rename(git_path(TMP_RENAMED_LOG), path.buf)) {
                if ((errno==EISDIR || errno==ENOTDIR) && --attempts_remaining > 0) {
                        /*
                         * rename(a, b) when b is an existing
                         * directory ought to result in ISDIR, but
                         * Solaris 5.8 gives ENOTDIR.  Sheesh.
                         */
-                       if (remove_empty_directories(git_path("logs/%s", newrefname))) {
+                       if (remove_empty_directories(&path)) {
                                error("Directory not empty: logs/%s", newrefname);
-                               return -1;
+                               goto out;
                        }
                        goto retry;
                } else if (errno == ENOENT && --attempts_remaining > 0) {
@@ -2885,10 +2965,13 @@ static int rename_tmp_log(const char *newrefname)
                } else {
                        error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s",
                                newrefname, strerror(errno));
-                       return -1;
+                       goto out;
                }
        }
-       return 0;
+       ret = 0;
+out:
+       strbuf_release(&path);
+       return ret;
 }
 
 static int rename_ref_available(const char *oldname, const char *newname)
@@ -2910,9 +2993,11 @@ static int rename_ref_available(const char *oldname, const char *newname)
        return ret;
 }
 
-static int write_ref_to_lockfile(struct ref_lock *lock, const unsigned char *sha1);
+static int write_ref_to_lockfile(struct ref_lock *lock,
+                                const unsigned char *sha1, struct strbuf *err);
 static int commit_ref_update(struct ref_lock *lock,
-                            const unsigned char *sha1, const char *logmsg);
+                            const unsigned char *sha1, const char *logmsg,
+                            int flags, struct strbuf *err);
 
 int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg)
 {
@@ -2950,7 +3035,14 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
        if (!read_ref_full(newrefname, RESOLVE_REF_READING, sha1, NULL) &&
            delete_ref(newrefname, sha1, REF_NODEREF)) {
                if (errno==EISDIR) {
-                       if (remove_empty_directories(git_path("%s", newrefname))) {
+                       struct strbuf path = STRBUF_INIT;
+                       int result;
+
+                       strbuf_git_path(&path, "%s", newrefname);
+                       result = remove_empty_directories(&path);
+                       strbuf_release(&path);
+
+                       if (result) {
                                error("Directory not empty: %s", newrefname);
                                goto rollback;
                        }
@@ -2973,9 +3065,10 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
        }
        hashcpy(lock->old_oid.hash, orig_sha1);
 
-       if (write_ref_to_lockfile(lock, orig_sha1) ||
-           commit_ref_update(lock, orig_sha1, logmsg)) {
-               error("unable to write current sha1 into %s", newrefname);
+       if (write_ref_to_lockfile(lock, orig_sha1, &err) ||
+           commit_ref_update(lock, orig_sha1, logmsg, 0, &err)) {
+               error("unable to write current sha1 into %s: %s", newrefname, err.buf);
+               strbuf_release(&err);
                goto rollback;
        }
 
@@ -2991,9 +3084,11 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
 
        flag = log_all_ref_updates;
        log_all_ref_updates = 0;
-       if (write_ref_to_lockfile(lock, orig_sha1) ||
-           commit_ref_update(lock, orig_sha1, NULL))
-               error("unable to write current sha1 into %s", oldrefname);
+       if (write_ref_to_lockfile(lock, orig_sha1, &err) ||
+           commit_ref_update(lock, orig_sha1, NULL, 0, &err)) {
+               error("unable to write current sha1 into %s: %s", oldrefname, err.buf);
+               strbuf_release(&err);
+       }
        log_all_ref_updates = flag;
 
  rollbacklog:
@@ -3048,60 +3143,73 @@ static int copy_msg(char *buf, const char *msg)
        return cp - buf;
 }
 
-/* This function must set a meaningful errno on failure */
-int log_ref_setup(const char *refname, struct strbuf *sb_logfile)
+static int should_autocreate_reflog(const char *refname)
+{
+       if (!log_all_ref_updates)
+               return 0;
+       return starts_with(refname, "refs/heads/") ||
+               starts_with(refname, "refs/remotes/") ||
+               starts_with(refname, "refs/notes/") ||
+               !strcmp(refname, "HEAD");
+}
+
+/*
+ * Create a reflog for a ref.  If force_create = 0, the reflog will
+ * only be created for certain refs (those for which
+ * should_autocreate_reflog returns non-zero.  Otherwise, create it
+ * regardless of the ref name.  Fill in *err and return -1 on failure.
+ */
+static int log_ref_setup(const char *refname, struct strbuf *logfile, struct strbuf *err, int force_create)
 {
        int logfd, oflags = O_APPEND | O_WRONLY;
-       char *logfile;
-
-       strbuf_git_path(sb_logfile, "logs/%s", refname);
-       logfile = sb_logfile->buf;
-       /* make sure the rest of the function can't change "logfile" */
-       sb_logfile = NULL;
-       if (log_all_ref_updates &&
-           (starts_with(refname, "refs/heads/") ||
-            starts_with(refname, "refs/remotes/") ||
-            starts_with(refname, "refs/notes/") ||
-            !strcmp(refname, "HEAD"))) {
-               if (safe_create_leading_directories(logfile) < 0) {
-                       int save_errno = errno;
-                       error("unable to create directory for %s", logfile);
-                       errno = save_errno;
+
+       strbuf_git_path(logfile, "logs/%s", refname);
+       if (force_create || should_autocreate_reflog(refname)) {
+               if (safe_create_leading_directories(logfile->buf) < 0) {
+                       strbuf_addf(err, "unable to create directory for %s: "
+                                   "%s", logfile->buf, strerror(errno));
                        return -1;
                }
                oflags |= O_CREAT;
        }
 
-       logfd = open(logfile, oflags, 0666);
+       logfd = open(logfile->buf, oflags, 0666);
        if (logfd < 0) {
                if (!(oflags & O_CREAT) && (errno == ENOENT || errno == EISDIR))
                        return 0;
 
                if (errno == EISDIR) {
                        if (remove_empty_directories(logfile)) {
-                               int save_errno = errno;
-                               error("There are still logs under '%s'",
-                                     logfile);
-                               errno = save_errno;
+                               strbuf_addf(err, "There are still logs under "
+                                           "'%s'", logfile->buf);
                                return -1;
                        }
-                       logfd = open(logfile, oflags, 0666);
+                       logfd = open(logfile->buf, oflags, 0666);
                }
 
                if (logfd < 0) {
-                       int save_errno = errno;
-                       error("Unable to append to %s: %s", logfile,
-                             strerror(errno));
-                       errno = save_errno;
+                       strbuf_addf(err, "unable to append to %s: %s",
+                                   logfile->buf, strerror(errno));
                        return -1;
                }
        }
 
-       adjust_shared_perm(logfile);
+       adjust_shared_perm(logfile->buf);
        close(logfd);
        return 0;
 }
 
+
+int safe_create_reflog(const char *refname, int force_create, struct strbuf *err)
+{
+       int ret;
+       struct strbuf sb = STRBUF_INIT;
+
+       ret = log_ref_setup(refname, &sb, err, force_create);
+       strbuf_release(&sb);
+       return ret;
+}
+
 static int log_ref_write_fd(int fd, const unsigned char *old_sha1,
                            const unsigned char *new_sha1,
                            const char *committer, const char *msg)
@@ -3130,47 +3238,45 @@ static int log_ref_write_fd(int fd, const unsigned char *old_sha1,
 
 static int log_ref_write_1(const char *refname, const unsigned char *old_sha1,
                           const unsigned char *new_sha1, const char *msg,
-                          struct strbuf *sb_log_file)
+                          struct strbuf *logfile, int flags,
+                          struct strbuf *err)
 {
        int logfd, result, oflags = O_APPEND | O_WRONLY;
-       char *log_file;
 
        if (log_all_ref_updates < 0)
                log_all_ref_updates = !is_bare_repository();
 
-       result = log_ref_setup(refname, sb_log_file);
+       result = log_ref_setup(refname, logfile, err, flags & REF_FORCE_CREATE_REFLOG);
+
        if (result)
                return result;
-       log_file = sb_log_file->buf;
-       /* make sure the rest of the function can't change "log_file" */
-       sb_log_file = NULL;
 
-       logfd = open(log_file, oflags);
+       logfd = open(logfile->buf, oflags);
        if (logfd < 0)
                return 0;
        result = log_ref_write_fd(logfd, old_sha1, new_sha1,
                                  git_committer_info(0), msg);
        if (result) {
-               int save_errno = errno;
+               strbuf_addf(err, "unable to append to %s: %s", logfile->buf,
+                           strerror(errno));
                close(logfd);
-               error("Unable to append to %s", log_file);
-               errno = save_errno;
                return -1;
        }
        if (close(logfd)) {
-               int save_errno = errno;
-               error("Unable to append to %s", log_file);
-               errno = save_errno;
+               strbuf_addf(err, "unable to append to %s: %s", logfile->buf,
+                           strerror(errno));
                return -1;
        }
        return 0;
 }
 
 static int log_ref_write(const char *refname, const unsigned char *old_sha1,
-                        const unsigned char *new_sha1, const char *msg)
+                        const unsigned char *new_sha1, const char *msg,
+                        int flags, struct strbuf *err)
 {
        struct strbuf sb = STRBUF_INIT;
-       int ret = log_ref_write_1(refname, old_sha1, new_sha1, msg, &sb);
+       int ret = log_ref_write_1(refname, old_sha1, new_sha1, msg, &sb, flags,
+                                 err);
        strbuf_release(&sb);
        return ret;
 }
@@ -3182,36 +3288,36 @@ int is_branch(const char *refname)
 
 /*
  * Write sha1 into the open lockfile, then close the lockfile. On
- * errors, rollback the lockfile and set errno to reflect the problem.
+ * errors, rollback the lockfile, fill in *err and
+ * return -1.
  */
 static int write_ref_to_lockfile(struct ref_lock *lock,
-                                const unsigned char *sha1)
+                                const unsigned char *sha1, struct strbuf *err)
 {
        static char term = '\n';
        struct object *o;
 
        o = parse_object(sha1);
        if (!o) {
-               error("Trying to write ref %s with nonexistent object %s",
-                       lock->ref_name, sha1_to_hex(sha1));
+               strbuf_addf(err,
+                           "Trying to write ref %s with nonexistent object %s",
+                           lock->ref_name, sha1_to_hex(sha1));
                unlock_ref(lock);
-               errno = EINVAL;
                return -1;
        }
        if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) {
-               error("Trying to write non-commit object %s to branch %s",
-                       sha1_to_hex(sha1), lock->ref_name);
+               strbuf_addf(err,
+                           "Trying to write non-commit object %s to branch %s",
+                           sha1_to_hex(sha1), lock->ref_name);
                unlock_ref(lock);
-               errno = EINVAL;
                return -1;
        }
        if (write_in_full(lock->lk->fd, sha1_to_hex(sha1), 40) != 40 ||
            write_in_full(lock->lk->fd, &term, 1) != 1 ||
            close_ref(lock) < 0) {
-               int save_errno = errno;
-               error("Couldn't write %s", lock->lk->filename.buf);
+               strbuf_addf(err,
+                           "Couldn't write %s", lock->lk->filename.buf);
                unlock_ref(lock);
-               errno = save_errno;
                return -1;
        }
        return 0;
@@ -3223,12 +3329,17 @@ static int write_ref_to_lockfile(struct ref_lock *lock,
  * necessary, using the specified lockmsg (which can be NULL).
  */
 static int commit_ref_update(struct ref_lock *lock,
-                            const unsigned char *sha1, const char *logmsg)
+                            const unsigned char *sha1, const char *logmsg,
+                            int flags, struct strbuf *err)
 {
        clear_loose_ref_cache(&ref_cache);
-       if (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg) < 0 ||
+       if (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg, flags, err) < 0 ||
            (strcmp(lock->ref_name, lock->orig_ref_name) &&
-            log_ref_write(lock->orig_ref_name, lock->old_oid.hash, sha1, logmsg) < 0)) {
+            log_ref_write(lock->orig_ref_name, lock->old_oid.hash, sha1, logmsg, flags, err) < 0)) {
+               char *old_msg = strbuf_detach(err, NULL);
+               strbuf_addf(err, "Cannot update the ref '%s': %s",
+                           lock->ref_name, old_msg);
+               free(old_msg);
                unlock_ref(lock);
                return -1;
        }
@@ -3251,14 +3362,21 @@ static int commit_ref_update(struct ref_lock *lock,
                head_ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
                                              head_sha1, &head_flag);
                if (head_ref && (head_flag & REF_ISSYMREF) &&
-                   !strcmp(head_ref, lock->ref_name))
-                       log_ref_write("HEAD", lock->old_oid.hash, sha1, logmsg);
+                   !strcmp(head_ref, lock->ref_name)) {
+                       struct strbuf log_err = STRBUF_INIT;
+                       if (log_ref_write("HEAD", lock->old_oid.hash, sha1,
+                                         logmsg, 0, &log_err)) {
+                               error("%s", log_err.buf);
+                               strbuf_release(&log_err);
+                       }
+               }
        }
        if (commit_ref(lock)) {
                error("Couldn't set %s", lock->ref_name);
                unlock_ref(lock);
                return -1;
        }
+
        unlock_ref(lock);
        return 0;
 }
@@ -3266,11 +3384,12 @@ static int commit_ref_update(struct ref_lock *lock,
 int create_symref(const char *ref_target, const char *refs_heads_master,
                  const char *logmsg)
 {
-       const char *lockpath;
+       char *lockpath = NULL;
        char ref[1000];
        int fd, len, written;
        char *git_HEAD = git_pathdup("%s", ref_target);
        unsigned char old_sha1[20], new_sha1[20];
+       struct strbuf err = STRBUF_INIT;
 
        if (logmsg && read_ref(ref_target, old_sha1))
                hashclr(old_sha1);
@@ -3292,7 +3411,7 @@ int create_symref(const char *ref_target, const char *refs_heads_master,
                error("refname too long: %s", refs_heads_master);
                goto error_free_return;
        }
-       lockpath = mkpath("%s.lock", git_HEAD);
+       lockpath = mkpathdup("%s.lock", git_HEAD);
        fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666);
        if (fd < 0) {
                error("Unable to open %s for writing", lockpath);
@@ -3312,15 +3431,20 @@ int create_symref(const char *ref_target, const char *refs_heads_master,
        error_unlink_return:
                unlink_or_warn(lockpath);
        error_free_return:
+               free(lockpath);
                free(git_HEAD);
                return -1;
        }
+       free(lockpath);
 
 #ifndef NO_SYMLINK_HEAD
        done:
 #endif
-       if (logmsg && !read_ref(refs_heads_master, new_sha1))
-               log_ref_write(ref_target, old_sha1, new_sha1, logmsg);
+       if (logmsg && !read_ref(refs_heads_master, new_sha1) &&
+               log_ref_write(ref_target, old_sha1, new_sha1, logmsg, 0, &err)) {
+               error("%s", err.buf);
+               strbuf_release(&err);
+       }
 
        free(git_HEAD);
        return 0;
@@ -3371,14 +3495,14 @@ static int read_ref_at_ent(unsigned char *osha1, unsigned char *nsha1,
                        hashcpy(cb->sha1, nsha1);
                        if (hashcmp(cb->osha1, nsha1))
                                warning("Log for ref %s has gap after %s.",
-                                       cb->refname, show_date(cb->date, cb->tz, DATE_RFC2822));
+                                       cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822)));
                }
                else if (cb->date == cb->at_time)
                        hashcpy(cb->sha1, nsha1);
                else if (hashcmp(nsha1, cb->sha1))
                        warning("Log for ref %s unexpectedly ended on %s.",
                                cb->refname, show_date(cb->date, cb->tz,
-                                                  DATE_RFC2822));
+                                                      DATE_MODE(RFC2822)));
                hashcpy(cb->osha1, osha1);
                hashcpy(cb->nsha1, nsha1);
                cb->found_it = 1;
@@ -3956,14 +4080,19 @@ int ref_transaction_commit(struct ref_transaction *transaction,
                                 * value, so we don't need to write it.
                                 */
                        } else if (write_ref_to_lockfile(update->lock,
-                                                        update->new_sha1)) {
+                                                        update->new_sha1,
+                                                        err)) {
+                               char *write_err = strbuf_detach(err, NULL);
+
                                /*
                                 * The lock was freed upon failure of
                                 * write_ref_to_lockfile():
                                 */
                                update->lock = NULL;
-                               strbuf_addf(err, "cannot update the ref '%s'.",
-                                           update->refname);
+                               strbuf_addf(err,
+                                           "cannot update the ref '%s': %s",
+                                           update->refname, write_err);
+                               free(write_err);
                                ret = TRANSACTION_GENERIC_ERROR;
                                goto cleanup;
                        } else {
@@ -3989,11 +4118,10 @@ int ref_transaction_commit(struct ref_transaction *transaction,
 
                if (update->flags & REF_NEEDS_COMMIT) {
                        if (commit_ref_update(update->lock,
-                                             update->new_sha1, update->msg)) {
+                                             update->new_sha1, update->msg,
+                                             update->flags, err)) {
                                /* freed by commit_ref_update(): */
                                update->lock = NULL;
-                               strbuf_addf(err, "Cannot update the ref '%s'.",
-                                           update->refname);
                                ret = TRANSACTION_GENERIC_ERROR;
                                goto cleanup;
                        } else {
@@ -4038,6 +4166,98 @@ int ref_transaction_commit(struct ref_transaction *transaction,
        return ret;
 }
 
+static int ref_present(const char *refname,
+                      const struct object_id *oid, int flags, void *cb_data)
+{
+       struct string_list *affected_refnames = cb_data;
+
+       return string_list_has_string(affected_refnames, refname);
+}
+
+int initial_ref_transaction_commit(struct ref_transaction *transaction,
+                                  struct strbuf *err)
+{
+       struct ref_dir *loose_refs = get_loose_refs(&ref_cache);
+       struct ref_dir *packed_refs = get_packed_refs(&ref_cache);
+       int ret = 0, i;
+       int n = transaction->nr;
+       struct ref_update **updates = transaction->updates;
+       struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
+
+       assert(err);
+
+       if (transaction->state != REF_TRANSACTION_OPEN)
+               die("BUG: commit called for transaction that is not open");
+
+       /* Fail if a refname appears more than once in the transaction: */
+       for (i = 0; i < n; i++)
+               string_list_append(&affected_refnames, updates[i]->refname);
+       string_list_sort(&affected_refnames);
+       if (ref_update_reject_duplicates(&affected_refnames, err)) {
+               ret = TRANSACTION_GENERIC_ERROR;
+               goto cleanup;
+       }
+
+       /*
+        * It's really undefined to call this function in an active
+        * repository or when there are existing references: we are
+        * only locking and changing packed-refs, so (1) any
+        * simultaneous processes might try to change a reference at
+        * the same time we do, and (2) any existing loose versions of
+        * the references that we are setting would have precedence
+        * over our values. But some remote helpers create the remote
+        * "HEAD" and "master" branches before calling this function,
+        * so here we really only check that none of the references
+        * that we are creating already exists.
+        */
+       if (for_each_rawref(ref_present, &affected_refnames))
+               die("BUG: initial ref transaction called with existing refs");
+
+       for (i = 0; i < n; i++) {
+               struct ref_update *update = updates[i];
+
+               if ((update->flags & REF_HAVE_OLD) &&
+                   !is_null_sha1(update->old_sha1))
+                       die("BUG: initial ref transaction with old_sha1 set");
+               if (verify_refname_available(update->refname,
+                                            &affected_refnames, NULL,
+                                            loose_refs, err) ||
+                   verify_refname_available(update->refname,
+                                            &affected_refnames, NULL,
+                                            packed_refs, err)) {
+                       ret = TRANSACTION_NAME_CONFLICT;
+                       goto cleanup;
+               }
+       }
+
+       if (lock_packed_refs(0)) {
+               strbuf_addf(err, "unable to lock packed-refs file: %s",
+                           strerror(errno));
+               ret = TRANSACTION_GENERIC_ERROR;
+               goto cleanup;
+       }
+
+       for (i = 0; i < n; i++) {
+               struct ref_update *update = updates[i];
+
+               if ((update->flags & REF_HAVE_NEW) &&
+                   !is_null_sha1(update->new_sha1))
+                       add_packed_ref(update->refname, update->new_sha1);
+       }
+
+       if (commit_packed_refs()) {
+               strbuf_addf(err, "unable to commit packed-refs file: %s",
+                           strerror(errno));
+               ret = TRANSACTION_GENERIC_ERROR;
+               goto cleanup;
+       }
+
+cleanup:
+       transaction->state = REF_TRANSACTION_CLOSED;
+       string_list_clear(&affected_refnames, 0);
+       return ret;
+}
+
 char *shorten_unambiguous_ref(const char *refname, int strict)
 {
        int i;
@@ -4159,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/refs.h b/refs.h
index 8c3d433a8a5b1b29f49d82e1c5799e8e3dc6e7a4..6a3fa6d41d50a7a76fdb2dab4c18aaaba1d9b8eb 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -1,6 +1,98 @@
 #ifndef REFS_H
 #define REFS_H
 
+/*
+ * Resolve a reference, recursively following symbolic refererences.
+ *
+ * Store the referred-to object's name in sha1 and return the name of
+ * the non-symbolic reference that ultimately pointed at it.  The
+ * return value, if not NULL, is a pointer into either a static buffer
+ * or the input ref.
+ *
+ * If the reference cannot be resolved to an object, the behavior
+ * depends on the RESOLVE_REF_READING flag:
+ *
+ * - If RESOLVE_REF_READING is set, return NULL.
+ *
+ * - If RESOLVE_REF_READING is not set, clear sha1 and return the name of
+ *   the last reference name in the chain, which will either be a non-symbolic
+ *   reference or an undefined reference.  If this is a prelude to
+ *   "writing" to the ref, the return value is the name of the ref
+ *   that will actually be created or changed.
+ *
+ * If the RESOLVE_REF_NO_RECURSE flag is passed, only resolves one
+ * level of symbolic reference.  The value stored in sha1 for a symbolic
+ * reference will always be null_sha1 in this case, and the return
+ * value is the reference that the symref refers to directly.
+ *
+ * If flags is non-NULL, set the value that it points to the
+ * combination of REF_ISPACKED (if the reference was found among the
+ * packed references), REF_ISSYMREF (if the initial reference was a
+ * symbolic reference), REF_BAD_NAME (if the reference name is ill
+ * formed --- see RESOLVE_REF_ALLOW_BAD_NAME below), and REF_ISBROKEN
+ * (if the ref is malformed or has a bad name). See refs.h for more detail
+ * on each flag.
+ *
+ * If ref is not a properly-formatted, normalized reference, return
+ * NULL.  If more than MAXDEPTH recursive symbolic lookups are needed,
+ * give up and return NULL.
+ *
+ * RESOLVE_REF_ALLOW_BAD_NAME allows resolving refs even when their
+ * name is invalid according to git-check-ref-format(1).  If the name
+ * is bad then the value stored in sha1 will be null_sha1 and the two
+ * flags REF_ISBROKEN and REF_BAD_NAME will be set.
+ *
+ * Even with RESOLVE_REF_ALLOW_BAD_NAME, names that escape the refs/
+ * directory and do not consist of all caps and underscores cannot be
+ * resolved. The function returns NULL for such ref names.
+ * Caps and underscores refers to the special refs, such as HEAD,
+ * FETCH_HEAD and friends, that all live outside of the refs/ directory.
+ */
+#define RESOLVE_REF_READING 0x01
+#define RESOLVE_REF_NO_RECURSE 0x02
+#define RESOLVE_REF_ALLOW_BAD_NAME 0x04
+
+extern const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
+                                     unsigned char *sha1, int *flags);
+
+extern char *resolve_refdup(const char *refname, int resolve_flags,
+                           unsigned char *sha1, int *flags);
+
+extern int read_ref_full(const char *refname, int resolve_flags,
+                        unsigned char *sha1, int *flags);
+extern int read_ref(const char *refname, unsigned char *sha1);
+
+extern int ref_exists(const char *refname);
+
+extern int is_branch(const char *refname);
+
+/*
+ * If refname is a non-symbolic reference that refers to a tag object,
+ * and the tag can be (recursively) dereferenced to a non-tag object,
+ * store the SHA1 of the referred-to object to sha1 and return 0.  If
+ * any of these conditions are not met, return a non-zero value.
+ * Symbolic references are considered unpeelable, even if they
+ * ultimately resolve to a peelable tag.
+ */
+extern int peel_ref(const char *refname, unsigned char *sha1);
+
+/**
+ * Resolve refname in the nested "gitlink" repository that is located
+ * at path.  If the resolution is successful, return 0 and set sha1 to
+ * the name of the object; otherwise, return a non-zero value.
+ */
+extern int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1);
+
+/*
+ * Return true iff abbrev_name is a possible abbreviation for
+ * full_name according to the rules defined by ref_rev_parse_rules in
+ * refs.c.
+ */
+extern int refname_match(const char *abbrev_name, const char *full_name);
+
+extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
+extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
+
 /*
  * A ref_transaction represents a collection of ref updates
  * that should succeed or fail together.
@@ -78,15 +170,15 @@ typedef int each_ref_fn(const char *refname,
  * modifies the reference also returns a nonzero value to immediately
  * stop the iteration.
  */
-extern int head_ref(each_ref_fn, void *);
-extern int for_each_ref(each_ref_fn, void *);
-extern int for_each_ref_in(const char *, each_ref_fn, void *);
-extern int for_each_tag_ref(each_ref_fn, void *);
-extern int for_each_branch_ref(each_ref_fn, void *);
-extern int for_each_remote_ref(each_ref_fn, void *);
-extern int for_each_replace_ref(each_ref_fn, void *);
-extern int for_each_glob_ref(each_ref_fn, const char *pattern, void *);
-extern int for_each_glob_ref_in(each_ref_fn, const char *pattern, const char* prefix, void *);
+extern int head_ref(each_ref_fn fn, void *cb_data);
+extern int for_each_ref(each_ref_fn fn, void *cb_data);
+extern int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data);
+extern int for_each_tag_ref(each_ref_fn fn, void *cb_data);
+extern int for_each_branch_ref(each_ref_fn fn, void *cb_data);
+extern int for_each_remote_ref(each_ref_fn fn, void *cb_data);
+extern int for_each_replace_ref(each_ref_fn fn, void *cb_data);
+extern int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data);
+extern int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, const char *prefix, void *cb_data);
 
 extern int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
 extern int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
@@ -99,47 +191,17 @@ extern int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn,
 extern int head_ref_namespaced(each_ref_fn fn, void *cb_data);
 extern int for_each_namespaced_ref(each_ref_fn fn, void *cb_data);
 
+/* can be used to learn about broken ref and symref */
+extern int for_each_rawref(each_ref_fn fn, void *cb_data);
+
 static inline const char *has_glob_specials(const char *pattern)
 {
        return strpbrk(pattern, "?*[");
 }
 
-/* can be used to learn about broken ref and symref */
-extern int for_each_rawref(each_ref_fn, void *);
-
 extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname);
 extern void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct string_list *refnames);
 
-/*
- * Lock the packed-refs file for writing.  Flags is passed to
- * hold_lock_file_for_update().  Return 0 on success.
- * Errno is set to something meaningful on error.
- */
-extern int lock_packed_refs(int flags);
-
-/*
- * Add a reference to the in-memory packed reference cache.  This may
- * only be called while the packed-refs file is locked (see
- * lock_packed_refs()).  To actually write the packed-refs file, call
- * commit_packed_refs().
- */
-extern void add_packed_ref(const char *refname, const unsigned char *sha1);
-
-/*
- * Write the current version of the packed refs cache from memory to
- * disk.  The packed-refs file must already be locked for writing (see
- * lock_packed_refs()).  Return zero on success.
- * Sets errno to something meaningful on error.
- */
-extern int commit_packed_refs(void);
-
-/*
- * Rollback the lockfile for the packed-refs file, and discard the
- * in-memory packed reference cache.  (The packed-refs file will be
- * read anew if it is needed again after this function is called.)
- */
-extern void rollback_packed_refs(void);
-
 /*
  * Flags for controlling behaviour of pack_refs()
  * PACK_REFS_PRUNE: Prune loose refs after packing
@@ -154,31 +216,6 @@ extern void rollback_packed_refs(void);
  */
 int pack_refs(unsigned int flags);
 
-/*
- * Rewrite the packed-refs file, omitting any refs listed in
- * 'refnames'. On error, packed-refs will be unchanged, the return
- * value is nonzero, and a message about the error is written to the
- * 'err' strbuf.
- *
- * The refs in 'refnames' needn't be sorted. `err` must not be NULL.
- */
-extern int repack_without_refs(struct string_list *refnames,
-                              struct strbuf *err);
-
-extern int ref_exists(const char *);
-
-extern int is_branch(const char *refname);
-
-/*
- * If refname is a non-symbolic reference that refers to a tag object,
- * and the tag can be (recursively) dereferenced to a non-tag object,
- * store the SHA1 of the referred-to object to sha1 and return 0.  If
- * any of these conditions are not met, return a non-zero value.
- * Symbolic references are considered unpeelable, even if they
- * ultimately resolve to a peelable tag.
- */
-extern int peel_ref(const char *refname, unsigned char *sha1);
-
 /*
  * Flags controlling ref_transaction_update(), ref_transaction_create(), etc.
  * REF_NODEREF: act on the ref directly, instead of dereferencing
@@ -187,11 +224,12 @@ extern int peel_ref(const char *refname, unsigned char *sha1);
  * Other flags are reserved for internal use.
  */
 #define REF_NODEREF    0x01
+#define REF_FORCE_CREATE_REFLOG 0x40
 
 /*
- * Setup reflog before using. Set errno to something meaningful on failure.
+ * Setup reflog before using. Fill in err and return -1 on failure.
  */
-int log_ref_setup(const char *refname, struct strbuf *logfile);
+int safe_create_reflog(const char *refname, int force_create, struct strbuf *err);
 
 /** Reads log for the value of ref during at_time. **/
 extern int read_ref_at(const char *refname, unsigned int flags,
@@ -202,6 +240,23 @@ extern int read_ref_at(const char *refname, unsigned int flags,
 /** Check if a particular reflog exists */
 extern int reflog_exists(const char *refname);
 
+/*
+ * Delete the specified reference. If old_sha1 is non-NULL, then
+ * verify that the current value of the reference is old_sha1 before
+ * deleting it. If old_sha1 is NULL, delete the reference if it
+ * exists, regardless of its old value. It is an error for old_sha1 to
+ * be NULL_SHA1. flags is passed through to ref_transaction_delete().
+ */
+extern int delete_ref(const char *refname, const unsigned char *old_sha1,
+                     unsigned int flags);
+
+/*
+ * Delete the specified references. If there are any problems, emit
+ * errors but attempt to keep going (i.e., the deletes are not done in
+ * an all-or-nothing transaction).
+ */
+extern int delete_refs(struct string_list *refnames);
+
 /** Delete a reflog */
 extern int delete_reflog(const char *refname);
 
@@ -224,23 +279,19 @@ extern int for_each_reflog(each_ref_fn, void *);
  * to the rules described in Documentation/git-check-ref-format.txt.
  * If REFNAME_ALLOW_ONELEVEL is set in flags, then accept one-level
  * reference names.  If REFNAME_REFSPEC_PATTERN is set in flags, then
- * allow a "*" wildcard character in place of one of the name
- * components.  No leading or repeated slashes are accepted.
+ * allow a single "*" wildcard character in the refspec. No leading or
+ * repeated slashes are accepted.
  */
 extern int check_refname_format(const char *refname, int flags);
 
 extern const char *prettify_refname(const char *refname);
+
 extern char *shorten_unambiguous_ref(const char *refname, int strict);
 
 /** rename ref, return 0 on success **/
 extern int rename_ref(const char *oldref, const char *newref, const char *logmsg);
 
-/**
- * Resolve refname in the nested "gitlink" repository that is located
- * at path.  If the resolution is successful, return 0 and set sha1 to
- * the name of the object; otherwise, return a non-zero value.
- */
-extern int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1);
+extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg);
 
 enum action_on_err {
        UPDATE_REFS_MSG_ON_ERR,
@@ -359,6 +410,20 @@ int ref_transaction_verify(struct ref_transaction *transaction,
 int ref_transaction_commit(struct ref_transaction *transaction,
                           struct strbuf *err);
 
+/*
+ * Like ref_transaction_commit(), but optimized for creating
+ * references when originally initializing a repository (e.g., by "git
+ * clone"). It writes the new references directly to packed-refs
+ * without locking the individual references.
+ *
+ * It is a bug to call this function when there might be other
+ * processes accessing the repository or if there are existing
+ * references that might conflict with the ones being created. All
+ * old_sha1 values must either be absent or NULL_SHA1.
+ */
+int initial_ref_transaction_commit(struct ref_transaction *transaction,
+                                  struct strbuf *err);
+
 /*
  * Free an existing transaction and all associated data.
  */
@@ -377,6 +442,7 @@ int update_ref(const char *msg, const char *refname,
               unsigned int flags, enum action_on_err onerr);
 
 extern int parse_hide_refs_config(const char *var, const char *value, const char *);
+
 extern int ref_is_hidden(const char *);
 
 enum expire_reflog_flags {
index 48bf6eb93b361a736d96e701749c16bae8d169a8..f599c372c64dd8930f9640e9503f73c5edaff49c 100644 (file)
@@ -1,4 +1,5 @@
 #include "cache.h"
+#include "refs.h"
 #include "remote.h"
 #include "strbuf.h"
 #include "url.h"
index 94aea9a36f16d9d72c1769aa9e258c5370c7dc84..6a517aa383b75512ba77592616e084d74647b491 100644 (file)
--- a/rerere.c
+++ b/rerere.c
@@ -20,8 +20,6 @@ static int rerere_enabled = -1;
 /* automatically update cleanly resolved paths to the index */
 static int rerere_autoupdate;
 
-static char *merge_rr_path;
-
 const char *rerere_path(const char *hex, const char *file)
 {
        return git_path("rr-cache/%s/%s", hex, file);
@@ -37,7 +35,7 @@ static void read_rr(struct string_list *rr)
 {
        unsigned char sha1[20];
        char buf[PATH_MAX];
-       FILE *in = fopen(merge_rr_path, "r");
+       FILE *in = fopen(git_path_merge_rr(), "r");
        if (!in)
                return;
        while (fread(buf, 40, 1, in) == 1) {
@@ -577,21 +575,21 @@ static void git_rerere_config(void)
        git_config(git_default_config, NULL);
 }
 
+static GIT_PATH_FUNC(git_path_rr_cache, "rr-cache")
+
 static int is_rerere_enabled(void)
 {
-       const char *rr_cache;
        int rr_cache_exists;
 
        if (!rerere_enabled)
                return 0;
 
-       rr_cache = git_path("rr-cache");
-       rr_cache_exists = is_directory(rr_cache);
+       rr_cache_exists = is_directory(git_path_rr_cache());
        if (rerere_enabled < 0)
                return rr_cache_exists;
 
-       if (!rr_cache_exists && mkdir_in_gitdir(rr_cache))
-               die("Could not create directory %s", rr_cache);
+       if (!rr_cache_exists && mkdir_in_gitdir(git_path_rr_cache()))
+               die("Could not create directory %s", git_path_rr_cache());
        return 1;
 }
 
@@ -605,8 +603,7 @@ int setup_rerere(struct string_list *merge_rr, int flags)
 
        if (flags & (RERERE_AUTOUPDATE|RERERE_NOAUTOUPDATE))
                rerere_autoupdate = !!(flags & RERERE_AUTOUPDATE);
-       merge_rr_path = git_pathdup("MERGE_RR");
-       fd = hold_lock_file_for_update(&write_lock, merge_rr_path,
+       fd = hold_lock_file_for_update(&write_lock, git_path_merge_rr(),
                                       LOCK_DIE_ON_ERROR);
        read_rr(merge_rr);
        return fd;
@@ -741,5 +738,5 @@ void rerere_clear(struct string_list *merge_rr)
                if (!has_rerere_resolution(name))
                        unlink_rr_item(name);
        }
-       unlink_or_warn(git_path("MERGE_RR"));
+       unlink_or_warn(git_path_merge_rr());
 }
index ab97ffd4590cc41743d4bb828a9c5bdb1a00cce9..5350139599d7d9fe542423ab9042962b75821dae 100644 (file)
 #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;
@@ -1996,10 +2000,10 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
        } else if (!strcmp(arg, "--full-history")) {
                revs->simplify_history = 0;
        } else if (!strcmp(arg, "--relative-date")) {
-               revs->date_mode = DATE_RELATIVE;
+               revs->date_mode.type = DATE_RELATIVE;
                revs->date_mode_explicit = 1;
        } else if ((argcount = parse_long_opt("date", argv, &optarg))) {
-               revs->date_mode = parse_date_format(optarg);
+               parse_date_format(optarg, &revs->date_mode);
                revs->date_mode_explicit = 1;
                return argcount;
        } else if (!strcmp(arg, "--log-size")) {
@@ -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;
index 0ea8b4e25555e30d852f47ce37ec2a75a61043e3..5bc9686846045182c3d2393a98e3018df2f05af4 100644 (file)
@@ -146,7 +146,7 @@ struct rev_info {
                        track_first_time:1,
                        linear:1;
 
-       enum date_mode date_mode;
+       struct date_mode date_mode;
 
        unsigned int    abbrev;
        enum cmit_fmt   commit_format;
index 0d01671c1ff26ccfe04481713a4071c321d8ab11..3277cf797ed41e5834b3b94fa8d2e9e9d5b4a317 100644 (file)
@@ -784,11 +784,13 @@ int finish_async(struct async *async)
 
 const char *find_hook(const char *name)
 {
-       const char *path = git_path("hooks/%s", name);
-       if (access(path, X_OK) < 0)
-               path = NULL;
+       static struct strbuf path = STRBUF_INIT;
 
-       return path;
+       strbuf_reset(&path);
+       strbuf_git_path(&path, "hooks/%s", name);
+       if (access(path.buf, X_OK) < 0)
+               return NULL;
+       return path.buf;
 }
 
 int run_hook_ve(const char *const *env, const char *name, va_list args)
index 1103805af1b01e8396de15f48fbd06f75313fb0d..5b4425a3cbe1aea2bae40c4e060e45ee2d7a29a5 100644 (file)
@@ -52,6 +52,11 @@ int start_command(struct child_process *);
 int finish_command(struct child_process *);
 int run_command(struct child_process *);
 
+/*
+ * Returns the path to the hook file, or NULL if the hook is missing
+ * or disabled. Note that this points to static storage that will be
+ * overwritten by further calls to find_hook and run_hook_*.
+ */
 extern const char *find_hook(const char *name);
 LAST_ARG_MUST_BE_NULL
 extern int run_hook_le(const char *const *env, const char *name, ...);
index c4f4b7d571fa088f80a0dbba815491a35f0c153f..147adfac818b53156b1ad269211014e21525619c 100644 (file)
 const char sign_off_header[] = "Signed-off-by: ";
 static const char cherry_picked_prefix[] = "(cherry picked from commit ";
 
+static GIT_PATH_FUNC(git_path_todo_file, SEQ_TODO_FILE)
+static GIT_PATH_FUNC(git_path_opts_file, SEQ_OPTS_FILE)
+static GIT_PATH_FUNC(git_path_seq_dir, SEQ_DIR)
+static GIT_PATH_FUNC(git_path_head_file, SEQ_HEAD_FILE)
+
 static int is_rfc2822_line(const char *buf, int len)
 {
        int i;
@@ -186,7 +191,7 @@ static void print_advice(int show_hint, struct replay_opts *opts)
                 * (typically rebase --interactive) wants to take care
                 * of the commit itself so remove CHERRY_PICK_HEAD
                 */
-               unlink(git_path("CHERRY_PICK_HEAD"));
+               unlink(git_path_cherry_pick_head());
                return;
        }
 
@@ -467,7 +472,6 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
        struct commit *base, *next, *parent;
        const char *base_label, *next_label;
        struct commit_message msg = { NULL, NULL, NULL, NULL };
-       char *defmsg = NULL;
        struct strbuf msgbuf = STRBUF_INIT;
        int res, unborn = 0, allow;
 
@@ -537,8 +541,6 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
         * reverse of it if we are revert.
         */
 
-       defmsg = git_pathdup("MERGE_MSG");
-
        if (opts->action == REPLAY_REVERT) {
                base = commit;
                base_label = msg.label;
@@ -585,12 +587,12 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
        if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) {
                res = do_recursive_merge(base, next, base_label, next_label,
                                         head, &msgbuf, opts);
-               write_message(&msgbuf, defmsg);
+               write_message(&msgbuf, git_path_merge_msg());
        } else {
                struct commit_list *common = NULL;
                struct commit_list *remotes = NULL;
 
-               write_message(&msgbuf, defmsg);
+               write_message(&msgbuf, git_path_merge_msg());
 
                commit_list_insert(base, &common);
                commit_list_insert(next, &remotes);
@@ -628,11 +630,10 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
                goto leave;
        }
        if (!opts->no_commit)
-               res = run_git_commit(defmsg, opts, allow);
+               res = run_git_commit(git_path_merge_msg(), opts, allow);
 
 leave:
        free_message(commit, &msg);
-       free(defmsg);
 
        return res;
 }
@@ -756,24 +757,23 @@ static int parse_insn_buffer(char *buf, struct commit_list **todo_list,
 static void read_populate_todo(struct commit_list **todo_list,
                        struct replay_opts *opts)
 {
-       const char *todo_file = git_path(SEQ_TODO_FILE);
        struct strbuf buf = STRBUF_INIT;
        int fd, res;
 
-       fd = open(todo_file, O_RDONLY);
+       fd = open(git_path_todo_file(), O_RDONLY);
        if (fd < 0)
-               die_errno(_("Could not open %s"), todo_file);
+               die_errno(_("Could not open %s"), git_path_todo_file());
        if (strbuf_read(&buf, fd, 0) < 0) {
                close(fd);
                strbuf_release(&buf);
-               die(_("Could not read %s."), todo_file);
+               die(_("Could not read %s."), git_path_todo_file());
        }
        close(fd);
 
        res = parse_insn_buffer(buf.buf, todo_list, opts);
        strbuf_release(&buf);
        if (res)
-               die(_("Unusable instruction sheet: %s"), todo_file);
+               die(_("Unusable instruction sheet: %s"), git_path_todo_file());
 }
 
 static int populate_opts_cb(const char *key, const char *value, void *data)
@@ -813,12 +813,10 @@ static int populate_opts_cb(const char *key, const char *value, void *data)
 
 static void read_populate_opts(struct replay_opts **opts_ptr)
 {
-       const char *opts_file = git_path(SEQ_OPTS_FILE);
-
-       if (!file_exists(opts_file))
+       if (!file_exists(git_path_opts_file()))
                return;
-       if (git_config_from_file(populate_opts_cb, opts_file, *opts_ptr) < 0)
-               die(_("Malformed options sheet: %s"), opts_file);
+       if (git_config_from_file(populate_opts_cb, git_path_opts_file(), *opts_ptr) < 0)
+               die(_("Malformed options sheet: %s"), git_path_opts_file());
 }
 
 static void walk_revs_populate_todo(struct commit_list **todo_list,
@@ -836,31 +834,29 @@ static void walk_revs_populate_todo(struct commit_list **todo_list,
 
 static int create_seq_dir(void)
 {
-       const char *seq_dir = git_path(SEQ_DIR);
-
-       if (file_exists(seq_dir)) {
+       if (file_exists(git_path_seq_dir())) {
                error(_("a cherry-pick or revert is already in progress"));
                advise(_("try \"git cherry-pick (--continue | --quit | --abort)\""));
                return -1;
        }
-       else if (mkdir(seq_dir, 0777) < 0)
-               die_errno(_("Could not create sequencer directory %s"), seq_dir);
+       else if (mkdir(git_path_seq_dir(), 0777) < 0)
+               die_errno(_("Could not create sequencer directory %s"),
+                         git_path_seq_dir());
        return 0;
 }
 
 static void save_head(const char *head)
 {
-       const char *head_file = git_path(SEQ_HEAD_FILE);
        static struct lock_file head_lock;
        struct strbuf buf = STRBUF_INIT;
        int fd;
 
-       fd = hold_lock_file_for_update(&head_lock, head_file, LOCK_DIE_ON_ERROR);
+       fd = hold_lock_file_for_update(&head_lock, git_path_head_file(), LOCK_DIE_ON_ERROR);
        strbuf_addf(&buf, "%s\n", head);
        if (write_in_full(fd, buf.buf, buf.len) < 0)
-               die_errno(_("Could not write to %s"), head_file);
+               die_errno(_("Could not write to %s"), git_path_head_file());
        if (commit_lock_file(&head_lock) < 0)
-               die(_("Error wrapping up %s."), head_file);
+               die(_("Error wrapping up %s."), git_path_head_file());
 }
 
 static int reset_for_rollback(const unsigned char *sha1)
@@ -877,8 +873,8 @@ static int rollback_single_pick(void)
 {
        unsigned char head_sha1[20];
 
-       if (!file_exists(git_path("CHERRY_PICK_HEAD")) &&
-           !file_exists(git_path("REVERT_HEAD")))
+       if (!file_exists(git_path_cherry_pick_head()) &&
+           !file_exists(git_path_revert_head()))
                return error(_("no cherry-pick or revert in progress"));
        if (read_ref_full("HEAD", 0, head_sha1, NULL))
                return error(_("cannot resolve HEAD"));
@@ -889,13 +885,11 @@ static int rollback_single_pick(void)
 
 static int sequencer_rollback(struct replay_opts *opts)
 {
-       const char *filename;
        FILE *f;
        unsigned char sha1[20];
        struct strbuf buf = STRBUF_INIT;
 
-       filename = git_path(SEQ_HEAD_FILE);
-       f = fopen(filename, "r");
+       f = fopen(git_path_head_file(), "r");
        if (!f && errno == ENOENT) {
                /*
                 * There is no multiple-cherry-pick in progress.
@@ -905,18 +899,18 @@ static int sequencer_rollback(struct replay_opts *opts)
                return rollback_single_pick();
        }
        if (!f)
-               return error(_("cannot open %s: %s"), filename,
+               return error(_("cannot open %s: %s"), git_path_head_file(),
                                                strerror(errno));
        if (strbuf_getline(&buf, f, '\n')) {
-               error(_("cannot read %s: %s"), filename, ferror(f) ?
-                       strerror(errno) : _("unexpected end of file"));
+               error(_("cannot read %s: %s"), git_path_head_file(),
+                     ferror(f) ?  strerror(errno) : _("unexpected end of file"));
                fclose(f);
                goto fail;
        }
        fclose(f);
        if (get_sha1_hex(buf.buf, sha1) || buf.buf[40] != '\0') {
                error(_("stored pre-cherry-pick HEAD file '%s' is corrupt"),
-                       filename);
+                       git_path_head_file());
                goto fail;
        }
        if (reset_for_rollback(sha1))
@@ -931,28 +925,27 @@ static int sequencer_rollback(struct replay_opts *opts)
 
 static void save_todo(struct commit_list *todo_list, struct replay_opts *opts)
 {
-       const char *todo_file = git_path(SEQ_TODO_FILE);
        static struct lock_file todo_lock;
        struct strbuf buf = STRBUF_INIT;
        int fd;
 
-       fd = hold_lock_file_for_update(&todo_lock, todo_file, LOCK_DIE_ON_ERROR);
+       fd = hold_lock_file_for_update(&todo_lock, git_path_todo_file(), LOCK_DIE_ON_ERROR);
        if (format_todo(&buf, todo_list, opts) < 0)
-               die(_("Could not format %s."), todo_file);
+               die(_("Could not format %s."), git_path_todo_file());
        if (write_in_full(fd, buf.buf, buf.len) < 0) {
                strbuf_release(&buf);
-               die_errno(_("Could not write to %s"), todo_file);
+               die_errno(_("Could not write to %s"), git_path_todo_file());
        }
        if (commit_lock_file(&todo_lock) < 0) {
                strbuf_release(&buf);
-               die(_("Error wrapping up %s."), todo_file);
+               die(_("Error wrapping up %s."), git_path_todo_file());
        }
        strbuf_release(&buf);
 }
 
 static void save_opts(struct replay_opts *opts)
 {
-       const char *opts_file = git_path(SEQ_OPTS_FILE);
+       const char *opts_file = git_path_opts_file();
 
        if (opts->no_commit)
                git_config_set_in_file(opts_file, "options.no-commit", "true");
@@ -1013,8 +1006,8 @@ static int continue_single_pick(void)
 {
        const char *argv[] = { "commit", NULL };
 
-       if (!file_exists(git_path("CHERRY_PICK_HEAD")) &&
-           !file_exists(git_path("REVERT_HEAD")))
+       if (!file_exists(git_path_cherry_pick_head()) &&
+           !file_exists(git_path_revert_head()))
                return error(_("no cherry-pick or revert in progress"));
        return run_command_v_opt(argv, RUN_GIT_CMD);
 }
@@ -1023,14 +1016,14 @@ static int sequencer_continue(struct replay_opts *opts)
 {
        struct commit_list *todo_list = NULL;
 
-       if (!file_exists(git_path(SEQ_TODO_FILE)))
+       if (!file_exists(git_path_todo_file()))
                return continue_single_pick();
        read_populate_opts(&opts);
        read_populate_todo(&todo_list, opts);
 
        /* Verify that the conflict has been resolved */
-       if (file_exists(git_path("CHERRY_PICK_HEAD")) ||
-           file_exists(git_path("REVERT_HEAD"))) {
+       if (file_exists(git_path_cherry_pick_head()) ||
+           file_exists(git_path_revert_head())) {
                int ret = continue_single_pick();
                if (ret)
                        return ret;
diff --git a/setup.c b/setup.c
index 82c0cc2a13bfeae3ec364dce2438fb4c67e46a35..5f9f07dcdb059ad6aeac8d90ee18aedf56a8f85e 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -411,35 +411,58 @@ static void update_linked_gitdir(const char *gitfile, const char *gitdir)
 /*
  * Try to read the location of the git directory from the .git file,
  * return path to git directory if found.
+ *
+ * On failure, if return_error_code is not NULL, return_error_code
+ * will be set to an error code and NULL will be returned. If
+ * return_error_code is NULL the function will die instead (for most
+ * cases).
  */
-const char *read_gitfile(const char *path)
+const char *read_gitfile_gently(const char *path, int *return_error_code)
 {
-       char *buf;
-       char *dir;
+       const int max_file_size = 1 << 20;  /* 1MB */
+       int error_code = 0;
+       char *buf = NULL;
+       char *dir = NULL;
        const char *slash;
        struct stat st;
        int fd;
        ssize_t len;
 
-       if (stat(path, &st))
-               return NULL;
-       if (!S_ISREG(st.st_mode))
-               return NULL;
+       if (stat(path, &st)) {
+               error_code = READ_GITFILE_ERR_STAT_FAILED;
+               goto cleanup_return;
+       }
+       if (!S_ISREG(st.st_mode)) {
+               error_code = READ_GITFILE_ERR_NOT_A_FILE;
+               goto cleanup_return;
+       }
+       if (st.st_size > max_file_size) {
+               error_code = READ_GITFILE_ERR_TOO_LARGE;
+               goto cleanup_return;
+       }
        fd = open(path, O_RDONLY);
-       if (fd < 0)
-               die_errno("Error opening '%s'", path);
+       if (fd < 0) {
+               error_code = READ_GITFILE_ERR_OPEN_FAILED;
+               goto cleanup_return;
+       }
        buf = xmalloc(st.st_size + 1);
        len = read_in_full(fd, buf, st.st_size);
        close(fd);
-       if (len != st.st_size)
-               die("Error reading %s", path);
+       if (len != st.st_size) {
+               error_code = READ_GITFILE_ERR_READ_FAILED;
+               goto cleanup_return;
+       }
        buf[len] = '\0';
-       if (!starts_with(buf, "gitdir: "))
-               die("Invalid gitfile format: %s", path);
+       if (!starts_with(buf, "gitdir: ")) {
+               error_code = READ_GITFILE_ERR_INVALID_FORMAT;
+               goto cleanup_return;
+       }
        while (buf[len - 1] == '\n' || buf[len - 1] == '\r')
                len--;
-       if (len < 9)
-               die("No path in gitfile: %s", path);
+       if (len < 9) {
+               error_code = READ_GITFILE_ERR_NO_PATH;
+               goto cleanup_return;
+       }
        buf[len] = '\0';
        dir = buf + 8;
 
@@ -453,15 +476,41 @@ const char *read_gitfile(const char *path)
                free(buf);
                buf = dir;
        }
-
-       if (!is_git_directory(dir))
-               die("Not a git repository: %s", dir);
-
+       if (!is_git_directory(dir)) {
+               error_code = READ_GITFILE_ERR_NOT_A_REPO;
+               goto cleanup_return;
+       }
        update_linked_gitdir(path, dir);
        path = real_path(dir);
 
+cleanup_return:
+       if (return_error_code)
+               *return_error_code = error_code;
+       else if (error_code) {
+               switch (error_code) {
+               case READ_GITFILE_ERR_STAT_FAILED:
+               case READ_GITFILE_ERR_NOT_A_FILE:
+                       /* non-fatal; follow return path */
+                       break;
+               case READ_GITFILE_ERR_OPEN_FAILED:
+                       die_errno("Error opening '%s'", path);
+               case READ_GITFILE_ERR_TOO_LARGE:
+                       die("Too large to be a .git file: '%s'", path);
+               case READ_GITFILE_ERR_READ_FAILED:
+                       die("Error reading %s", path);
+               case READ_GITFILE_ERR_INVALID_FORMAT:
+                       die("Invalid gitfile format: %s", path);
+               case READ_GITFILE_ERR_NO_PATH:
+                       die("No path in gitfile: %s", path);
+               case READ_GITFILE_ERR_NOT_A_REPO:
+                       die("Not a git repository: %s", dir);
+               default:
+                       assert(0);
+               }
+       }
+
        free(buf);
-       return path;
+       return error_code ? NULL : path;
 }
 
 static const char *setup_explicit_git_dir(const char *gitdirenv,
index 1cee4384225fb9fab1a4b2a4949c8f730eb125ae..ea78ba7cf062c92fe78e67ff4c0f5a0607e6aebd 100644 (file)
@@ -404,13 +404,46 @@ void read_info_alternates(const char * relative_base, int depth)
 void add_to_alternates_file(const char *reference)
 {
        struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
-       int fd = hold_lock_file_for_append(lock, git_path("objects/info/alternates"), LOCK_DIE_ON_ERROR);
-       const char *alt = mkpath("%s\n", reference);
-       write_or_die(fd, alt, strlen(alt));
-       if (commit_lock_file(lock))
-               die("could not close alternates file");
-       if (alt_odb_tail)
-               link_alt_odb_entries(alt, strlen(alt), '\n', NULL, 0);
+       char *alts = git_pathdup("objects/info/alternates");
+       FILE *in, *out;
+
+       hold_lock_file_for_update(lock, alts, LOCK_DIE_ON_ERROR);
+       out = fdopen_lock_file(lock, "w");
+       if (!out)
+               die_errno("unable to fdopen alternates lockfile");
+
+       in = fopen(alts, "r");
+       if (in) {
+               struct strbuf line = STRBUF_INIT;
+               int found = 0;
+
+               while (strbuf_getline(&line, in, '\n') != EOF) {
+                       if (!strcmp(reference, line.buf)) {
+                               found = 1;
+                               break;
+                       }
+                       fprintf_or_die(out, "%s\n", line.buf);
+               }
+
+               strbuf_release(&line);
+               fclose(in);
+
+               if (found) {
+                       rollback_lock_file(lock);
+                       lock = NULL;
+               }
+       }
+       else if (errno != ENOENT)
+               die_errno("unable to read alternates file");
+
+       if (lock) {
+               fprintf_or_die(out, "%s\n", reference);
+               if (commit_lock_file(lock))
+                       die_errno("unable to move new alternates file into place");
+               if (alt_odb_tail)
+                       link_alt_odb_entries(reference, strlen(reference), '\n', NULL, 0);
+       }
+       free(alts);
 }
 
 int foreach_alt_odb(alt_odb_fn fn, void *cb)
@@ -2908,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;
 
@@ -3085,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)
index e57513e61032fb851a669255c30d8417f66c81f9..da6874c15ec51964b6044ae96fc88c282decb0cc 100644 (file)
@@ -576,7 +576,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1,
                                if (!(flags & GET_SHA1_QUIETLY)) {
                                        warning("Log for '%.*s' only goes "
                                                "back to %s.", len, str,
-                                               show_date(co_time, co_tz, DATE_RFC2822));
+                                               show_date(co_time, co_tz, DATE_MODE(RFC2822)));
                                }
                        } else {
                                if (flags & GET_SHA1_QUIETLY) {
index 257d8115c749be6bedc1ce5034e1011c58283800..4ded4a8cde45e01bdd2d551ccf45226e82f1ff4c 100644 (file)
--- a/shallow.c
+++ b/shallow.c
@@ -48,7 +48,7 @@ int is_repository_shallow(void)
                return is_shallow;
 
        if (!path)
-               path = git_path("shallow");
+               path = git_path_shallow();
        /*
         * fetch-pack sets '--shallow-file ""' as an indicator that no
         * shallow file should be used. We could just open it and it
@@ -142,7 +142,7 @@ static void check_shallow_file_for_update(void)
        if (is_shallow == -1)
                die("BUG: shallow must be initialized by now");
 
-       if (!stat_validity_check(&shallow_stat, git_path("shallow")))
+       if (!stat_validity_check(&shallow_stat, git_path_shallow()))
                die("shallow file has changed since we read it");
 }
 
@@ -261,7 +261,7 @@ void setup_alternate_shallow(struct lock_file *shallow_lock,
        struct strbuf sb = STRBUF_INIT;
        int fd;
 
-       fd = hold_lock_file_for_update(shallow_lock, git_path("shallow"),
+       fd = hold_lock_file_for_update(shallow_lock, git_path_shallow(),
                                       LOCK_DIE_ON_ERROR);
        check_shallow_file_for_update();
        if (write_shallow_commits(&sb, 0, extra)) {
@@ -308,7 +308,7 @@ void prune_shallow(int show_only)
                strbuf_release(&sb);
                return;
        }
-       fd = hold_lock_file_for_update(&shallow_lock, git_path("shallow"),
+       fd = hold_lock_file_for_update(&shallow_lock, git_path_shallow(),
                                       LOCK_DIE_ON_ERROR);
        check_shallow_file_for_update();
        if (write_shallow_commits_1(&sb, 0, NULL, SEEN_ONLY)) {
@@ -317,7 +317,7 @@ void prune_shallow(int show_only)
                                  shallow_lock.filename.buf);
                commit_lock_file(&shallow_lock);
        } else {
-               unlink(git_path("shallow"));
+               unlink(git_path_shallow());
                rollback_lock_file(&shallow_lock);
        }
        strbuf_release(&sb);
index bbaf32eef6b0b36ea52390be3aee2ffab7f24f13..29df55b1b0f36522be7e141e9f29ba2bfcc978c1 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -364,19 +364,19 @@ ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint)
 
        strbuf_grow(sb, hint ? hint : 8192);
        for (;;) {
-               ssize_t cnt;
+               ssize_t want = sb->alloc - sb->len - 1;
+               ssize_t got = read_in_full(fd, sb->buf + sb->len, want);
 
-               cnt = xread(fd, sb->buf + sb->len, sb->alloc - sb->len - 1);
-               if (cnt < 0) {
+               if (got < 0) {
                        if (oldalloc == 0)
                                strbuf_release(sb);
                        else
                                strbuf_setlen(sb, oldlen);
                        return -1;
                }
-               if (!cnt)
+               sb->len += got;
+               if (got < want)
                        break;
-               sb->len += cnt;
                strbuf_grow(sb, 8192);
        }
 
@@ -710,3 +710,36 @@ char *xstrfmt(const char *fmt, ...)
 
        return ret;
 }
+
+void strbuf_addftime(struct strbuf *sb, const char *fmt, const struct tm *tm)
+{
+       size_t hint = 128;
+       size_t len;
+
+       if (!*fmt)
+               return;
+
+       strbuf_grow(sb, hint);
+       len = strftime(sb->buf + sb->len, sb->alloc - sb->len, fmt, tm);
+
+       if (!len) {
+               /*
+                * strftime reports "0" if it could not fit the result in the buffer.
+                * Unfortunately, it also reports "0" if the requested time string
+                * takes 0 bytes. So our strategy is to munge the format so that the
+                * output contains at least one character, and then drop the extra
+                * character before returning.
+                */
+               struct strbuf munged_fmt = STRBUF_INIT;
+               strbuf_addf(&munged_fmt, "%s ", fmt);
+               while (!len) {
+                       hint *= 2;
+                       strbuf_grow(sb, hint);
+                       len = strftime(sb->buf + sb->len, sb->alloc - sb->len,
+                                      munged_fmt.buf, tm);
+               }
+               strbuf_release(&munged_fmt);
+               len--; /* drop munged space */
+       }
+       strbuf_setlen(sb, sb->len + len);
+}
index 4a48c0031a7c0df6a7efdb2151d5701dd256fa28..aef2794651985b5c4177690f69a1ff0aac612d18 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -344,6 +344,11 @@ extern void strbuf_commented_addf(struct strbuf *sb, const char *fmt, ...);
 __attribute__((format (printf,2,0)))
 extern void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap);
 
+/**
+ * Add the time specified by `tm`, as formatted by `strftime`.
+ */
+extern void strbuf_addftime(struct strbuf *sb, const char *fmt, const struct tm *tm);
+
 /**
  * Read a given size of data from a FILE* pointer to the buffer.
  *
index 15e90d1c10ebc9ecb0825f731b33d81cf3d9b4d5..700bbf4fcb4cd93b79304d934bed8901c63a853a 100644 (file)
@@ -993,7 +993,7 @@ static void print_commit(struct commit *commit)
 {
        struct strbuf sb = STRBUF_INIT;
        struct pretty_print_context ctx = {0};
-       ctx.date_mode = DATE_NORMAL;
+       ctx.date_mode.type = DATE_NORMAL;
        format_commit_message(commit, " %h: %m %s", &sb, &ctx);
        fprintf(stderr, "%s\n", sb.buf);
        strbuf_release(&sb);
index 6bd252212a7eb901dded2a460eebe92aa3b6212b..9a96e1566d016e960f79875eb4edba2ffb7c5244 100644 (file)
@@ -14,7 +14,7 @@
 #       specified line.
 #
 #   "<cmd> <lineno>" -- add a line with the specified command
-#       ("squash", "fixup", "edit", or "reword") and the SHA1 taken
+#       ("squash", "fixup", "edit", "reword" or "drop") and the SHA1 taken
 #       from the specified line.
 #
 #   "exec_cmd_with_args" -- add an "exec cmd with args" line.
@@ -46,7 +46,7 @@ set_fake_editor () {
        action=pick
        for line in $FAKE_LINES; do
                case $line in
-               squash|fixup|edit|reword)
+               squash|fixup|edit|reword|drop)
                        action="$line";;
                exec*)
                        echo "$line" | sed 's/_/ /g' >> "$1";;
@@ -54,6 +54,11 @@ set_fake_editor () {
                        echo '# comment' >> "$1";;
                ">")
                        echo >> "$1";;
+               bad)
+                       action="badcmd";;
+               fakesha)
+                       echo "$action XXXXXXX False commit" >> "$1"
+                       action=pick;;
                *)
                        sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1"
                        action=pick;;
diff --git a/t/perf/p7300-clean.sh b/t/perf/p7300-clean.sh
new file mode 100755 (executable)
index 0000000..ec94cdd
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+test_description="Test git-clean performance"
+
+. ./perf-lib.sh
+
+test_perf_default_repo
+test_checkout_worktree
+
+test_expect_success 'setup untracked directory with many sub dirs' '
+       rm -rf 500_sub_dirs 100000_sub_dirs clean_test_dir &&
+       mkdir 500_sub_dirs 100000_sub_dirs clean_test_dir &&
+       for i in $(test_seq 1 500)
+       do
+               mkdir 500_sub_dirs/dir$i || return $?
+       done &&
+       for i in $(test_seq 1 200)
+       do
+               cp -r 500_sub_dirs 100000_sub_dirs/dir$i || return $?
+       done
+'
+
+test_perf 'clean many untracked sub dirs, check for nested git' '
+       git clean -n -q -f -d 100000_sub_dirs/
+'
+
+test_perf 'clean many untracked sub dirs, ignore nested git' '
+       git clean -n -q -f -f -d 100000_sub_dirs/
+'
+
+test_done
index 37e9396e5dae8957ba73507018d5c334ee65e30d..9393322c3e7028ea7074d2b1ec50fbb293c6728f 100755 (executable)
@@ -99,4 +99,21 @@ test_expect_success 'check rev-list' '
        test "$SHA" = "$(git rev-list HEAD)"
 '
 
+test_expect_success 'setup_git_dir twice in subdir' '
+       git init sgd &&
+       (
+               cd sgd &&
+               git config alias.lsfi ls-files &&
+               mv .git .realgit &&
+               echo "gitdir: .realgit" >.git &&
+               mkdir subdir &&
+               cd subdir &&
+               >foo &&
+               git add foo &&
+               git lsfi >actual &&
+               echo foo >expected &&
+               test_cmp expected actual
+       )
+'
+
 test_done
index b0447851750e3f196ea2da90f84ce31fefce30ae..9be6411104999959ff93276ec2bbfb065bfa71e7 100755 (executable)
@@ -19,6 +19,7 @@ usage: test-parse-options <options>
 
     -i, --integer <n>     get a integer
     -j <n>                get a integer, too
+    -m, --magnitude <n>   get a magnitude
     --set23               set integer to 23
     -t <time>             get timestamp of <time>
     -L, --length <str>    get length of <str>
@@ -58,6 +59,7 @@ mv expect expect.err
 cat >expect.template <<EOF
 boolean: 0
 integer: 0
+magnitude: 0
 timestamp: 0
 string: (not set)
 abbrev: 7
@@ -132,9 +134,32 @@ test_expect_success 'OPT_BOOL() no negation #2' 'check_unknown_i18n --no-no-fear
 
 test_expect_success 'OPT_BOOL() positivation' 'check boolean: 0 -D --doubt'
 
+test_expect_success 'OPT_INT() negative' 'check integer: -2345 -i -2345'
+
+test_expect_success 'OPT_MAGNITUDE() simple' '
+       check magnitude: 2345678 -m 2345678
+'
+
+test_expect_success 'OPT_MAGNITUDE() kilo' '
+       check magnitude: 239616 -m 234k
+'
+
+test_expect_success 'OPT_MAGNITUDE() mega' '
+       check magnitude: 104857600 -m 100m
+'
+
+test_expect_success 'OPT_MAGNITUDE() giga' '
+       check magnitude: 1073741824 -m 1g
+'
+
+test_expect_success 'OPT_MAGNITUDE() 3giga' '
+       check magnitude: 3221225472 -m 3g
+'
+
 cat > expect << EOF
 boolean: 2
 integer: 1729
+magnitude: 16384
 timestamp: 0
 string: 123
 abbrev: 7
@@ -145,8 +170,8 @@ file: prefix/my.file
 EOF
 
 test_expect_success 'short options' '
-       test-parse-options -s123 -b -i 1729 -b -vv -n -F my.file \
-       > output 2> output.err &&
+       test-parse-options -s123 -b -i 1729 -m 16k -b -vv -n -F my.file \
+       >output 2>output.err &&
        test_cmp expect output &&
        test_must_be_empty output.err
 '
@@ -154,6 +179,7 @@ test_expect_success 'short options' '
 cat > expect << EOF
 boolean: 2
 integer: 1729
+magnitude: 16384
 timestamp: 0
 string: 321
 abbrev: 10
@@ -164,9 +190,10 @@ file: prefix/fi.le
 EOF
 
 test_expect_success 'long options' '
-       test-parse-options --boolean --integer 1729 --boolean --string2=321 \
-               --verbose --verbose --no-dry-run --abbrev=10 --file fi.le\
-               --obsolete > output 2> output.err &&
+       test-parse-options --boolean --integer 1729 --magnitude 16k \
+               --boolean --string2=321 --verbose --verbose --no-dry-run \
+               --abbrev=10 --file fi.le --obsolete \
+               >output 2>output.err &&
        test_must_be_empty output.err &&
        test_cmp expect output
 '
@@ -180,6 +207,7 @@ test_expect_success 'missing required value' '
 cat > expect << EOF
 boolean: 1
 integer: 13
+magnitude: 0
 timestamp: 0
 string: 123
 abbrev: 7
@@ -202,6 +230,7 @@ test_expect_success 'intermingled arguments' '
 cat > expect << EOF
 boolean: 0
 integer: 2
+magnitude: 0
 timestamp: 0
 string: (not set)
 abbrev: 7
@@ -230,6 +259,7 @@ test_expect_success 'ambiguously abbreviated option' '
 cat > expect << EOF
 boolean: 0
 integer: 0
+magnitude: 0
 timestamp: 0
 string: 123
 abbrev: 7
@@ -268,6 +298,7 @@ test_expect_success 'detect possible typos' '
 cat > expect <<EOF
 boolean: 0
 integer: 0
+magnitude: 0
 timestamp: 0
 string: (not set)
 abbrev: 7
@@ -287,6 +318,7 @@ test_expect_success 'keep some options as arguments' '
 cat > expect <<EOF
 boolean: 0
 integer: 0
+magnitude: 0
 timestamp: 1
 string: (not set)
 abbrev: 7
@@ -308,6 +340,7 @@ cat > expect <<EOF
 Callback: "four", 0
 boolean: 5
 integer: 4
+magnitude: 0
 timestamp: 0
 string: (not set)
 abbrev: 7
@@ -336,6 +369,7 @@ test_expect_success 'OPT_CALLBACK() and callback errors work' '
 cat > expect <<EOF
 boolean: 1
 integer: 23
+magnitude: 0
 timestamp: 0
 string: (not set)
 abbrev: 7
@@ -360,6 +394,7 @@ test_expect_success 'OPT_NEGBIT() and OPT_SET_INT() work' '
 cat > expect <<EOF
 boolean: 6
 integer: 0
+magnitude: 0
 timestamp: 0
 string: (not set)
 abbrev: 7
@@ -390,6 +425,7 @@ test_expect_success 'OPT_COUNTUP() with PARSE_OPT_NODASH works' '
 cat > expect <<EOF
 boolean: 0
 integer: 12345
+magnitude: 0
 timestamp: 0
 string: (not set)
 abbrev: 7
@@ -408,6 +444,7 @@ test_expect_success 'OPT_NUMBER_CALLBACK() works' '
 cat >expect <<EOF
 boolean: 0
 integer: 0
+magnitude: 0
 timestamp: 0
 string: (not set)
 abbrev: 7
index 601d02d71f735b4247528cc45b0ea055299ee7a0..055cc19000784bdb820e7ae53ba48987ead0367a 100755 (executable)
@@ -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 &&
index 93a4794930bfebc99504a245392f32a3c92b3dde..4f38078ff36f5a877defc360feaf6fdba804ccc1 100755 (executable)
@@ -547,4 +547,30 @@ test_expect_success 'git cat-file --batch --follow-symlink returns correct sha a
        test_cmp expect actual
 '
 
+test_expect_success 'cat-file --batch-all-objects shows all objects' '
+       # make new repos so we know the full set of objects; we will
+       # also make sure that there are some packed and some loose
+       # objects, some referenced and some not, and that there are
+       # some available only via alternates.
+       git init all-one &&
+       (
+               cd all-one &&
+               echo content >file &&
+               git add file &&
+               git commit -qm base &&
+               git rev-parse HEAD HEAD^{tree} HEAD:file &&
+               git repack -ad &&
+               echo not-cloned | git hash-object -w --stdin
+       ) >expect.unsorted &&
+       git clone -s all-one all-two &&
+       (
+               cd all-two &&
+               echo local-unref | git hash-object -w --stdin
+       ) >>expect.unsorted &&
+       sort <expect.unsorted >expect &&
+       git -C all-two cat-file --batch-all-objects \
+                               --batch-check="%(objectname)" >actual &&
+       test_cmp expect actual
+'
+
 test_done
diff --git a/t/t1090-sparse-checkout-scope.sh b/t/t1090-sparse-checkout-scope.sh
new file mode 100755 (executable)
index 0000000..1f61eb3
--- /dev/null
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+test_description='sparse checkout scope tests'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       echo "initial" >a &&
+       echo "initial" >b &&
+       echo "initial" >c &&
+       git add a b c &&
+       git commit -m "initial commit"
+'
+
+test_expect_success 'create feature branch' '
+       git checkout -b feature &&
+       echo "modified" >b &&
+       echo "modified" >c &&
+       git add b c &&
+       git commit -m "modification"
+'
+
+test_expect_success 'perform sparse checkout of master' '
+       git config --local --bool core.sparsecheckout true &&
+       echo "!/*" >.git/info/sparse-checkout &&
+       echo "/a" >>.git/info/sparse-checkout &&
+       echo "/c" >>.git/info/sparse-checkout &&
+       git checkout master &&
+       test_path_is_file a &&
+       test_path_is_missing b &&
+       test_path_is_file c
+'
+
+test_expect_success 'merge feature branch into sparse checkout of master' '
+       git merge feature &&
+       test_path_is_file a &&
+       test_path_is_missing b &&
+       test_path_is_file c &&
+       test "$(cat c)" = "modified"
+'
+
+test_expect_success 'return to full checkout of master' '
+       git checkout feature &&
+       echo "/*" >.git/info/sparse-checkout &&
+       git checkout master &&
+       test_path_is_file a &&
+       test_path_is_file b &&
+       test_path_is_file c &&
+       test "$(cat b)" = "modified"
+'
+
+test_done
index d787bf50f801442b52aa914b5318359f2ecd6d2f..97406fa4b137afdd99e64172d956718005844120 100755 (executable)
@@ -23,6 +23,7 @@ test_expect_success setup '
 m=refs/heads/master
 n_dir=refs/heads/gu
 n=$n_dir/fixes
+outside=foo
 
 test_expect_success \
        "create $m" \
@@ -74,6 +75,24 @@ test_expect_success "delete $m (by HEAD)" '
 '
 rm -f .git/$m
 
+test_expect_success 'update-ref does not create reflogs by default' '
+       test_when_finished "git update-ref -d $outside" &&
+       git update-ref $outside $A &&
+       git rev-parse $A >expect &&
+       git rev-parse $outside >actual &&
+       test_cmp expect actual &&
+       test_must_fail git reflog exists $outside
+'
+
+test_expect_success 'update-ref creates reflogs with --create-reflog' '
+       test_when_finished "git update-ref -d $outside" &&
+       git update-ref --create-reflog $outside $A &&
+       git rev-parse $A >expect &&
+       git rev-parse $outside >actual &&
+       test_cmp expect actual &&
+       git reflog exists $outside
+'
+
 test_expect_success \
        "create $m (by HEAD)" \
        "git update-ref HEAD $A &&
@@ -155,12 +174,11 @@ test_expect_success "(not) changed .git/$m" "
 '
 rm -f .git/$m
 
-: a repository with working tree always has reflog these days...
-: >.git/logs/refs/heads/master
+rm -f .git/logs/refs/heads/master
 test_expect_success \
        "create $m (logged by touch)" \
        'GIT_COMMITTER_DATE="2005-05-26 23:30" \
-        git update-ref HEAD '"$A"' -m "Initial Creation" &&
+        git update-ref --create-reflog HEAD '"$A"' -m "Initial Creation" &&
         test '"$A"' = $(cat .git/'"$m"')'
 test_expect_success \
        "update $m (logged by touch)" \
@@ -472,6 +490,25 @@ test_expect_success 'stdin create ref works' '
        test_cmp expect actual
 '
 
+test_expect_success 'stdin does not create reflogs by default' '
+       test_when_finished "git update-ref -d $outside" &&
+       echo "create $outside $m" >stdin &&
+       git update-ref --stdin <stdin &&
+       git rev-parse $m >expect &&
+       git rev-parse $outside >actual &&
+       test_cmp expect actual &&
+       test_must_fail git reflog exists $outside
+'
+
+test_expect_success 'stdin creates reflogs with --create-reflog' '
+       echo "create $outside $m" >stdin &&
+       git update-ref --create-reflog --stdin <stdin &&
+       git rev-parse $m >expect &&
+       git rev-parse $outside >actual &&
+       test_cmp expect actual &&
+       git reflog exists $outside
+'
+
 test_expect_success 'stdin succeeds with quoted argument' '
        git update-ref -d $a &&
        echo "create $a \"$m\"" >stdin &&
index e5dc62e9efbf9c26bad93c7384f9fb9fa6a970e8..0790edf60de2d0324ea4752087f03db1113d822e 100755 (executable)
@@ -62,9 +62,11 @@ invalid_ref 'heads/foo\bar'
 invalid_ref "$(printf 'heads/foo\t')"
 invalid_ref "$(printf 'heads/foo\177')"
 valid_ref "$(printf 'heads/fu\303\237')"
-invalid_ref 'heads/*foo/bar' --refspec-pattern
-invalid_ref 'heads/foo*/bar' --refspec-pattern
-invalid_ref 'heads/f*o/bar' --refspec-pattern
+valid_ref 'heads/*foo/bar' --refspec-pattern
+valid_ref 'heads/foo*/bar' --refspec-pattern
+valid_ref 'heads/f*o/bar' --refspec-pattern
+invalid_ref 'heads/f*o*/bar' --refspec-pattern
+invalid_ref 'heads/foo*/bar*' --refspec-pattern
 
 ref='foo'
 invalid_ref "$ref"
index 779d4e3829b29d7092d54f6e342f923f0d239b96..b79049f6f606f106e40dfdc241d7a168b10b88cb 100755 (executable)
@@ -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
index 6f47c0dd0ec9b5e59205ca98eea9bd729605e004..6ac7734d79be21a82feeadff10064bb4ca7ad47b 100755 (executable)
@@ -138,7 +138,7 @@ test_expect_success '--date magic does not override explicit @{0} syntax' '
 : >expect
 test_expect_success 'empty reflog file' '
        git branch empty &&
-       : >.git/logs/refs/heads/empty &&
+       git reflog expire --expire=all refs/heads/empty &&
 
        git log -g empty >actual &&
        test_cmp expect actual
@@ -166,4 +166,9 @@ test_expect_success 'git log -g -p shows diffs vs. parents' '
        test_cmp expect actual
 '
 
+test_expect_success 'reflog exists works' '
+       git reflog exists refs/heads/master &&
+       ! git reflog exists refs/heads/nonexistent
+'
+
 test_done
index cfb32b62420dc162e79aaea29a36f7d78f3353f5..956673b8a14d39ef6bece7629b2407b0c10a071a 100755 (executable)
@@ -231,8 +231,8 @@ test_expect_success 'tag with incorrect tag name & missing tagger' '
        git fsck --tags 2>out &&
 
        cat >expect <<-EOF &&
-       warning in tag $tag: invalid '\''tag'\'' name: wrong name format
-       warning in tag $tag: invalid format - expected '\''tagger'\'' line
+       warning in tag $tag: badTagName: invalid '\''tag'\'' name: wrong name format
+       warning in tag $tag: missingTaggerEntry: invalid format - expected '\''tagger'\'' line
        EOF
        test_cmp expect out
 '
@@ -287,6 +287,17 @@ test_expect_success 'rev-list --verify-objects with bad sha1' '
        grep -q "error: sha1 mismatch 63ffffffffffffffffffffffffffffffffffffff" out
 '
 
+test_expect_success 'force fsck to ignore double author' '
+       git cat-file commit HEAD >basis &&
+       sed "s/^author .*/&,&/" <basis | tr , \\n >multiple-authors &&
+       new=$(git hash-object -t commit -w --stdin <multiple-authors) &&
+       test_when_finished "remove_object $new" &&
+       git update-ref refs/heads/bogus "$new" &&
+       test_when_finished "git update-ref -d refs/heads/bogus" &&
+       test_must_fail git fsck &&
+       git -c fsck.multipleAuthors=ignore fsck
+'
+
 _bz='\0'
 _bz5="$_bz$_bz$_bz$_bz$_bz"
 _bz20="$_bz5$_bz5$_bz5$_bz5"
@@ -420,4 +431,26 @@ test_expect_success 'fsck notices ref pointing to missing tag' '
        test_must_fail git -C missing fsck
 '
 
+test_expect_success 'fsck --connectivity-only' '
+       rm -rf connectivity-only &&
+       git init connectivity-only &&
+       (
+               cd connectivity-only &&
+               touch empty &&
+               git add empty &&
+               test_commit empty &&
+               empty=.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 &&
+               rm -f $empty &&
+               echo invalid >$empty &&
+               test_must_fail git fsck --strict &&
+               git fsck --strict --connectivity-only &&
+               tree=$(git rev-parse HEAD:) &&
+               suffix=${tree#??} &&
+               tree=.git/objects/${tree%$suffix}/$suffix &&
+               rm -f $tree &&
+               echo invalid >$tree &&
+               test_must_fail git fsck --strict --connectivity-only
+       )
+'
+
 test_done
index ebe7c3b87c34755399495a375b9f4b7e0a45b1c1..310f93fd30ea51f94eaa2b4dd2e8d0e398783cba 100755 (executable)
@@ -3,7 +3,40 @@
 test_description='test git rev-parse --parseopt'
 . ./test-lib.sh
 
-sed -e 's/^|//' >expect <<\END_EXPECT
+test_expect_success 'setup optionspec' '
+       sed -e "s/^|//" >optionspec <<\EOF
+|some-command [options] <args>...
+|
+|some-command does foo and bar!
+|--
+|h,help    show the help
+|
+|foo       some nifty option --foo
+|bar=      some cool option --bar with an argument
+|b,baz     a short and long option
+|
+| An option group Header
+|C?        option C with an optional argument
+|d,data?   short and long option with an optional argument
+|
+| Argument hints
+|B=arg     short option required argument
+|bar2=arg  long option required argument
+|e,fuz=with-space  short and long option required argument
+|s?some    short option optional argument
+|long?data long option optional argument
+|g,fluf?path     short and long option optional argument
+|longest=very-long-argument-hint  a very long argument hint
+|pair=key=value  with an equals sign in the hint
+|short-hint=a    with a one symbol hint
+|
+|Extras
+|extra1    line above used to cause a segfault but no longer does
+EOF
+'
+
+test_expect_success 'test --parseopt help output' '
+       sed -e "s/^|//" >expect <<\END_EXPECT &&
 |cat <<\EOF
 |usage: some-command [options] <args>...
 |
@@ -28,49 +61,23 @@ sed -e 's/^|//' >expect <<\END_EXPECT
 |    -g, --fluf[=<path>]   short and long option optional argument
 |    --longest <very-long-argument-hint>
 |                          a very long argument hint
+|    --pair <key=value>    with an equals sign in the hint
+|    --short-hint <a>      with a one symbol hint
 |
 |Extras
 |    --extra1              line above used to cause a segfault but no longer does
 |
 |EOF
 END_EXPECT
-
-sed -e 's/^|//' >optionspec <<\EOF
-|some-command [options] <args>...
-|
-|some-command does foo and bar!
-|--
-|h,help    show the help
-|
-|foo       some nifty option --foo
-|bar=      some cool option --bar with an argument
-|b,baz     a short and long option
-|
-| An option group Header
-|C?        option C with an optional argument
-|d,data?   short and long option with an optional argument
-|
-| Argument hints
-|B=arg     short option required argument
-|bar2=arg  long option required argument
-|e,fuz=with-space  short and long option required argument
-|s?some    short option optional argument
-|long?data long option optional argument
-|g,fluf?path     short and long option optional argument
-|longest=very-long-argument-hint  a very long argument hint
-|
-|Extras
-|extra1    line above used to cause a segfault but no longer does
-EOF
-
-test_expect_success 'test --parseopt help output' '
        test_expect_code 129 git rev-parse --parseopt -- -h > output < optionspec &&
        test_i18ncmp expect output
 '
 
-cat > expect <<EOF
+test_expect_success 'setup expect.1' "
+       cat > expect <<EOF
 set -- --foo --bar 'ham' -b -- 'arg'
 EOF
+"
 
 test_expect_success 'test --parseopt' '
        git rev-parse --parseopt -- --foo --bar=ham --baz arg < optionspec > output &&
@@ -82,9 +89,11 @@ test_expect_success 'test --parseopt with mixed options and arguments' '
        test_cmp expect output
 '
 
-cat > expect <<EOF
+test_expect_success 'setup expect.2' "
+       cat > expect <<EOF
 set -- --foo -- 'arg' '--bar=ham'
 EOF
+"
 
 test_expect_success 'test --parseopt with --' '
        git rev-parse --parseopt -- --foo -- arg --bar=ham < optionspec > output &&
@@ -96,54 +105,66 @@ test_expect_success 'test --parseopt --stop-at-non-option' '
        test_cmp expect output
 '
 
-cat > expect <<EOF
+test_expect_success 'setup expect.3' "
+       cat > expect <<EOF
 set -- --foo -- '--' 'arg' '--bar=ham'
 EOF
+"
 
 test_expect_success 'test --parseopt --keep-dashdash' '
        git rev-parse --parseopt --keep-dashdash -- --foo -- arg --bar=ham < optionspec > output &&
        test_cmp expect output
 '
 
-cat >expect <<EOF
+test_expect_success 'setup expect.4' "
+       cat >expect <<EOF
 set -- --foo -- '--' 'arg' '--spam=ham'
 EOF
+"
 
 test_expect_success 'test --parseopt --keep-dashdash --stop-at-non-option with --' '
        git rev-parse --parseopt --keep-dashdash --stop-at-non-option -- --foo -- arg --spam=ham <optionspec >output &&
        test_cmp expect output
 '
 
-cat > expect <<EOF
+test_expect_success 'setup expect.5' "
+       cat > expect <<EOF
 set -- --foo -- 'arg' '--spam=ham'
 EOF
+"
 
 test_expect_success 'test --parseopt --keep-dashdash --stop-at-non-option without --' '
        git rev-parse --parseopt --keep-dashdash --stop-at-non-option -- --foo arg --spam=ham <optionspec >output &&
        test_cmp expect output
 '
 
-cat > expect <<EOF
+test_expect_success 'setup expect.6' "
+       cat > expect <<EOF
 set -- --foo --bar='z' --baz -C'Z' --data='A' -- 'arg'
 EOF
+"
 
 test_expect_success 'test --parseopt --stuck-long' '
        git rev-parse --parseopt --stuck-long -- --foo --bar=z -b arg -CZ -dA <optionspec >output &&
        test_cmp expect output
 '
 
-cat > expect <<EOF
+test_expect_success 'setup expect.7' "
+       cat > expect <<EOF
 set -- --data='' -C --baz -- 'arg'
 EOF
+"
 
 test_expect_success 'test --parseopt --stuck-long and empty optional argument' '
        git rev-parse --parseopt --stuck-long -- --data= arg -C -b <optionspec >output &&
        test_cmp expect output
 '
 
-cat > expect <<EOF
+test_expect_success 'setup expect.8' "
+       cat > expect <<EOF
 set -- --data --baz -- 'arg'
 EOF
+"
 
 test_expect_success 'test --parseopt --stuck-long and long option with unset optional argument' '
        git rev-parse --parseopt --stuck-long -- --data arg -b <optionspec >output &&
index 823fe1d79924b68950a59ffde902b16db29205b6..ab27d0db5c5577a23e1053b58eefa63015526d0c 100755 (executable)
@@ -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 &&
index b6977d4b390ec9a3f4f3b621db88908a2c40f689..553a3f601ba7c76efa193d4a33823197c05281e7 100755 (executable)
@@ -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 &&
index ead8aa2a9d78c8f6451d19812d48ae5d69e6ee69..8267411a0ec129a67cde5381cfd79824471d8fd2 100755 (executable)
@@ -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
index ddea49808d063f6c169cf8078f5698e34c2ba803..cdaf6f64ec78830def2f082b72604075e15496f6 100755 (executable)
@@ -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' '
index 8aae98d482aa572b3a9a55f9b38280ad716c7baf..7b5b6d452e3762c9d0e311602bf39317200c21a7 100755 (executable)
@@ -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' '
index 467e6c1ed526d4bfffbab2e1479e1334058506de..d26e3f57dcbc18585c360ee78a725708f5a69207 100755 (executable)
@@ -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
 '
 
@@ -1123,4 +1123,127 @@ test_expect_success 'rebase --continue removes CHERRY_PICK_HEAD' '
        test ! -f .git/CHERRY_PICK_HEAD
 '
 
+rebase_setup_and_clean () {
+       test_when_finished "
+               git checkout master &&
+               test_might_fail git branch -D $1 &&
+               test_might_fail git rebase --abort
+       " &&
+       git checkout -b $1 master
+}
+
+test_expect_success 'drop' '
+       rebase_setup_and_clean drop-test &&
+       set_fake_editor &&
+       FAKE_LINES="1 drop 2 3 drop 4 5" git rebase -i --root &&
+       test E = $(git cat-file commit HEAD | sed -ne \$p) &&
+       test C = $(git cat-file commit HEAD^ | sed -ne \$p) &&
+       test A = $(git cat-file commit HEAD^^ | sed -ne \$p)
+'
+
+cat >expect <<EOF
+Successfully rebased and updated refs/heads/missing-commit.
+EOF
+
+test_expect_success 'rebase -i respects rebase.missingCommitsCheck = ignore' '
+       test_config rebase.missingCommitsCheck ignore &&
+       rebase_setup_and_clean missing-commit &&
+       set_fake_editor &&
+       FAKE_LINES="1 2 3 4" \
+               git rebase -i --root 2>actual &&
+       test D = $(git cat-file commit HEAD | sed -ne \$p) &&
+       test_cmp expect actual
+'
+
+cat >expect <<EOF
+Warning: some commits may have been dropped accidentally.
+Dropped commits (newer to older):
+ - $(git rev-list --pretty=oneline --abbrev-commit -1 master)
+To avoid this message, use "drop" to explicitly remove a commit.
+
+Use 'git config rebase.missingCommitsCheck' to change the level of warnings.
+The possible behaviours are: ignore, warn, error.
+
+Successfully rebased and updated refs/heads/missing-commit.
+EOF
+
+test_expect_success 'rebase -i respects rebase.missingCommitsCheck = warn' '
+       test_config rebase.missingCommitsCheck warn &&
+       rebase_setup_and_clean missing-commit &&
+       set_fake_editor &&
+       FAKE_LINES="1 2 3 4" \
+               git rebase -i --root 2>actual &&
+       test_cmp expect actual &&
+       test D = $(git cat-file commit HEAD | sed -ne \$p)
+'
+
+cat >expect <<EOF
+Warning: some commits may have been dropped accidentally.
+Dropped commits (newer to older):
+ - $(git rev-list --pretty=oneline --abbrev-commit -1 master)
+ - $(git rev-list --pretty=oneline --abbrev-commit -1 master~2)
+To avoid this message, use "drop" to explicitly remove a commit.
+
+Use 'git config rebase.missingCommitsCheck' to change the level of warnings.
+The possible behaviours are: ignore, warn, error.
+
+You can fix this with 'git rebase --edit-todo'.
+Or you can abort the rebase with 'git rebase --abort'.
+EOF
+
+test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '
+       test_config rebase.missingCommitsCheck error &&
+       rebase_setup_and_clean missing-commit &&
+       set_fake_editor &&
+       test_must_fail env FAKE_LINES="1 2 4" \
+               git rebase -i --root 2>actual &&
+       test_cmp expect actual &&
+       cp .git/rebase-merge/git-rebase-todo.backup \
+               .git/rebase-merge/git-rebase-todo &&
+       FAKE_LINES="1 2 drop 3 4 drop 5" \
+               git rebase --edit-todo &&
+       git rebase --continue &&
+       test D = $(git cat-file commit HEAD | sed -ne \$p) &&
+       test B = $(git cat-file commit HEAD^ | sed -ne \$p)
+'
+
+cat >expect <<EOF
+Warning: the command isn't recognized in the following line:
+ - badcmd $(git rev-list --oneline -1 master~1)
+
+You can fix this with 'git rebase --edit-todo'.
+Or you can abort the rebase with 'git rebase --abort'.
+EOF
+
+test_expect_success 'static check of bad command' '
+       rebase_setup_and_clean bad-cmd &&
+       set_fake_editor &&
+       test_must_fail env FAKE_LINES="1 2 3 bad 4 5" \
+               git rebase -i --root 2>actual &&
+       test_cmp expect actual &&
+       FAKE_LINES="1 2 3 drop 4 5" git rebase --edit-todo &&
+       git rebase --continue &&
+       test E = $(git cat-file commit HEAD | sed -ne \$p) &&
+       test C = $(git cat-file commit HEAD^ | sed -ne \$p)
+'
+
+cat >expect <<EOF
+Warning: the SHA-1 is missing or isn't a commit in the following line:
+ - edit XXXXXXX False commit
+
+You can fix this with 'git rebase --edit-todo'.
+Or you can abort the rebase with 'git rebase --abort'.
+EOF
+
+test_expect_success 'static check of bad SHA-1' '
+       rebase_setup_and_clean bad-sha &&
+       set_fake_editor &&
+       test_must_fail env FAKE_LINES="1 2 edit fakesha 3 4 5 #" \
+               git rebase -i --root 2>actual &&
+       test_cmp expect actual &&
+       FAKE_LINES="1 2 4 5 6" git rebase --edit-todo &&
+       git rebase --continue &&
+       test E = $(git cat-file commit HEAD | sed -ne \$p)
+'
+
 test_done
index 41370ab998c72e14f269a257304b3e0cc4aebc8c..8f53e54ce4176fd0697e8af60a105d40dc161a12 100755 (executable)
@@ -250,4 +250,25 @@ test_expect_success 'squash! fixup!' '
        test_auto_fixup_fixup squash fixup
 '
 
+test_expect_success 'autosquash with custom inst format' '
+       git reset --hard base &&
+       git config --add rebase.instructionFormat "[%an @ %ar] %s"  &&
+       echo 2 >file1 &&
+       git add -u &&
+       test_tick &&
+       git commit -m "squash! $(git rev-parse --short HEAD^)" &&
+       echo 1 >file1 &&
+       git add -u &&
+       test_tick &&
+       git commit -m "squash! $(git log -n 1 --format=%s HEAD~2)" &&
+       git tag final-squash-instFmt &&
+       test_tick &&
+       git rebase --autosquash -i HEAD~4 &&
+       git log --oneline >actual &&
+       test_line_count = 3 actual &&
+       git diff --exit-code final-squash-instFmt &&
+       test 1 = "$(git cat-file blob HEAD^:file1)" &&
+       test 2 = $(git cat-file commit HEAD^ | grep squash | wc -l)
+'
+
 test_done
index 2680375628207d0d46ee4a9c8335840609461e0a..4428b9086e8bcb383df801834d0de323f316f4fa 100755 (executable)
@@ -40,6 +40,25 @@ test_expect_success 'non-interactive rebase --continue works with touched file'
        git rebase --continue
 '
 
+test_expect_success 'non-interactive rebase --continue with rerere enabled' '
+       test_config rerere.enabled true &&
+       test_when_finished "test_might_fail git rebase --abort" &&
+       git reset --hard commit-new-file-F2-on-topic-branch &&
+       git checkout master &&
+       rm -fr .git/rebase-* &&
+
+       test_must_fail git rebase --onto master master topic &&
+       echo "Resolved" >F2 &&
+       git add F2 &&
+       cp F2 F2.expected &&
+       git rebase --continue &&
+
+       git reset --hard commit-new-file-F2-on-topic-branch &&
+       git checkout master &&
+       test_must_fail git rebase --onto master master topic &&
+       test_cmp F2.expected F2
+'
+
 test_expect_success 'rebase --continue can not be used with other options' '
        test_must_fail git rebase -v --continue &&
        test_must_fail git rebase --continue -v
index 75cf3ff9bdf909b9083266553773884f1f9abb52..509084e1a70b4cd5ee7f56421bb9b8995b0a5d94 100755 (executable)
@@ -251,4 +251,66 @@ test_expect_success 'rebase --merge (L/U)' '
        check_encoding 2 8859
 '
 
+test_expect_success 'am (U/U)' '
+       # Apply UTF-8 patches with UTF-8 commitencoding
+       git config i18n.commitencoding UTF-8 &&
+       . "$TEST_DIRECTORY"/t3901-utf8.txt &&
+
+       git reset --hard master &&
+       git am out-u1 out-u2 &&
+
+       check_encoding 2
+'
+
+test_expect_success !MINGW 'am (L/L)' '
+       # Apply ISO-8859-1 patches with ISO-8859-1 commitencoding
+       git config i18n.commitencoding ISO8859-1 &&
+       . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
+
+       git reset --hard master &&
+       git am out-l1 out-l2 &&
+
+       check_encoding 2 8859
+'
+
+test_expect_success 'am (U/L)' '
+       # Apply ISO-8859-1 patches with UTF-8 commitencoding
+       git config i18n.commitencoding UTF-8 &&
+       . "$TEST_DIRECTORY"/t3901-utf8.txt &&
+       git reset --hard master &&
+
+       # am specifies --utf8 by default.
+       git am out-l1 out-l2 &&
+
+       check_encoding 2
+'
+
+test_expect_success 'am --no-utf8 (U/L)' '
+       # Apply ISO-8859-1 patches with UTF-8 commitencoding
+       git config i18n.commitencoding UTF-8 &&
+       . "$TEST_DIRECTORY"/t3901-utf8.txt &&
+
+       git reset --hard master &&
+       git am --no-utf8 out-l1 out-l2 2>err &&
+
+       # commit-tree will warn that the commit message does not contain valid UTF-8
+       # as mailinfo did not convert it
+       grep "did not conform" err &&
+
+       check_encoding 2
+'
+
+test_expect_success !MINGW 'am (L/U)' '
+       # Apply UTF-8 patches with ISO-8859-1 commitencoding
+       git config i18n.commitencoding ISO8859-1 &&
+       . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
+
+       git reset --hard master &&
+       # mailinfo will re-code the commit message to the charset specified by
+       # i18n.commitencoding
+       git am out-u1 out-u2 &&
+
+       check_encoding 2 8859
+'
+
 test_done
index f5f18b7d21c00da258125a9125a46b32cf9e8504..2142c1fa92017bc47cfc4ccb1f25e16fc0eec1a4 100755 (executable)
@@ -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
 '
index 1dbaa3864a568e09c30c174f771d593beeb1284d..67373dc44ef18ae4c58f237ed9f5c7610ec6413c 100755 (executable)
@@ -31,6 +31,7 @@ diffpatterns="
        cpp
        csharp
        fortran
+       fountain
        html
        java
        matlab
diff --git a/t/t4018/fountain-scene b/t/t4018/fountain-scene
new file mode 100644 (file)
index 0000000..6b3257d
--- /dev/null
@@ -0,0 +1,4 @@
+EXT. STREET RIGHT OUTSIDE - DAY
+
+CHARACTER
+You didn't say the magic phrase, "ChangeMe".
index 6ced98cfb4a71553d66a828118a585e0f7fc84c7..dd627c42d3f5b73a088f91dd6ab8c1ce9ddcb88c 100755 (executable)
@@ -67,6 +67,19 @@ test_expect_success 'setup: messages' '
 
        EOF
 
+       cat >scissors-msg <<-\EOF &&
+       Test git-am with scissors line
+
+       This line should be included in the commit message.
+       EOF
+
+       cat - scissors-msg >no-scissors-msg <<-\EOF &&
+       This line should not be included in the commit message with --scissors enabled.
+
+        - - >8 - - remove everything above this line - - >8 - -
+
+       EOF
+
        signoff="Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
 '
 
@@ -104,6 +117,52 @@ test_expect_success setup '
                echo "X-Fake-Field: Line Three" &&
                git format-patch --stdout first | sed -e "1d"
        } > patch1-ws.eml &&
+       {
+               sed -ne "1p" msg &&
+               echo &&
+               echo "From: $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>" &&
+               echo "Date: $GIT_AUTHOR_DATE" &&
+               echo &&
+               sed -e "1,2d" msg &&
+               echo &&
+               echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" &&
+               echo "---" &&
+               git diff-tree --no-commit-id --stat -p second
+       } >patch1-stgit.eml &&
+       mkdir stgit-series &&
+       cp patch1-stgit.eml stgit-series/patch &&
+       {
+               echo "# This series applies on GIT commit $(git rev-parse first)" &&
+               echo "patch"
+       } >stgit-series/series &&
+       {
+               echo "# HG changeset patch" &&
+               echo "# User $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>" &&
+               echo "# Date $test_tick 25200" &&
+               echo "#      $(git show --pretty="%aD" -s second)" &&
+               echo "# Node ID $_z40" &&
+               echo "# Parent  $_z40" &&
+               cat msg &&
+               echo &&
+               echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" &&
+               echo &&
+               git diff-tree --no-commit-id -p second
+       } >patch1-hg.eml &&
+
+
+       echo scissors-file >scissors-file &&
+       git add scissors-file &&
+       git commit -F scissors-msg &&
+       git tag scissors &&
+       git format-patch --stdout scissors^ >scissors-patch.eml &&
+       git reset --hard HEAD^ &&
+
+       echo no-scissors-file >no-scissors-file &&
+       git add no-scissors-file &&
+       git commit -F no-scissors-msg &&
+       git tag no-scissors &&
+       git format-patch --stdout no-scissors^ >no-scissors-patch.eml &&
+       git reset --hard HEAD^ &&
 
        sed -n -e "3,\$p" msg >file &&
        git add file &&
@@ -154,6 +213,18 @@ test_expect_success 'am applies patch correctly' '
        test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
 '
 
+test_expect_success 'am fails if index is dirty' '
+       test_when_finished "rm -f dirtyfile" &&
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       git checkout first &&
+       echo dirtyfile >dirtyfile &&
+       git add dirtyfile &&
+       test_must_fail git am patch1 &&
+       test_path_is_dir .git/rebase-apply &&
+       test_cmp_rev first HEAD
+'
+
 test_expect_success 'am applies patch e-mail not in a mbox' '
        rm -fr .git/rebase-apply &&
        git reset --hard &&
@@ -187,6 +258,183 @@ test_expect_success 'am applies patch e-mail with preceding whitespace' '
        test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
 '
 
+test_expect_success 'am applies stgit patch' '
+       rm -fr .git/rebase-apply &&
+       git checkout -f first &&
+       git am patch1-stgit.eml &&
+       test_path_is_missing .git/rebase-apply &&
+       git diff --exit-code second &&
+       test_cmp_rev second HEAD &&
+       test_cmp_rev second^ HEAD^
+'
+
+test_expect_success 'am --patch-format=stgit applies stgit patch' '
+       rm -fr .git/rebase-apply &&
+       git checkout -f first &&
+       git am --patch-format=stgit <patch1-stgit.eml &&
+       test_path_is_missing .git/rebase-apply &&
+       git diff --exit-code second &&
+       test_cmp_rev second HEAD &&
+       test_cmp_rev second^ HEAD^
+'
+
+test_expect_success 'am applies stgit series' '
+       rm -fr .git/rebase-apply &&
+       git checkout -f first &&
+       git am stgit-series/series &&
+       test_path_is_missing .git/rebase-apply &&
+       git diff --exit-code second &&
+       test_cmp_rev second HEAD &&
+       test_cmp_rev second^ HEAD^
+'
+
+test_expect_success 'am applies hg patch' '
+       rm -fr .git/rebase-apply &&
+       git checkout -f first &&
+       git am patch1-hg.eml &&
+       test_path_is_missing .git/rebase-apply &&
+       git diff --exit-code second &&
+       test_cmp_rev second HEAD &&
+       test_cmp_rev second^ HEAD^
+'
+
+test_expect_success 'am --patch-format=hg applies hg patch' '
+       rm -fr .git/rebase-apply &&
+       git checkout -f first &&
+       git am --patch-format=hg <patch1-hg.eml &&
+       test_path_is_missing .git/rebase-apply &&
+       git diff --exit-code second &&
+       test_cmp_rev second HEAD &&
+       test_cmp_rev second^ HEAD^
+'
+
+test_expect_success 'am with applypatch-msg hook' '
+       test_when_finished "rm -f .git/hooks/applypatch-msg" &&
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       git checkout first &&
+       mkdir -p .git/hooks &&
+       write_script .git/hooks/applypatch-msg <<-\EOF &&
+       cat "$1" >actual-msg &&
+       echo hook-message >"$1"
+       EOF
+       git am patch1 &&
+       test_path_is_missing .git/rebase-apply &&
+       git diff --exit-code second &&
+       echo hook-message >expected &&
+       git log -1 --format=format:%B >actual &&
+       test_cmp expected actual &&
+       git log -1 --format=format:%B second >expected &&
+       test_cmp expected actual-msg
+'
+
+test_expect_success 'am with failing applypatch-msg hook' '
+       test_when_finished "rm -f .git/hooks/applypatch-msg" &&
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       git checkout first &&
+       mkdir -p .git/hooks &&
+       write_script .git/hooks/applypatch-msg <<-\EOF &&
+       exit 1
+       EOF
+       test_must_fail git am patch1 &&
+       test_path_is_dir .git/rebase-apply &&
+       git diff --exit-code first &&
+       test_cmp_rev first HEAD
+'
+
+test_expect_success 'am with pre-applypatch hook' '
+       test_when_finished "rm -f .git/hooks/pre-applypatch" &&
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       git checkout first &&
+       mkdir -p .git/hooks &&
+       write_script .git/hooks/pre-applypatch <<-\EOF &&
+       git diff first >diff.actual
+       exit 0
+       EOF
+       git am patch1 &&
+       test_path_is_missing .git/rebase-apply &&
+       git diff --exit-code second &&
+       test_cmp_rev second HEAD &&
+       git diff first..second >diff.expected &&
+       test_cmp diff.expected diff.actual
+'
+
+test_expect_success 'am with failing pre-applypatch hook' '
+       test_when_finished "rm -f .git/hooks/pre-applypatch" &&
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       git checkout first &&
+       mkdir -p .git/hooks &&
+       write_script .git/hooks/pre-applypatch <<-\EOF &&
+       exit 1
+       EOF
+       test_must_fail git am patch1 &&
+       test_path_is_dir .git/rebase-apply &&
+       git diff --exit-code second &&
+       test_cmp_rev first HEAD
+'
+
+test_expect_success 'am with post-applypatch hook' '
+       test_when_finished "rm -f .git/hooks/post-applypatch" &&
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       git checkout first &&
+       mkdir -p .git/hooks &&
+       write_script .git/hooks/post-applypatch <<-\EOF &&
+       git rev-parse HEAD >head.actual
+       git diff second >diff.actual
+       exit 0
+       EOF
+       git am patch1 &&
+       test_path_is_missing .git/rebase-apply &&
+       test_cmp_rev second HEAD &&
+       git rev-parse second >head.expected &&
+       test_cmp head.expected head.actual &&
+       git diff second >diff.expected &&
+       test_cmp diff.expected diff.actual
+'
+
+test_expect_success 'am with failing post-applypatch hook' '
+       test_when_finished "rm -f .git/hooks/post-applypatch" &&
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       git checkout first &&
+       mkdir -p .git/hooks &&
+       write_script .git/hooks/post-applypatch <<-\EOF &&
+       git rev-parse HEAD >head.actual
+       exit 1
+       EOF
+       git am patch1 &&
+       test_path_is_missing .git/rebase-apply &&
+       git diff --exit-code second &&
+       test_cmp_rev second HEAD &&
+       git rev-parse second >head.expected &&
+       test_cmp head.expected head.actual
+'
+
+test_expect_success 'am --scissors cuts the message at the scissors line' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       git checkout second &&
+       git am --scissors scissors-patch.eml &&
+       test_path_is_missing .git/rebase-apply &&
+       git diff --exit-code scissors &&
+       test_cmp_rev scissors HEAD
+'
+
+test_expect_success 'am --no-scissors overrides mailinfo.scissors' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       git checkout second &&
+       test_config mailinfo.scissors true &&
+       git am --no-scissors no-scissors-patch.eml &&
+       test_path_is_missing .git/rebase-apply &&
+       git diff --exit-code no-scissors &&
+       test_cmp_rev no-scissors HEAD
+'
+
 test_expect_success 'setup: new author and committer' '
        GIT_AUTHOR_NAME="Another Thor" &&
        GIT_AUTHOR_EMAIL="a.thor@example.com" &&
@@ -303,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 &&
@@ -366,6 +633,20 @@ test_expect_success 'am --abort removes a stray directory' '
        test_path_is_missing .git/rebase-apply
 '
 
+test_expect_success 'am refuses patches when paused' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       git checkout lorem2^^ &&
+
+       test_must_fail git am lorem-move.patch &&
+       test_path_is_dir .git/rebase-apply &&
+       test_cmp_rev lorem2^^ HEAD &&
+
+       test_must_fail git am <lorem-move.patch &&
+       test_path_is_dir .git/rebase-apply &&
+       test_cmp_rev lorem2^^ HEAD
+'
+
 test_expect_success 'am --resolved works' '
        echo goodbye >expected &&
        rm -fr .git/rebase-apply &&
@@ -380,6 +661,31 @@ test_expect_success 'am --resolved works' '
        test_cmp expected another
 '
 
+test_expect_success 'am --resolved fails if index has no changes' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       git checkout lorem2^^ &&
+       test_must_fail git am lorem-move.patch &&
+       test_path_is_dir .git/rebase-apply &&
+       test_cmp_rev lorem2^^ HEAD &&
+       test_must_fail git am --resolved &&
+       test_path_is_dir .git/rebase-apply &&
+       test_cmp_rev lorem2^^ HEAD
+'
+
+test_expect_success 'am --resolved fails if index has unmerged entries' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       git checkout second &&
+       test_must_fail git am -3 lorem-move.patch &&
+       test_path_is_dir .git/rebase-apply &&
+       test_cmp_rev second HEAD &&
+       test_must_fail git am --resolved >err &&
+       test_path_is_dir .git/rebase-apply &&
+       test_cmp_rev second HEAD &&
+       test_i18ngrep "still have unmerged paths" err
+'
+
 test_expect_success 'am takes patches from a Pine mailbox' '
        rm -fr .git/rebase-apply &&
        git reset --hard &&
@@ -544,6 +850,18 @@ test_expect_success 'am --message-id really adds the message id' '
        test_cmp expected actual
 '
 
+test_expect_success 'am.messageid really adds the message id' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       git checkout HEAD^ &&
+       test_config am.messageid true &&
+       git am patch1.eml &&
+       test_path_is_missing .git/rebase-apply &&
+       git cat-file commit HEAD | tail -n1 >actual &&
+       grep Message-Id patch1.eml >expected &&
+       test_cmp expected actual
+'
+
 test_expect_success 'am --message-id -s signs off after the message id' '
        rm -fr .git/rebase-apply &&
        git reset --hard &&
index 833e7b2ceae99cbf53bac63e239b362e368ddf8f..05bdc3ee49408ac1ab35fe54d0b3bfbe401bf7cf 100755 (executable)
@@ -95,6 +95,21 @@ test_expect_success 'am --abort will keep the local commits intact' '
        test_cmp expect actual
 '
 
+test_expect_success 'am --abort will keep dirty index intact' '
+       git reset --hard initial &&
+       echo dirtyfile >dirtyfile &&
+       cp dirtyfile dirtyfile.expected &&
+       git add dirtyfile &&
+       test_must_fail git am 0001-*.patch &&
+       test_cmp_rev initial HEAD &&
+       test_path_is_file dirtyfile &&
+       test_cmp dirtyfile.expected dirtyfile &&
+       git am --abort &&
+       test_cmp_rev initial HEAD &&
+       test_path_is_file dirtyfile &&
+       test_cmp dirtyfile.expected dirtyfile
+'
+
 test_expect_success 'am -3 stops on conflict on unborn branch' '
        git checkout -f --orphan orphan &&
        git reset &&
index 1b2e981a0011d520d1a7d8bb8d0a5fd7892bacc4..35d2d7c22152e1735293dea1d47488ed5331c6b2 100755 (executable)
@@ -146,7 +146,30 @@ test_expect_success 'git log --follow' '
        actual=$(git log --follow --pretty="format:%s" ichi) &&
        expect=$(echo third ; echo second ; echo initial) &&
        verbose test "$actual" = "$expect"
+'
+
+test_expect_success 'git config log.follow works like --follow' '
+       test_config log.follow true &&
+       actual=$(git log --pretty="format:%s" ichi) &&
+       expect=$(echo third ; echo second ; echo initial) &&
+       verbose test "$actual" = "$expect"
+'
 
+test_expect_success 'git config log.follow does not die with multiple paths' '
+       test_config log.follow true &&
+       git log --pretty="format:%s" ichi ein
+'
+
+test_expect_success 'git config log.follow does not die with no paths' '
+       test_config log.follow true &&
+       git log --
+'
+
+test_expect_success 'git config log.follow is overridden by --no-follow' '
+       test_config log.follow true &&
+       actual=$(git log --no-follow --pretty="format:%s" ichi) &&
+       expect="third" &&
+       verbose test "$actual" = "$expect"
 '
 
 cat > expect << EOF
index 61bc8da56028625fa2525cc5b067d40367f2b1c6..3dc5ec4dd331c152754f3c9480042103d5d9f290 100755 (executable)
@@ -259,7 +259,7 @@ EOF
     thirtyeight=${tag#??} &&
     rm -f .git/objects/${tag%$thirtyeight}/$thirtyeight &&
     git index-pack --strict tag-test-${pack1}.pack 2>err &&
-    grep "^error:.* expected .tagger. line" err
+    grep "^warning:.* expected .tagger. line" err
 '
 
 test_done
index 8e98b44083f76b959f32883059f07d65a5b8aadb..da9d59940d5aa72a059a36f4187775b093eb5a58 100755 (executable)
@@ -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' '
index 69ee13c8bebbdb1303378288597420cdf70b7b88..44f3d5fb284e9848180df9ed9cfbc44a91762277 100755 (executable)
@@ -115,4 +115,55 @@ test_expect_success 'push with transfer.fsckobjects' '
        test_cmp exp act
 '
 
+cat >bogus-commit <<\EOF
+tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904
+author Bugs Bunny 1234567890 +0000
+committer Bugs Bunny <bugs@bun.ni> 1234567890 +0000
+
+This commit object intentionally broken
+EOF
+
+test_expect_success 'push with receive.fsck.skipList' '
+       commit="$(git hash-object -t commit -w --stdin <bogus-commit)" &&
+       git push . $commit:refs/heads/bogus &&
+       rm -rf dst &&
+       git init dst &&
+       git --git-dir=dst/.git config receive.fsckObjects true &&
+       test_must_fail git push --porcelain dst bogus &&
+       git --git-dir=dst/.git config receive.fsck.skipList SKIP &&
+       echo $commit >dst/.git/SKIP &&
+       git push --porcelain dst bogus
+'
+
+test_expect_success 'push with receive.fsck.missingEmail=warn' '
+       commit="$(git hash-object -t commit -w --stdin <bogus-commit)" &&
+       git push . $commit:refs/heads/bogus &&
+       rm -rf dst &&
+       git init dst &&
+       git --git-dir=dst/.git config receive.fsckobjects true &&
+       test_must_fail git push --porcelain dst bogus &&
+       git --git-dir=dst/.git config \
+               receive.fsck.missingEmail warn &&
+       git push --porcelain dst bogus >act 2>&1 &&
+       grep "missingEmail" act &&
+       git --git-dir=dst/.git branch -D bogus &&
+       git --git-dir=dst/.git config --add \
+               receive.fsck.missingEmail ignore &&
+       git --git-dir=dst/.git config --add \
+               receive.fsck.badDate warn &&
+       git push --porcelain dst bogus >act 2>&1 &&
+       test_must_fail grep "missingEmail" act
+'
+
+test_expect_success \
+       'receive.fsck.unterminatedHeader=warn triggers error' '
+       rm -rf dst &&
+       git init dst &&
+       git --git-dir=dst/.git config receive.fsckobjects true &&
+       git --git-dir=dst/.git config \
+               receive.fsck.unterminatedheader warn &&
+       test_must_fail git push --porcelain dst HEAD >act 2>&1 &&
+       grep "Cannot demote unterminatedheader" act
+'
+
 test_done
index de6db86ccff044b5cc97a7b9236760076a8784d8..f541f30bc2f8745dad051bc10a0989be1f2b3ff0 100755 (executable)
@@ -71,15 +71,18 @@ test_refspec fetch ':refs/remotes/frotz/HEAD-to-me'
 test_refspec push ':refs/remotes/frotz/delete me'              invalid
 test_refspec fetch ':refs/remotes/frotz/HEAD to me'            invalid
 
-test_refspec fetch 'refs/heads/*/for-linus:refs/remotes/mine/*-blah' invalid
-test_refspec push 'refs/heads/*/for-linus:refs/remotes/mine/*-blah' invalid
+test_refspec fetch 'refs/heads/*/for-linus:refs/remotes/mine/*-blah'
+test_refspec push 'refs/heads/*/for-linus:refs/remotes/mine/*-blah'
 
-test_refspec fetch 'refs/heads*/for-linus:refs/remotes/mine/*' invalid
-test_refspec push 'refs/heads*/for-linus:refs/remotes/mine/*' invalid
+test_refspec fetch 'refs/heads*/for-linus:refs/remotes/mine/*'
+test_refspec push 'refs/heads*/for-linus:refs/remotes/mine/*'
 
 test_refspec fetch 'refs/heads/*/*/for-linus:refs/remotes/mine/*' invalid
 test_refspec push 'refs/heads/*/*/for-linus:refs/remotes/mine/*' invalid
 
+test_refspec fetch 'refs/heads/*g*/for-linus:refs/remotes/mine/*' invalid
+test_refspec push 'refs/heads/*g*/for-linus:refs/remotes/mine/*' invalid
+
 test_refspec fetch 'refs/heads/*/for-linus:refs/remotes/mine/*'
 test_refspec push 'refs/heads/*/for-linus:refs/remotes/mine/*'
 
index 3bd9759e0ff01af9969e1f8ae088945d9e2e34e4..aadaac515e086a7f87c6d06a0313fe7b77d11f92 100755 (executable)
@@ -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
index f4a7193677a4861440d79e761f02315c790e275f..a0013ee32f1940e32b4038d6cc9a39b218d794f1 100755 (executable)
@@ -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 &&
index bfdaf75966f7b8bb057e4db0b8791306f6e6a44f..9b34f3c615df5080085b5f8cf956d2bac099d9d4 100755 (executable)
@@ -496,4 +496,11 @@ test_expect_success 'shallow clone locally' '
        ( cd ddsstt && git fsck )
 '
 
+test_expect_success 'GIT_TRACE_PACKFILE produces a usable pack' '
+       rm -rf dst.git &&
+       GIT_TRACE_PACKFILE=$PWD/tmp.pack git clone --no-local --bare src dst.git &&
+       git init --bare replay.git &&
+       git -C replay.git index-pack -v --stdin <tmp.pack
+'
+
 test_done
diff --git a/t/t5603-clone-dirname.sh b/t/t5603-clone-dirname.sh
new file mode 100755 (executable)
index 0000000..d5af758
--- /dev/null
@@ -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
index 3e783fc450d21c8de6fae2cb3677e9eaebacf927..ef1779f5cae5642c65c83a9e18b55488005ccf12 100755 (executable)
@@ -10,49 +10,51 @@ base_dir=`pwd`
 
 U=$base_dir/UPLOAD_LOG
 
-test_expect_success 'preparing first repository' \
-'test_create_repo A && cd A &&
-echo first > file1 &&
-git add file1 &&
-git commit -m initial'
-
-cd "$base_dir"
-
-test_expect_success 'preparing second repository' \
-'git clone A B && cd B &&
-echo second > file2 &&
-git add file2 &&
-git commit -m addition &&
-git repack -a -d &&
-git prune'
-
-cd "$base_dir"
-
-test_expect_success 'cloning with reference (-l -s)' \
-'git clone -l -s --reference B A C'
-
-cd "$base_dir"
-
-test_expect_success 'existence of info/alternates' \
-'test_line_count = 2 C/.git/objects/info/alternates'
-
-cd "$base_dir"
+# create a commit in repo $1 with name $2
+commit_in () {
+       (
+               cd "$1" &&
+               echo "$2" >"$2" &&
+               git add "$2" &&
+               git commit -m "$2"
+       )
+}
+
+# check that there are $2 loose objects in repo $1
+test_objcount () {
+       echo "$2" >expect &&
+       git -C "$1" count-objects >actual.raw &&
+       cut -d' ' -f1 <actual.raw >actual &&
+       test_cmp expect actual
+}
+
+test_expect_success 'preparing first repository' '
+       test_create_repo A &&
+       commit_in A file1
+'
 
-test_expect_success 'pulling from reference' \
-'cd C &&
-git pull ../B master'
+test_expect_success 'preparing second repository' '
+       git clone A B &&
+       commit_in B file2 &&
+       git -C B repack -ad &&
+       git -C B prune
+'
 
-cd "$base_dir"
+test_expect_success 'cloning with reference (-l -s)' '
+       git clone -l -s --reference B A C
+'
 
-test_expect_success 'that reference gets used' \
-'cd C &&
-echo "0 objects, 0 kilobytes" > expected &&
-git count-objects > current &&
-test_cmp expected current'
+test_expect_success 'existence of info/alternates' '
+       test_line_count = 2 C/.git/objects/info/alternates
+'
 
-cd "$base_dir"
+test_expect_success 'pulling from reference' '
+       git -C C pull ../B master
+'
 
-rm -f "$U.D"
+test_expect_success 'that reference gets used' '
+       test_objcount C 0
+'
 
 test_expect_success 'cloning with reference (no -l -s)' '
        GIT_TRACE_PACKET=$U.D git clone --reference B "file://$(pwd)/A" D
@@ -63,95 +65,69 @@ test_expect_success 'fetched no objects' '
        ! grep " want" "$U.D"
 '
 
-cd "$base_dir"
-
-test_expect_success 'existence of info/alternates' \
-'test_line_count = 1 D/.git/objects/info/alternates'
-
-cd "$base_dir"
-
-test_expect_success 'pulling from reference' \
-'cd D && git pull ../B master'
-
-cd "$base_dir"
-
-test_expect_success 'that reference gets used' \
-'cd D && echo "0 objects, 0 kilobytes" > expected &&
-git count-objects > current &&
-test_cmp expected current'
-
-cd "$base_dir"
+test_expect_success 'existence of info/alternates' '
+       test_line_count = 1 D/.git/objects/info/alternates
+'
 
-test_expect_success 'updating origin' \
-'cd A &&
-echo third > file3 &&
-git add file3 &&
-git commit -m update &&
-git repack -a -d &&
-git prune'
+test_expect_success 'pulling from reference' '
+       git -C D pull ../B master
+'
 
-cd "$base_dir"
+test_expect_success 'that reference gets used' '
+       test_objcount D 0
+'
 
-test_expect_success 'pulling changes from origin' \
-'cd C &&
-git pull origin'
+test_expect_success 'updating origin' '
+       commit_in A file3 &&
+       git -C A repack -ad &&
+       git -C A prune
+'
 
-cd "$base_dir"
+test_expect_success 'pulling changes from origin' '
+       git -C C pull origin
+'
 
 # the 2 local objects are commit and tree from the merge
-test_expect_success 'that alternate to origin gets used' \
-'cd C &&
-echo "2 objects" > expected &&
-git count-objects | cut -d, -f1 > current &&
-test_cmp expected current'
-
-cd "$base_dir"
-
-test_expect_success 'pulling changes from origin' \
-'cd D &&
-git pull origin'
+test_expect_success 'that alternate to origin gets used' '
+       test_objcount C 2
+'
 
-cd "$base_dir"
+test_expect_success 'pulling changes from origin' '
+       git -C D pull origin
+'
 
 # the 5 local objects are expected; file3 blob, commit in A to add it
 # and its tree, and 2 are our tree and the merge commit.
-test_expect_success 'check objects expected to exist locally' \
-'cd D &&
-echo "5 objects" > expected &&
-git count-objects | cut -d, -f1 > current &&
-test_cmp expected current'
-
-cd "$base_dir"
-
-test_expect_success 'preparing alternate repository #1' \
-'test_create_repo F && cd F &&
-echo first > file1 &&
-git add file1 &&
-git commit -m initial'
-
-cd "$base_dir"
-
-test_expect_success 'cloning alternate repo #2 and adding changes to repo #1' \
-'git clone F G && cd F &&
-echo second > file2 &&
-git add file2 &&
-git commit -m addition'
+test_expect_success 'check objects expected to exist locally' '
+       test_objcount D 5
+'
 
-cd "$base_dir"
+test_expect_success 'preparing alternate repository #1' '
+       test_create_repo F &&
+       commit_in F file1
+'
 
-test_expect_success 'cloning alternate repo #1, using #2 as reference' \
-'git clone --reference G F H'
+test_expect_success 'cloning alternate repo #2 and adding changes to repo #1' '
+       git clone F G &&
+       commit_in F file2
+'
 
-cd "$base_dir"
+test_expect_success 'cloning alternate repo #1, using #2 as reference' '
+       git clone --reference G F H
+'
 
-test_expect_success 'cloning with reference being subset of source (-l -s)' \
-'git clone -l -s --reference A B E'
+test_expect_success 'cloning with reference being subset of source (-l -s)' '
+       git clone -l -s --reference A B E
+'
 
-cd "$base_dir"
+test_expect_success 'cloning with multiple references drops duplicates' '
+       git clone -s --reference B --reference A --reference B A dups &&
+       test_line_count = 2 dups/.git/objects/info/alternates
+'
 
 test_expect_success 'clone with reference from a tagged repository' '
        (
-               cd A && git tag -a -m 'tagged' HEAD
+               cd A && git tag -a -m tagged HEAD
        ) &&
        git clone --reference=A A I
 '
@@ -168,8 +144,6 @@ test_expect_success 'prepare branched repository' '
        )
 '
 
-rm -f "$U.K"
-
 test_expect_success 'fetch with incomplete alternates' '
        git init K &&
        echo "$base_dir/A/.git/objects" >K/.git/objects/info/alternates &&
index 06b48681090499d681d7e7ce6b14d2a4e17e5193..9e2c20374732d790edefc4cd87aa28223fbb24c4 100755 (executable)
@@ -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
 '
 
index 24fc2ba55da38232f07c58346c628b61822af44a..7c9bec76302259beebeea7521b9c74acfe6de264 100755 (executable)
@@ -227,6 +227,24 @@ test_expect_success 'Check format "rfc2822" date fields output' '
        test_cmp expected actual
 '
 
+test_expect_success 'Check format of strftime date fields' '
+       echo "my date is 2006-07-03" >expected &&
+       git for-each-ref \
+         --format="%(authordate:format:my date is %Y-%m-%d)" \
+         refs/heads >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'exercise strftime with odd fields' '
+       echo >expected &&
+       git for-each-ref --format="%(authordate:format:)" refs/heads >actual &&
+       test_cmp expected actual &&
+       long="long format -- $_z40$_z40$_z40$_z40$_z40$_z40$_z40" &&
+       echo $long >expected &&
+       git for-each-ref --format="%(authordate:format:$long)" refs/heads >actual &&
+       test_cmp expected actual
+'
+
 cat >expected <<\EOF
 refs/heads/master
 refs/remotes/origin/master
index 2adf825076b9c8f774973dcc83686980dca7725b..cf076dcd940aa0d74bd8df03b4f6c768e1954455 100755 (executable)
@@ -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)" '
index d1ff5c94f2f3241b3b1ed436d0011280986abab7..d31788cc6ce6a5698c34ffe6ee8a59fccacab90c 100755 (executable)
@@ -51,7 +51,19 @@ test_expect_success 'creating a tag using default HEAD should succeed' '
        echo foo >foo &&
        git add foo &&
        git commit -m Foo &&
-       git tag mytag
+       git tag mytag &&
+       test_must_fail git reflog exists refs/tags/mytag
+'
+
+test_expect_success 'creating a tag with --create-reflog should create reflog' '
+       test_when_finished "git tag -d tag_with_reflog" &&
+       git tag --create-reflog tag_with_reflog &&
+       git reflog exists refs/tags/tag_with_reflog
+'
+
+test_expect_success '--create-reflog does not create reflog on failure' '
+       test_must_fail git tag --create-reflog mytag &&
+       test_must_fail git reflog exists refs/tags/mytag
 '
 
 test_expect_success 'listing all tags if one exists should succeed' '
diff --git a/t/t7030-verify-tag.sh b/t/t7030-verify-tag.sh
new file mode 100755 (executable)
index 0000000..4608e71
--- /dev/null
@@ -0,0 +1,115 @@
+#!/bin/sh
+
+test_description='signed tag tests'
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-gpg.sh"
+
+test_expect_success GPG 'create signed tags' '
+       echo 1 >file && git add file &&
+       test_tick && git commit -m initial &&
+       git tag -s -m initial initial &&
+       git branch side &&
+
+       echo 2 >file && test_tick && git commit -a -m second &&
+       git tag -s -m second second &&
+
+       git checkout side &&
+       echo 3 >elif && git add elif &&
+       test_tick && git commit -m "third on side" &&
+
+       git checkout master &&
+       test_tick && git merge -S side &&
+       git tag -s -m merge merge &&
+
+       echo 4 >file && test_tick && git commit -a -S -m "fourth unsigned" &&
+       git tag -a -m fourth-unsigned fourth-unsigned &&
+
+       test_tick && git commit --amend -S -m "fourth signed" &&
+       git tag -s -m fourth fourth-signed &&
+
+       echo 5 >file && test_tick && git commit -a -m "fifth" &&
+       git tag fifth-unsigned &&
+
+       git config commit.gpgsign true &&
+       echo 6 >file && test_tick && git commit -a -m "sixth" &&
+       git tag -a -m sixth sixth-unsigned &&
+
+       test_tick && git rebase -f HEAD^^ && git tag -s -m 6th sixth-signed HEAD^ &&
+       git tag -m seventh -s seventh-signed &&
+
+       echo 8 >file && test_tick && git commit -a -m eighth &&
+       git tag -uB7227189 -m eighth eighth-signed-alt
+'
+
+test_expect_success GPG 'verify and show signatures' '
+       (
+               for tag in initial second merge fourth-signed sixth-signed seventh-signed
+               do
+                       git verify-tag $tag 2>actual &&
+                       grep "Good signature from" actual &&
+                       ! grep "BAD signature from" actual &&
+                       echo $tag OK || exit 1
+               done
+       ) &&
+       (
+               for tag in fourth-unsigned fifth-unsigned sixth-unsigned
+               do
+                       test_must_fail git verify-tag $tag 2>actual &&
+                       ! grep "Good signature from" actual &&
+                       ! grep "BAD signature from" actual &&
+                       echo $tag OK || exit 1
+               done
+       ) &&
+       (
+               for tag in eighth-signed-alt
+               do
+                       git verify-tag $tag 2>actual &&
+                       grep "Good signature from" actual &&
+                       ! grep "BAD signature from" actual &&
+                       grep "not certified" actual &&
+                       echo $tag OK || exit 1
+               done
+       )
+'
+
+test_expect_success GPG 'detect fudged signature' '
+       git cat-file tag seventh-signed >raw &&
+       sed -e "s/seventh/7th forged/" raw >forged1 &&
+       git hash-object -w -t tag forged1 >forged1.tag &&
+       test_must_fail git verify-tag $(cat forged1.tag) 2>actual1 &&
+       grep "BAD signature from" actual1 &&
+       ! grep "Good signature from" actual1
+'
+
+test_expect_success GPG 'verify signatures with --raw' '
+       (
+               for tag in initial second merge fourth-signed sixth-signed seventh-signed
+               do
+                       git verify-tag --raw $tag 2>actual &&
+                       grep "GOODSIG" actual &&
+                       ! grep "BADSIG" actual &&
+                       echo $tag OK || exit 1
+               done
+       ) &&
+       (
+               for tag in fourth-unsigned fifth-unsigned sixth-unsigned
+               do
+                       test_must_fail git verify-tag --raw $tag 2>actual &&
+                       ! grep "GOODSIG" actual &&
+                       ! grep "BADSIG" actual &&
+                       echo $tag OK || exit 1
+               done
+       ) &&
+       (
+               for tag in eighth-signed-alt
+               do
+                       git verify-tag --raw $tag 2>actual &&
+                       grep "GOODSIG" actual &&
+                       ! grep "BADSIG" actual &&
+                       grep "TRUST_UNDEFINED" actual &&
+                       echo $tag OK || exit 1
+               done
+       )
+'
+
+test_done
index bd4806c12a0a6c09986c65de80ec79b59410ae68..ff23f4e94e9a67a2f327f45bb0557c051738800f 100755 (executable)
@@ -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
index 99be5d95d063f85873a4cb5c5c7ebefd8ebfc990..27557d64f35bf7d53d52ee7583258daa24ef68b7 100755 (executable)
@@ -455,6 +455,146 @@ test_expect_success 'nested git work tree' '
        ! test -d bar
 '
 
+test_expect_success 'should clean things that almost look like git but are not' '
+       rm -fr almost_git almost_bare_git almost_submodule &&
+       mkdir -p almost_git/.git/objects &&
+       mkdir -p almost_git/.git/refs &&
+       cat >almost_git/.git/HEAD <<-\EOF &&
+       garbage
+       EOF
+       cp -r almost_git/.git/ almost_bare_git &&
+       mkdir almost_submodule/ &&
+       cat >almost_submodule/.git <<-\EOF &&
+       garbage
+       EOF
+       test_when_finished "rm -rf almost_*" &&
+       git clean -f -d &&
+       test_path_is_missing almost_git &&
+       test_path_is_missing almost_bare_git &&
+       test_path_is_missing almost_submodule
+'
+
+test_expect_success 'should not clean submodules' '
+       rm -fr repo to_clean sub1 sub2 &&
+       mkdir repo to_clean &&
+       (
+               cd repo &&
+               git init &&
+               test_commit msg hello.world
+       ) &&
+       git submodule add ./repo/.git sub1 &&
+       git commit -m "sub1" &&
+       git branch before_sub2 &&
+       git submodule add ./repo/.git sub2 &&
+       git commit -m "sub2" &&
+       git checkout before_sub2 &&
+       >to_clean/should_clean.this &&
+       git clean -f -d &&
+       test_path_is_file repo/.git/index &&
+       test_path_is_file repo/hello.world &&
+       test_path_is_file sub1/.git &&
+       test_path_is_file sub1/hello.world &&
+       test_path_is_file sub2/.git &&
+       test_path_is_file sub2/hello.world &&
+       test_path_is_missing to_clean
+'
+
+test_expect_success POSIXPERM 'should avoid cleaning possible submodules' '
+       rm -fr to_clean possible_sub1 &&
+       mkdir to_clean possible_sub1 &&
+       test_when_finished "rm -rf possible_sub*" &&
+       echo "gitdir: foo" >possible_sub1/.git &&
+       >possible_sub1/hello.world &&
+       chmod 0 possible_sub1/.git &&
+       >to_clean/should_clean.this &&
+       git clean -f -d &&
+       test_path_is_file possible_sub1/.git &&
+       test_path_is_file possible_sub1/hello.world &&
+       test_path_is_missing to_clean
+'
+
+test_expect_success 'nested (empty) git should be kept' '
+       rm -fr empty_repo to_clean &&
+       git init empty_repo &&
+       mkdir to_clean &&
+       >to_clean/should_clean.this &&
+       git clean -f -d &&
+       test_path_is_file empty_repo/.git/HEAD &&
+       test_path_is_missing to_clean
+'
+
+test_expect_success 'nested bare repositories should be cleaned' '
+       rm -fr bare1 bare2 subdir &&
+       git init --bare bare1 &&
+       git clone --local --bare . bare2 &&
+       mkdir subdir &&
+       cp -r bare2 subdir/bare3 &&
+       git clean -f -d &&
+       test_path_is_missing bare1 &&
+       test_path_is_missing bare2 &&
+       test_path_is_missing subdir
+'
+
+test_expect_failure 'nested (empty) bare repositories should be cleaned even when in .git' '
+       rm -fr strange_bare &&
+       mkdir strange_bare &&
+       git init --bare strange_bare/.git &&
+       git clean -f -d &&
+       test_path_is_missing strange_bare
+'
+
+test_expect_failure 'nested (non-empty) bare repositories should be cleaned even when in .git' '
+       rm -fr strange_bare &&
+       mkdir strange_bare &&
+       git clone --local --bare . strange_bare/.git &&
+       git clean -f -d &&
+       test_path_is_missing strange_bare
+'
+
+test_expect_success 'giving path in nested git work tree will remove it' '
+       rm -fr repo &&
+       mkdir repo &&
+       (
+               cd repo &&
+               git init &&
+               mkdir -p bar/baz &&
+               test_commit msg bar/baz/hello.world
+       ) &&
+       git clean -f -d repo/bar/baz &&
+       test_path_is_file repo/.git/HEAD &&
+       test_path_is_dir repo/bar/ &&
+       test_path_is_missing repo/bar/baz
+'
+
+test_expect_success 'giving path to nested .git will not remove it' '
+       rm -fr repo &&
+       mkdir repo untracked &&
+       (
+               cd repo &&
+               git init &&
+               test_commit msg hello.world
+       ) &&
+       git clean -f -d repo/.git &&
+       test_path_is_file repo/.git/HEAD &&
+       test_path_is_dir repo/.git/refs &&
+       test_path_is_dir repo/.git/objects &&
+       test_path_is_dir untracked/
+'
+
+test_expect_success 'giving path to nested .git/ will remove contents' '
+       rm -fr repo untracked &&
+       mkdir repo untracked &&
+       (
+               cd repo &&
+               git init &&
+               test_commit msg hello.world
+       ) &&
+       git clean -f -d repo/.git/ &&
+       test_path_is_dir repo/.git &&
+       test_dir_is_empty repo/.git &&
+       test_path_is_dir untracked/
+'
+
 test_expect_success 'force removal of nested git work tree' '
        rm -fr foo bar baz &&
        mkdir -p foo bar baz/boo &&
index 9ac794052df6ac925733ff8a8852e9759bb0baf0..db9774e345586428ae1ac23f11d92ae00e05c1e9 100755 (executable)
@@ -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 &&
index 13331e533bf520b6f268df16f8a143b8661a1c72..18e5cf06630273a6131d13d9a82f085d33f15153 100755 (executable)
@@ -81,6 +81,44 @@ test_expect_success GPG 'verify and show signatures' '
        )
 '
 
+test_expect_success GPG 'verify-commit exits success on untrusted signature' '
+       git verify-commit eighth-signed-alt 2>actual &&
+       grep "Good signature from" actual &&
+       ! grep "BAD signature from" actual &&
+       grep "not certified" actual
+'
+
+test_expect_success GPG 'verify signatures with --raw' '
+       (
+               for commit in initial second merge fourth-signed fifth-signed sixth-signed seventh-signed
+               do
+                       git verify-commit --raw $commit 2>actual &&
+                       grep "GOODSIG" actual &&
+                       ! grep "BADSIG" actual &&
+                       echo $commit OK || exit 1
+               done
+       ) &&
+       (
+               for commit in merge^2 fourth-unsigned sixth-unsigned seventh-unsigned
+               do
+                       test_must_fail git verify-commit --raw $commit 2>actual &&
+                       ! grep "GOODSIG" actual &&
+                       ! grep "BADSIG" actual &&
+                       echo $commit OK || exit 1
+               done
+       ) &&
+       (
+               for commit in eighth-signed-alt
+               do
+                       git verify-commit --raw $commit 2>actual &&
+                       grep "GOODSIG" actual &&
+                       ! grep "BADSIG" actual &&
+                       grep "TRUST_UNDEFINED" actual &&
+                       echo $commit OK || exit 1
+               done
+       )
+'
+
 test_expect_success GPG 'show signed commit with signature' '
        git show -s initial >commit &&
        git show -s --show-signature initial >show &&
index 68ad2d7454d6cbc93b5f7109fe836138c95ba684..49d19a3b36d0649dfd01de29acdbf0fab2532e17 100755 (executable)
@@ -134,9 +134,13 @@ test_expect_success 'prepare for rebase_i_conflicts' '
 test_expect_success 'status during rebase -i when conflicts unresolved' '
        test_when_finished "git rebase --abort" &&
        ONTO=$(git rev-parse --short rebase_i_conflicts) &&
+       LAST_COMMIT=$(git rev-parse --short rebase_i_conflicts_second) &&
        test_must_fail git rebase -i rebase_i_conflicts &&
        cat >expected <<EOF &&
-rebase in progress; onto $ONTO
+interactive rebase in progress; onto $ONTO
+Last command done (1 command done):
+   pick $LAST_COMMIT one_second
+No commands remaining.
 You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''.
   (fix conflicts and then run "git rebase --continue")
   (use "git rebase --skip" to skip this patch)
@@ -159,10 +163,14 @@ test_expect_success 'status during rebase -i after resolving conflicts' '
        git reset --hard rebase_i_conflicts_second &&
        test_when_finished "git rebase --abort" &&
        ONTO=$(git rev-parse --short rebase_i_conflicts) &&
+       LAST_COMMIT=$(git rev-parse --short rebase_i_conflicts_second) &&
        test_must_fail git rebase -i rebase_i_conflicts &&
        git add main.txt &&
        cat >expected <<EOF &&
-rebase in progress; onto $ONTO
+interactive rebase in progress; onto $ONTO
+Last command done (1 command done):
+   pick $LAST_COMMIT one_second
+No commands remaining.
 You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''.
   (all conflicts fixed: run "git rebase --continue")
 
@@ -183,14 +191,20 @@ test_expect_success 'status when rebasing -i in edit mode' '
        git checkout -b rebase_i_edit &&
        test_commit one_rebase_i main.txt one &&
        test_commit two_rebase_i main.txt two &&
+       COMMIT2=$(git rev-parse --short rebase_i_edit) &&
        test_commit three_rebase_i main.txt three &&
+       COMMIT3=$(git rev-parse --short rebase_i_edit) &&
        FAKE_LINES="1 edit 2" &&
        export FAKE_LINES &&
        test_when_finished "git rebase --abort" &&
        ONTO=$(git rev-parse --short HEAD~2) &&
        git rebase -i HEAD~2 &&
        cat >expected <<EOF &&
-rebase in progress; onto $ONTO
+interactive rebase in progress; onto $ONTO
+Last commands done (2 commands done):
+   pick $COMMIT2 two_rebase_i
+   edit $COMMIT3 three_rebase_i
+No commands remaining.
 You are currently editing a commit while rebasing branch '\''rebase_i_edit'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
   (use "git rebase --continue" once you are satisfied with your changes)
@@ -207,8 +221,11 @@ test_expect_success 'status when splitting a commit' '
        git checkout -b split_commit &&
        test_commit one_split main.txt one &&
        test_commit two_split main.txt two &&
+       COMMIT2=$(git rev-parse --short split_commit) &&
        test_commit three_split main.txt three &&
+       COMMIT3=$(git rev-parse --short split_commit) &&
        test_commit four_split main.txt four &&
+       COMMIT4=$(git rev-parse --short split_commit) &&
        FAKE_LINES="1 edit 2 3" &&
        export FAKE_LINES &&
        test_when_finished "git rebase --abort" &&
@@ -216,7 +233,13 @@ test_expect_success 'status when splitting a commit' '
        git rebase -i HEAD~3 &&
        git reset HEAD^ &&
        cat >expected <<EOF &&
-rebase in progress; onto $ONTO
+interactive rebase in progress; onto $ONTO
+Last commands done (2 commands done):
+   pick $COMMIT2 two_split
+   edit $COMMIT3 three_split
+Next command to do (1 remaining command):
+   pick $COMMIT4 four_split
+  (use "git rebase --edit-todo" to view and edit)
 You are currently splitting a commit while rebasing branch '\''split_commit'\'' on '\''$ONTO'\''.
   (Once your working directory is clean, run "git rebase --continue")
 
@@ -239,7 +262,9 @@ test_expect_success 'status after editing the last commit with --amend during a
        test_commit one_amend main.txt one &&
        test_commit two_amend main.txt two &&
        test_commit three_amend main.txt three &&
+       COMMIT3=$(git rev-parse --short amend_last) &&
        test_commit four_amend main.txt four &&
+       COMMIT4=$(git rev-parse --short amend_last) &&
        FAKE_LINES="1 2 edit 3" &&
        export FAKE_LINES &&
        test_when_finished "git rebase --abort" &&
@@ -247,7 +272,12 @@ test_expect_success 'status after editing the last commit with --amend during a
        git rebase -i HEAD~3 &&
        git commit --amend -m "foo" &&
        cat >expected <<EOF &&
-rebase in progress; onto $ONTO
+interactive rebase in progress; onto $ONTO
+Last commands done (3 commands done):
+   pick $COMMIT3 three_amend
+   edit $COMMIT4 four_amend
+  (see more in file .git/rebase-merge/done)
+No commands remaining.
 You are currently editing a commit while rebasing branch '\''amend_last'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
   (use "git rebase --continue" once you are satisfied with your changes)
@@ -273,11 +303,20 @@ test_expect_success 'status: (continue first edit) second edit' '
        FAKE_LINES="edit 1 edit 2 3" &&
        export FAKE_LINES &&
        test_when_finished "git rebase --abort" &&
+       COMMIT2=$(git rev-parse --short several_edits^^) &&
+       COMMIT3=$(git rev-parse --short several_edits^) &&
+       COMMIT4=$(git rev-parse --short several_edits) &&
        ONTO=$(git rev-parse --short HEAD~3) &&
        git rebase -i HEAD~3 &&
        git rebase --continue &&
        cat >expected <<EOF &&
-rebase in progress; onto $ONTO
+interactive rebase in progress; onto $ONTO
+Last commands done (2 commands done):
+   edit $COMMIT2 two_edits
+   edit $COMMIT3 three_edits
+Next command to do (1 remaining command):
+   pick $COMMIT4 four_edits
+  (use "git rebase --edit-todo" to view and edit)
 You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
   (use "git rebase --continue" once you are satisfied with your changes)
@@ -294,12 +333,21 @@ test_expect_success 'status: (continue first edit) second edit and split' '
        FAKE_LINES="edit 1 edit 2 3" &&
        export FAKE_LINES &&
        test_when_finished "git rebase --abort" &&
+       COMMIT2=$(git rev-parse --short several_edits^^) &&
+       COMMIT3=$(git rev-parse --short several_edits^) &&
+       COMMIT4=$(git rev-parse --short several_edits) &&
        ONTO=$(git rev-parse --short HEAD~3) &&
        git rebase -i HEAD~3 &&
        git rebase --continue &&
        git reset HEAD^ &&
        cat >expected <<EOF &&
-rebase in progress; onto $ONTO
+interactive rebase in progress; onto $ONTO
+Last commands done (2 commands done):
+   edit $COMMIT2 two_edits
+   edit $COMMIT3 three_edits
+Next command to do (1 remaining command):
+   pick $COMMIT4 four_edits
+  (use "git rebase --edit-todo" to view and edit)
 You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (Once your working directory is clean, run "git rebase --continue")
 
@@ -321,12 +369,21 @@ test_expect_success 'status: (continue first edit) second edit and amend' '
        FAKE_LINES="edit 1 edit 2 3" &&
        export FAKE_LINES &&
        test_when_finished "git rebase --abort" &&
+       COMMIT2=$(git rev-parse --short several_edits^^) &&
+       COMMIT3=$(git rev-parse --short several_edits^) &&
+       COMMIT4=$(git rev-parse --short several_edits) &&
        ONTO=$(git rev-parse --short HEAD~3) &&
        git rebase -i HEAD~3 &&
        git rebase --continue &&
        git commit --amend -m "foo" &&
        cat >expected <<EOF &&
-rebase in progress; onto $ONTO
+interactive rebase in progress; onto $ONTO
+Last commands done (2 commands done):
+   edit $COMMIT2 two_edits
+   edit $COMMIT3 three_edits
+Next command to do (1 remaining command):
+   pick $COMMIT4 four_edits
+  (use "git rebase --edit-todo" to view and edit)
 You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
   (use "git rebase --continue" once you are satisfied with your changes)
@@ -343,12 +400,21 @@ test_expect_success 'status: (amend first edit) second edit' '
        FAKE_LINES="edit 1 edit 2 3" &&
        export FAKE_LINES &&
        test_when_finished "git rebase --abort" &&
+       COMMIT2=$(git rev-parse --short several_edits^^) &&
+       COMMIT3=$(git rev-parse --short several_edits^) &&
+       COMMIT4=$(git rev-parse --short several_edits) &&
        ONTO=$(git rev-parse --short HEAD~3) &&
        git rebase -i HEAD~3 &&
        git commit --amend -m "a" &&
        git rebase --continue &&
        cat >expected <<EOF &&
-rebase in progress; onto $ONTO
+interactive rebase in progress; onto $ONTO
+Last commands done (2 commands done):
+   edit $COMMIT2 two_edits
+   edit $COMMIT3 three_edits
+Next command to do (1 remaining command):
+   pick $COMMIT4 four_edits
+  (use "git rebase --edit-todo" to view and edit)
 You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
   (use "git rebase --continue" once you are satisfied with your changes)
@@ -366,12 +432,21 @@ test_expect_success 'status: (amend first edit) second edit and split' '
        export FAKE_LINES &&
        test_when_finished "git rebase --abort" &&
        ONTO=$(git rev-parse --short HEAD~3) &&
+       COMMIT2=$(git rev-parse --short several_edits^^) &&
+       COMMIT3=$(git rev-parse --short several_edits^) &&
+       COMMIT4=$(git rev-parse --short several_edits) &&
        git rebase -i HEAD~3 &&
        git commit --amend -m "b" &&
        git rebase --continue &&
        git reset HEAD^ &&
        cat >expected <<EOF &&
-rebase in progress; onto $ONTO
+interactive rebase in progress; onto $ONTO
+Last commands done (2 commands done):
+   edit $COMMIT2 two_edits
+   edit $COMMIT3 three_edits
+Next command to do (1 remaining command):
+   pick $COMMIT4 four_edits
+  (use "git rebase --edit-todo" to view and edit)
 You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (Once your working directory is clean, run "git rebase --continue")
 
@@ -393,13 +468,22 @@ test_expect_success 'status: (amend first edit) second edit and amend' '
        FAKE_LINES="edit 1 edit 2 3" &&
        export FAKE_LINES &&
        test_when_finished "git rebase --abort" &&
+       COMMIT2=$(git rev-parse --short several_edits^^) &&
+       COMMIT3=$(git rev-parse --short several_edits^) &&
+       COMMIT4=$(git rev-parse --short several_edits) &&
        ONTO=$(git rev-parse --short HEAD~3) &&
        git rebase -i HEAD~3 &&
        git commit --amend -m "c" &&
        git rebase --continue &&
        git commit --amend -m "d" &&
        cat >expected <<EOF &&
-rebase in progress; onto $ONTO
+interactive rebase in progress; onto $ONTO
+Last commands done (2 commands done):
+   edit $COMMIT2 two_edits
+   edit $COMMIT3 three_edits
+Next command to do (1 remaining command):
+   pick $COMMIT4 four_edits
+  (use "git rebase --edit-todo" to view and edit)
 You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
   (use "git rebase --continue" once you are satisfied with your changes)
@@ -416,6 +500,9 @@ test_expect_success 'status: (split first edit) second edit' '
        FAKE_LINES="edit 1 edit 2 3" &&
        export FAKE_LINES &&
        test_when_finished "git rebase --abort" &&
+       COMMIT2=$(git rev-parse --short several_edits^^) &&
+       COMMIT3=$(git rev-parse --short several_edits^) &&
+       COMMIT4=$(git rev-parse --short several_edits) &&
        ONTO=$(git rev-parse --short HEAD~3) &&
        git rebase -i HEAD~3 &&
        git reset HEAD^ &&
@@ -423,7 +510,13 @@ test_expect_success 'status: (split first edit) second edit' '
        git commit -m "e" &&
        git rebase --continue &&
        cat >expected <<EOF &&
-rebase in progress; onto $ONTO
+interactive rebase in progress; onto $ONTO
+Last commands done (2 commands done):
+   edit $COMMIT2 two_edits
+   edit $COMMIT3 three_edits
+Next command to do (1 remaining command):
+   pick $COMMIT4 four_edits
+  (use "git rebase --edit-todo" to view and edit)
 You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
   (use "git rebase --continue" once you are satisfied with your changes)
@@ -440,6 +533,9 @@ test_expect_success 'status: (split first edit) second edit and split' '
        FAKE_LINES="edit 1 edit 2 3" &&
        export FAKE_LINES &&
        test_when_finished "git rebase --abort" &&
+       COMMIT2=$(git rev-parse --short several_edits^^) &&
+       COMMIT3=$(git rev-parse --short several_edits^) &&
+       COMMIT4=$(git rev-parse --short several_edits) &&
        ONTO=$(git rev-parse --short HEAD~3) &&
        git rebase -i HEAD~3 &&
        git reset HEAD^ &&
@@ -448,7 +544,13 @@ test_expect_success 'status: (split first edit) second edit and split' '
        git rebase --continue &&
        git reset HEAD^ &&
        cat >expected <<EOF &&
-rebase in progress; onto $ONTO
+interactive rebase in progress; onto $ONTO
+Last commands done (2 commands done):
+   edit $COMMIT2 two_edits
+   edit $COMMIT3 three_edits
+Next command to do (1 remaining command):
+   pick $COMMIT4 four_edits
+  (use "git rebase --edit-todo" to view and edit)
 You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (Once your working directory is clean, run "git rebase --continue")
 
@@ -470,6 +572,9 @@ test_expect_success 'status: (split first edit) second edit and amend' '
        FAKE_LINES="edit 1 edit 2 3" &&
        export FAKE_LINES &&
        test_when_finished "git rebase --abort" &&
+       COMMIT2=$(git rev-parse --short several_edits^^) &&
+       COMMIT3=$(git rev-parse --short several_edits^) &&
+       COMMIT4=$(git rev-parse --short several_edits) &&
        ONTO=$(git rev-parse --short HEAD~3) &&
        git rebase -i HEAD~3 &&
        git reset HEAD^ &&
@@ -478,7 +583,13 @@ test_expect_success 'status: (split first edit) second edit and amend' '
        git rebase --continue &&
        git commit --amend -m "h" &&
        cat >expected <<EOF &&
-rebase in progress; onto $ONTO
+interactive rebase in progress; onto $ONTO
+Last commands done (2 commands done):
+   edit $COMMIT2 two_edits
+   edit $COMMIT3 three_edits
+Next command to do (1 remaining command):
+   pick $COMMIT4 four_edits
+  (use "git rebase --edit-todo" to view and edit)
 You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
   (use "git rebase --continue" once you are satisfied with your changes)
@@ -745,4 +856,91 @@ EOF
        test_i18ncmp expected actual
 '
 
+test_expect_success 'prepare for different number of commits rebased' '
+       git reset --hard master &&
+       git checkout -b several_commits &&
+       test_commit one_commit main.txt one &&
+       test_commit two_commit main.txt two &&
+       test_commit three_commit main.txt three &&
+       test_commit four_commit main.txt four
+'
+
+test_expect_success 'status: one command done nothing remaining' '
+       FAKE_LINES="exec_exit_15" &&
+       export FAKE_LINES &&
+       test_when_finished "git rebase --abort" &&
+       ONTO=$(git rev-parse --short HEAD~3) &&
+       test_must_fail git rebase -i HEAD~3 &&
+       cat >expected <<EOF &&
+interactive rebase in progress; onto $ONTO
+Last command done (1 command done):
+   exec exit 15
+No commands remaining.
+You are currently editing a commit while rebasing branch '\''several_commits'\'' on '\''$ONTO'\''.
+  (use "git commit --amend" to amend the current commit)
+  (use "git rebase --continue" once you are satisfied with your changes)
+
+nothing to commit (use -u to show untracked files)
+EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+test_expect_success 'status: two commands done with some white lines in done file' '
+       FAKE_LINES="1 > exec_exit_15  2 3" &&
+       export FAKE_LINES &&
+       test_when_finished "git rebase --abort" &&
+       ONTO=$(git rev-parse --short HEAD~3) &&
+       COMMIT4=$(git rev-parse --short HEAD) &&
+       COMMIT3=$(git rev-parse --short HEAD^) &&
+       COMMIT2=$(git rev-parse --short HEAD^^) &&
+       test_must_fail git rebase -i HEAD~3 &&
+       cat >expected <<EOF &&
+interactive rebase in progress; onto $ONTO
+Last commands done (2 commands done):
+   pick $COMMIT2 two_commit
+   exec exit 15
+Next commands to do (2 remaining commands):
+   pick $COMMIT3 three_commit
+   pick $COMMIT4 four_commit
+  (use "git rebase --edit-todo" to view and edit)
+You are currently editing a commit while rebasing branch '\''several_commits'\'' on '\''$ONTO'\''.
+  (use "git commit --amend" to amend the current commit)
+  (use "git rebase --continue" once you are satisfied with your changes)
+
+nothing to commit (use -u to show untracked files)
+EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+test_expect_success 'status: two remaining commands with some white lines in todo file' '
+       FAKE_LINES="1 2 exec_exit_15 3 > 4" &&
+       export FAKE_LINES &&
+       test_when_finished "git rebase --abort" &&
+       ONTO=$(git rev-parse --short HEAD~4) &&
+       COMMIT4=$(git rev-parse --short HEAD) &&
+       COMMIT3=$(git rev-parse --short HEAD^) &&
+       COMMIT2=$(git rev-parse --short HEAD^^) &&
+       test_must_fail git rebase -i HEAD~4 &&
+       cat >expected <<EOF &&
+interactive rebase in progress; onto $ONTO
+Last commands done (3 commands done):
+   pick $COMMIT2 two_commit
+   exec exit 15
+  (see more in file .git/rebase-merge/done)
+Next commands to do (2 remaining commands):
+   pick $COMMIT3 three_commit
+   pick $COMMIT4 four_commit
+  (use "git rebase --edit-todo" to view and edit)
+You are currently editing a commit while rebasing branch '\''several_commits'\'' on '\''$ONTO'\''.
+  (use "git commit --amend" to amend the current commit)
+  (use "git rebase --continue" once you are satisfied with your changes)
+
+nothing to commit (use -u to show untracked files)
+EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
 test_done
diff --git a/t/t9000-addresses.sh b/t/t9000-addresses.sh
new file mode 100755 (executable)
index 0000000..a1ebef6
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+test_description='compare address parsing with and without Mail::Address'
+. ./test-lib.sh
+
+if ! test_have_prereq PERL; then
+       skip_all='skipping perl interface tests, perl not available'
+       test_done
+fi
+
+perl -MTest::More -e 0 2>/dev/null || {
+       skip_all="Perl Test::More unavailable, skipping test"
+       test_done
+}
+
+perl -MMail::Address -e 0 2>/dev/null || {
+       skip_all="Perl Mail::Address unavailable, skipping test"
+       test_done
+}
+
+test_external_has_tap=1
+
+test_external_without_stderr \
+       'Perl address parsing function' \
+       perl "$TEST_DIRECTORY"/t9000/test.pl
+
+test_done
diff --git a/t/t9000/test.pl b/t/t9000/test.pl
new file mode 100755 (executable)
index 0000000..2d05d3e
--- /dev/null
@@ -0,0 +1,67 @@
+#!/usr/bin/perl
+use lib (split(/:/, $ENV{GITPERLLIB}));
+
+use 5.008;
+use warnings;
+use strict;
+
+use Test::More qw(no_plan);
+use Mail::Address;
+
+BEGIN { use_ok('Git') }
+
+my @success_list = (q[Jane],
+       q[jdoe@example.com],
+       q[<jdoe@example.com>],
+       q[Jane <jdoe@example.com>],
+       q[Jane Doe <jdoe@example.com>],
+       q["Jane" <jdoe@example.com>],
+       q["Doe, Jane" <jdoe@example.com>],
+       q["Jane@:;\>.,()<Doe" <jdoe@example.com>],
+       q[Jane!#$%&'*+-/=?^_{|}~Doe' <jdoe@example.com>],
+       q["<jdoe@example.com>"],
+       q["Jane jdoe@example.com"],
+       q[Jane Doe <jdoe    @   example.com  >],
+       q[Jane       Doe <  jdoe@example.com  >],
+       q[Jane @ Doe @ Jane @ Doe],
+       q["Jane, 'Doe'" <jdoe@example.com>],
+       q['Doe, "Jane' <jdoe@example.com>],
+       q["Jane" "Do"e <jdoe@example.com>],
+       q["Jane' Doe" <jdoe@example.com>],
+       q["Jane Doe <jdoe@example.com>" <jdoe@example.com>],
+       q["Jane\" Doe" <jdoe@example.com>],
+       q[Doe, jane <jdoe@example.com>],
+       q["Jane Doe <jdoe@example.com>],
+       q['Jane 'Doe' <jdoe@example.com>]);
+
+my @known_failure_list = (q[Jane\ Doe <jdoe@example.com>],
+       q["Doe, Ja"ne <jdoe@example.com>],
+       q["Doe, Katarina" Jane <jdoe@example.com>],
+       q[Jane@:;\.,()<>Doe <jdoe@example.com>],
+       q[Jane jdoe@example.com],
+       q[<jdoe@example.com> Jane Doe],
+       q[Jane <jdoe@example.com> Doe],
+       q["Jane "Kat"a" ri"na" ",Doe" <jdoe@example.com>],
+       q[Jane Doe],
+       q[Jane "Doe <jdoe@example.com>"],
+       q[\"Jane Doe <jdoe@example.com>],
+       q[Jane\"\" Doe <jdoe@example.com>],
+       q['Jane "Katarina\" \' Doe' <jdoe@example.com>]);
+
+foreach my $str (@success_list) {
+       my @expected = map { $_->format } Mail::Address->parse("$str");
+       my @actual = Git::parse_mailboxes("$str");
+       is_deeply(\@expected, \@actual, qq[same output : $str]);
+}
+
+TODO: {
+       local $TODO = "known breakage";
+       foreach my $str (@known_failure_list) {
+               my @expected = map { $_->format } Mail::Address->parse("$str");
+               my @actual = Git::parse_mailboxes("$str");
+               is_deeply(\@expected, \@actual, qq[same output : $str]);
+       }
+}
+
+my $is_passing = eval { Test::More->is_passing };
+exit($is_passing ? 0 : 1) unless $@ =~ /Can't locate object method/;
index db2f45e83b14fb5ae5b5f32b4178d7cd1ce97bf8..5b4a5ce06b94355725fafc84c31f9ed67b8b15ed 100755 (executable)
@@ -312,13 +312,19 @@ test_expect_success $PREREQ,!AUTOIDENT 'broken implicit ident aborts send-email'
        )
 '
 
+test_expect_success $PREREQ 'setup tocmd and cccmd scripts' '
+       write_script tocmd-sed <<-\EOF &&
+       sed -n -e "s/^tocmd--//p" "$1"
+       EOF
+       write_script cccmd-sed <<-\EOF
+       sed -n -e "s/^cccmd--//p" "$1"
+       EOF
+'
+
 test_expect_success $PREREQ 'tocmd works' '
        clean_fake_sendmail &&
        cp $patches tocmd.patch &&
        echo tocmd--tocmd@example.com >>tocmd.patch &&
-       write_script tocmd-sed <<-\EOF &&
-       sed -n -e "s/^tocmd--//p" "$1"
-       EOF
        git send-email \
                --from="Example <nobody@example.com>" \
                --to-cmd=./tocmd-sed \
@@ -332,9 +338,6 @@ test_expect_success $PREREQ 'cccmd works' '
        clean_fake_sendmail &&
        cp $patches cccmd.patch &&
        echo "cccmd--  cccmd@example.com" >>cccmd.patch &&
-       write_script cccmd-sed <<-\EOF &&
-       sed -n -e "s/^cccmd--//p" "$1"
-       EOF
        git send-email \
                --from="Example <nobody@example.com>" \
                --to=nobody@example.com \
@@ -519,6 +522,12 @@ Result: OK
 EOF
 "
 
+replace_variable_fields () {
+       sed     -e "s/^\(Date:\).*/\1 DATE-STRING/" \
+               -e "s/^\(Message-Id:\).*/\1 MESSAGE-ID-STRING/" \
+               -e "s/^\(X-Mailer:\).*/\1 X-MAILER-STRING/"
+}
+
 test_suppression () {
        git send-email \
                --dry-run \
@@ -526,10 +535,7 @@ test_suppression () {
                --from="Example <from@example.com>" \
                --to=to@example.com \
                --smtp-server relay.example.com \
-               $patches |
-       sed     -e "s/^\(Date:\).*/\1 DATE-STRING/" \
-               -e "s/^\(Message-Id:\).*/\1 MESSAGE-ID-STRING/" \
-               -e "s/^\(X-Mailer:\).*/\1 X-MAILER-STRING/" \
+               $patches | replace_variable_fields \
                >actual-suppress-$1${2+"-$2"} &&
        test_cmp expected-suppress-$1${2+"-$2"} actual-suppress-$1${2+"-$2"}
 }
@@ -1621,6 +1627,66 @@ test_sendmail_aliases 'sendmail aliases tolerate bogus line folding' \
 test_sendmail_aliases 'sendmail aliases empty' alice bcgrp <<-\EOF
        EOF
 
+test_expect_success $PREREQ 'alias support in To header' '
+       clean_fake_sendmail &&
+       echo "alias sbd  someone@example.org" >.mailrc &&
+       test_config sendemail.aliasesfile ".mailrc" &&
+       test_config sendemail.aliasfiletype mailrc &&
+       git format-patch --stdout -1 --to=sbd >aliased.patch &&
+       git send-email \
+               --from="Example <nobody@example.com>" \
+               --smtp-server="$(pwd)/fake.sendmail" \
+               aliased.patch \
+               2>errors >out &&
+       grep "^!someone@example\.org!$" commandline1
+'
+
+test_expect_success $PREREQ 'alias support in Cc header' '
+       clean_fake_sendmail &&
+       echo "alias sbd  someone@example.org" >.mailrc &&
+       test_config sendemail.aliasesfile ".mailrc" &&
+       test_config sendemail.aliasfiletype mailrc &&
+       git format-patch --stdout -1 --cc=sbd >aliased.patch &&
+       git send-email \
+               --from="Example <nobody@example.com>" \
+               --smtp-server="$(pwd)/fake.sendmail" \
+               aliased.patch \
+               2>errors >out &&
+       grep "^!someone@example\.org!$" commandline1
+'
+
+test_expect_success $PREREQ 'tocmd works with aliases' '
+       clean_fake_sendmail &&
+       echo "alias sbd  someone@example.org" >.mailrc &&
+       test_config sendemail.aliasesfile ".mailrc" &&
+       test_config sendemail.aliasfiletype mailrc &&
+       git format-patch --stdout -1 >tocmd.patch &&
+       echo tocmd--sbd >>tocmd.patch &&
+       git send-email \
+               --from="Example <nobody@example.com>" \
+               --to-cmd=./tocmd-sed \
+               --smtp-server="$(pwd)/fake.sendmail" \
+               tocmd.patch \
+               2>errors >out &&
+       grep "^!someone@example\.org!$" commandline1
+'
+
+test_expect_success $PREREQ 'cccmd works with aliases' '
+       clean_fake_sendmail &&
+       echo "alias sbd  someone@example.org" >.mailrc &&
+       test_config sendemail.aliasesfile ".mailrc" &&
+       test_config sendemail.aliasfiletype mailrc &&
+       git format-patch --stdout -1 >cccmd.patch &&
+       echo cccmd--sbd >>cccmd.patch &&
+       git send-email \
+               --from="Example <nobody@example.com>" \
+               --cc-cmd=./cccmd-sed \
+               --smtp-server="$(pwd)/fake.sendmail" \
+               cccmd.patch \
+               2>errors >out &&
+       grep "^!someone@example\.org!$" commandline1
+'
+
 do_xmailer_test () {
        expected=$1 params=$2 &&
        git format-patch -1 &&
@@ -1654,4 +1720,72 @@ test_expect_success $PREREQ '--[no-]xmailer with sendemail.xmailer=false' '
        do_xmailer_test 1 "--xmailer"
 '
 
+test_expect_success $PREREQ 'setup expected-list' '
+       git send-email \
+       --dry-run \
+       --from="Example <from@example.com>" \
+       --to="To 1 <to1@example.com>" \
+       --to="to2@example.com" \
+       --to="to3@example.com" \
+       --cc="Cc 1 <cc1@example.com>" \
+       --cc="Cc2 <cc2@example.com>" \
+       --bcc="bcc1@example.com" \
+       --bcc="bcc2@example.com" \
+       0001-add-master.patch | replace_variable_fields \
+       >expected-list
+'
+
+test_expect_success $PREREQ 'use email list in --cc --to and --bcc' '
+       git send-email \
+       --dry-run \
+       --from="Example <from@example.com>" \
+       --to="To 1 <to1@example.com>, to2@example.com" \
+       --to="to3@example.com" \
+       --cc="Cc 1 <cc1@example.com>, Cc2 <cc2@example.com>" \
+       --bcc="bcc1@example.com, bcc2@example.com" \
+       0001-add-master.patch | replace_variable_fields \
+       >actual-list &&
+       test_cmp expected-list actual-list
+'
+
+test_expect_success $PREREQ 'aliases work with email list' '
+       echo "alias to2 to2@example.com" >.mutt &&
+       echo "alias cc1 Cc 1 <cc1@example.com>" >>.mutt &&
+       test_config sendemail.aliasesfile ".mutt" &&
+       test_config sendemail.aliasfiletype mutt &&
+       git send-email \
+       --dry-run \
+       --from="Example <from@example.com>" \
+       --to="To 1 <to1@example.com>, to2, to3@example.com" \
+       --cc="cc1, Cc2 <cc2@example.com>" \
+       --bcc="bcc1@example.com, bcc2@example.com" \
+       0001-add-master.patch | replace_variable_fields \
+       >actual-list &&
+       test_cmp expected-list actual-list
+'
+
+test_expect_success $PREREQ 'leading and trailing whitespaces are removed' '
+       echo "alias to2 to2@example.com" >.mutt &&
+       echo "alias cc1 Cc 1 <cc1@example.com>" >>.mutt &&
+       test_config sendemail.aliasesfile ".mutt" &&
+       test_config sendemail.aliasfiletype mutt &&
+       TO1=$(echo "QTo 1 <to1@example.com>" | q_to_tab) &&
+       TO2=$(echo "QZto2" | qz_to_tab_space) &&
+       CC1=$(echo "cc1" | append_cr) &&
+       BCC1=$(echo "Q bcc1@example.com Q" | q_to_nul) &&
+       git send-email \
+       --dry-run \
+       --from="        Example <from@example.com>" \
+       --to="$TO1" \
+       --to="$TO2" \
+       --to="  to3@example.com   " \
+       --cc="$CC1" \
+       --cc="Cc2 <cc2@example.com>" \
+       --bcc="$BCC1" \
+       --bcc="bcc2@example.com" \
+       0001-add-master.patch | replace_variable_fields \
+       >actual-list &&
+       test_cmp expected-list actual-list
+'
+
 test_done
index aac126fd57cc140f90e841e6568152e15d1a90e7..9984c48b5a75a2f6442616602e79f4689e7066fb 100755 (executable)
@@ -2339,6 +2339,19 @@ test_expect_success !MINGW 'R: in-stream cat-blob-fd not respected' '
        test_cmp expect actual.1
 '
 
+test_expect_success !MINGW 'R: print mark for new blob' '
+       echo "effluentish" | git hash-object --stdin >expect &&
+       git fast-import --cat-blob-fd=6 6>actual <<-\EOF &&
+       blob
+       mark :1
+       data <<BLOB_END
+       effluentish
+       BLOB_END
+       get-mark :1
+       EOF
+       test_cmp expect actual
+'
+
 test_expect_success !MINGW 'R: print new blob' '
        blob=$(echo "yep yep yep" | git hash-object --stdin) &&
        cat >expect <<-EOF &&
index 49d58e6726836194e7a716365007465056744fc5..6b68777b9804a86a9d6e0bef63b5073e895efcfe 100755 (executable)
@@ -397,6 +397,31 @@ test_expect_success 'prompt - untracked files status indicator - untracked files
        test_cmp expected "$actual"
 '
 
+test_expect_success 'prompt - untracked files status indicator - empty untracked dir' '
+       printf " (master)" >expected &&
+       mkdir otherrepo/untracked-dir &&
+       test_when_finished "rm -rf otherrepo/untracked-dir" &&
+       (
+               GIT_PS1_SHOWUNTRACKEDFILES=y &&
+               cd otherrepo &&
+               __git_ps1 >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - untracked files status indicator - non-empty untracked dir' '
+       printf " (master %%)" >expected &&
+       mkdir otherrepo/untracked-dir &&
+       test_when_finished "rm -rf otherrepo/untracked-dir" &&
+       >otherrepo/untracked-dir/untracked-file &&
+       (
+               GIT_PS1_SHOWUNTRACKEDFILES=y &&
+               cd otherrepo &&
+               __git_ps1 >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
 test_expect_success 'prompt - untracked files status indicator - untracked files outside cwd' '
        printf " (master %%)" >expected &&
        (
index cea6cda6048e22e111f2c1f2c216d448868394ac..16c4d7b516649a13e3f07e081e91e1212afc61a5 100644 (file)
@@ -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
index 94a6997a8f6aa40cd86859dbd3eb27704e46b034..63f373557e7236d294fb580d38db9e6d331e53d3 100644 (file)
@@ -29,7 +29,7 @@ static void parse_dates(char **argv, struct timeval *now)
                parse_date(*argv, &result);
                if (sscanf(result.buf, "%lu %d", &t, &tz) == 2)
                        printf("%s -> %s\n",
-                              *argv, show_date(t, tz, DATE_ISO8601));
+                              *argv, show_date(t, tz, DATE_MODE(ISO8601)));
                else
                        printf("%s -> bad\n", *argv);
        }
@@ -41,7 +41,7 @@ static void parse_approxidate(char **argv, struct timeval *now)
        for (; *argv; argv++) {
                time_t t;
                t = approxidate_relative(*argv, now);
-               printf("%s -> %s\n", *argv, show_date(t, 0, DATE_ISO8601));
+               printf("%s -> %s\n", *argv, show_date(t, 0, DATE_MODE(ISO8601)));
        }
 }
 
index 5dabce60f363430d3800bdadc77b3617abbcbfb6..2c8c8f18edb46378b39c170b4ae6f7250ec631f5 100644 (file)
@@ -4,6 +4,7 @@
 
 static int boolean = 0;
 static int integer = 0;
+static unsigned long magnitude = 0;
 static unsigned long timestamp;
 static int abbrev = 7;
 static int verbose = 0, dry_run = 0, quiet = 0;
@@ -48,6 +49,7 @@ int main(int argc, char **argv)
                OPT_GROUP(""),
                OPT_INTEGER('i', "integer", &integer, "get a integer"),
                OPT_INTEGER('j', NULL, &integer, "get a integer, too"),
+               OPT_MAGNITUDE('m', "magnitude", &magnitude, "get a magnitude"),
                OPT_SET_INT(0, "set23", &integer, "set integer to 23", 23),
                OPT_DATE('t', NULL, &timestamp, "get timestamp of <time>"),
                OPT_CALLBACK('L', "length", &integer, "str",
@@ -82,7 +84,8 @@ int main(int argc, char **argv)
        argc = parse_options(argc, (const char **)argv, prefix, options, usage, 0);
 
        printf("boolean: %d\n", boolean);
-       printf("integer: %u\n", integer);
+       printf("integer: %d\n", integer);
+       printf("magnitude: %lu\n", magnitude);
        printf("timestamp: %lu\n", timestamp);
        printf("string: %s\n", string ? string : "(not set)");
        printf("abbrev: %d\n", abbrev);
index 3ade02c72d8801a4965431518f796df4835dc473..285f06b7ff262378150be80f2a09b4162ae3a564 100644 (file)
@@ -17,7 +17,7 @@ static void print_commit(struct commit *commit)
 {
        struct strbuf sb = STRBUF_INIT;
        struct pretty_print_context ctx = {0};
-       ctx.date_mode = DATE_NORMAL;
+       ctx.date_mode.type = DATE_NORMAL;
        format_commit_message(commit, " %m %s", &sb, &ctx);
        printf("%s\n", sb.buf);
        strbuf_release(&sb);
diff --git a/trace.c b/trace.c
index 3c3bd8fc98742075d6adc673ed2abc4f7e715b62..7393926ebcd95404fe16cdd84e40fd4f1bb02f40 100644 (file)
--- a/trace.c
+++ b/trace.c
@@ -120,6 +120,13 @@ static int prepare_trace_line(const char *file, int line,
        return 1;
 }
 
+void trace_verbatim(struct trace_key *key, const void *buf, unsigned len)
+{
+       if (!trace_want(key))
+               return;
+       write_or_whine_pipe(get_trace_fd(key), buf, len, err_msg);
+}
+
 static void print_trace_line(struct trace_key *key, struct strbuf *buf)
 {
        strbuf_complete_line(buf);
diff --git a/trace.h b/trace.h
index ae6a3329470cae428bb0864e51b14ea42078b182..179b249c597125ac8ad3ed2d0c0e6b00a94773eb 100644 (file)
--- a/trace.h
+++ b/trace.h
@@ -18,6 +18,7 @@ extern int trace_want(struct trace_key *key);
 extern void trace_disable(struct trace_key *key);
 extern uint64_t getnanotime(void);
 extern void trace_command_performance(const char **argv);
+extern void trace_verbatim(struct trace_key *key, const void *buf, unsigned len);
 
 #ifndef HAVE_VARIADIC_MACROS
 
index 5d99a6bc2e7a10d40f6d8213e2f3cad254dbdb04..68e498eebdda08f992e11154039e2e94e58f10d1 100644 (file)
@@ -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);
index 2927660d929eee776d43a87851a928df12b17716..bb09b1ed27a6c973c93f5ec855973e277279549f 100644 (file)
@@ -224,6 +224,9 @@ static int check_updates(struct unpack_trees_options *o)
                struct cache_entry *ce = index->cache[i];
 
                if (ce->ce_flags & CE_UPDATE) {
+                       if (ce->ce_flags & CE_WT_REMOVE)
+                               die("BUG: both update and delete flags are set on %s",
+                                   ce->name);
                        display_progress(progress, ++cnt);
                        ce->ce_flags &= ~CE_UPDATE;
                        if (o->update && !o->dry_run) {
@@ -293,6 +296,7 @@ static int apply_sparse_checkout(struct index_state *istate,
                if (!(ce->ce_flags & CE_UPDATE) && verify_uptodate_sparse(ce, o))
                        return -1;
                ce->ce_flags |= CE_WT_REMOVE;
+               ce->ce_flags &= ~CE_UPDATE;
        }
        if (was_skip_worktree && !ce_skip_worktree(ce)) {
                if (verify_absent_sparse(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o))
@@ -1025,10 +1029,12 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
        if (!core_apply_sparse_checkout || !o->update)
                o->skip_sparse_checkout = 1;
        if (!o->skip_sparse_checkout) {
-               if (add_excludes_from_file_to_list(git_path("info/sparse-checkout"), "", 0, &el, 0) < 0)
+               char *sparse = git_pathdup("info/sparse-checkout");
+               if (add_excludes_from_file_to_list(sparse, "", 0, &el, 0) < 0)
                        o->skip_sparse_checkout = 1;
                else
                        o->el = &el;
+               free(sparse);
        }
 
        memset(&o->result, 0, sizeof(o->result));
@@ -1156,6 +1162,14 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
        o->src_index = NULL;
        ret = check_updates(o) ? (-2) : 0;
        if (o->dst_index) {
+               if (!ret) {
+                       if (!o->result.cache_tree)
+                               o->result.cache_tree = cache_tree();
+                       if (!cache_tree_fully_valid(o->result.cache_tree))
+                               cache_tree_update(&o->result,
+                                                 WRITE_TREE_SILENT |
+                                                 WRITE_TREE_REPAIR);
+               }
                discard_index(o->dst_index);
                *o->dst_index = o->result;
        } else {
index 2ccbee50cb69171062e84c9a1b36baf7e31be2f8..6bf2505994b1d1054604b84d49faeb45eb6a9534 100644 (file)
@@ -35,6 +35,8 @@ IPATTERN("fortran",
          * they would have been matched above as a variable anyway. */
         "|[-+]?[0-9.]+([AaIiDdEeFfLlTtXx][Ss]?[-+]?[0-9.]*)?(_[a-zA-Z0-9][a-zA-Z0-9_]*)?"
         "|//|\\*\\*|::|[/<>=]="),
+IPATTERN("fountain", "^((\\.[^.]|(int|ext|est|int\\.?/ext|i/e)[. ]).*)$",
+        "[^ \t-]+"),
 PATTERNS("html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$",
         "[^<>= \t]+"),
 PATTERNS("java",
index ff49807948f3c406fe73511cec4ef4e2b2df186a..e4514634313c44354190e0c7e08a2b501aa0b098 100644 (file)
--- 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);
index eaed4fed32f48f4834afaf47f87bda7ce4794308..717fd48d13eb75730ed44a786473c7662173891c 100644 (file)
@@ -1027,21 +1027,142 @@ static int split_commit_in_progress(struct wt_status *s)
        return split_in_progress;
 }
 
+/*
+ * Turn
+ * "pick d6a2f0303e897ec257dd0e0a39a5ccb709bc2047 some message"
+ * into
+ * "pick d6a2f03 some message"
+ *
+ * The function assumes that the line does not contain useless spaces
+ * before or after the command.
+ */
+static void abbrev_sha1_in_line(struct strbuf *line)
+{
+       struct strbuf **split;
+       int i;
+
+       if (starts_with(line->buf, "exec ") ||
+           starts_with(line->buf, "x "))
+               return;
+
+       split = strbuf_split_max(line, ' ', 3);
+       if (split[0] && split[1]) {
+               unsigned char sha1[20];
+               const char *abbrev;
+
+               /*
+                * strbuf_split_max left a space. Trim it and re-add
+                * it after abbreviation.
+                */
+               strbuf_trim(split[1]);
+               if (!get_sha1(split[1]->buf, sha1)) {
+                       abbrev = find_unique_abbrev(sha1, DEFAULT_ABBREV);
+                       strbuf_reset(split[1]);
+                       strbuf_addf(split[1], "%s ", abbrev);
+                       strbuf_reset(line);
+                       for (i = 0; split[i]; i++)
+                               strbuf_addf(line, "%s", split[i]->buf);
+               }
+       }
+       for (i = 0; split[i]; i++)
+               strbuf_release(split[i]);
+
+}
+
+static void read_rebase_todolist(const char *fname, struct string_list *lines)
+{
+       struct strbuf line = STRBUF_INIT;
+       FILE *f = fopen(git_path("%s", fname), "r");
+
+       if (!f)
+               die_errno("Could not open file %s for reading",
+                         git_path("%s", fname));
+       while (!strbuf_getline(&line, f, '\n')) {
+               if (line.len && line.buf[0] == comment_line_char)
+                       continue;
+               strbuf_trim(&line);
+               if (!line.len)
+                       continue;
+               abbrev_sha1_in_line(&line);
+               string_list_append(lines, line.buf);
+       }
+}
+
+static void show_rebase_information(struct wt_status *s,
+                                       struct wt_status_state *state,
+                                       const char *color)
+{
+       if (state->rebase_interactive_in_progress) {
+               int i;
+               int nr_lines_to_show = 2;
+
+               struct string_list have_done = STRING_LIST_INIT_DUP;
+               struct string_list yet_to_do = STRING_LIST_INIT_DUP;
+
+               read_rebase_todolist("rebase-merge/done", &have_done);
+               read_rebase_todolist("rebase-merge/git-rebase-todo", &yet_to_do);
+
+               if (have_done.nr == 0)
+                       status_printf_ln(s, color, _("No commands done."));
+               else {
+                       status_printf_ln(s, color,
+                               Q_("Last command done (%d command done):",
+                                       "Last commands done (%d commands done):",
+                                       have_done.nr),
+                               have_done.nr);
+                       for (i = (have_done.nr > nr_lines_to_show)
+                               ? have_done.nr - nr_lines_to_show : 0;
+                               i < have_done.nr;
+                               i++)
+                               status_printf_ln(s, color, "   %s", have_done.items[i].string);
+                       if (have_done.nr > nr_lines_to_show && s->hints)
+                               status_printf_ln(s, color,
+                                       _("  (see more in file %s)"), git_path("rebase-merge/done"));
+               }
+
+               if (yet_to_do.nr == 0)
+                       status_printf_ln(s, color,
+                                        _("No commands remaining."));
+               else {
+                       status_printf_ln(s, color,
+                               Q_("Next command to do (%d remaining command):",
+                                       "Next commands to do (%d remaining commands):",
+                                       yet_to_do.nr),
+                               yet_to_do.nr);
+                       for (i = 0; i < nr_lines_to_show && i < yet_to_do.nr; i++)
+                               status_printf_ln(s, color, "   %s", yet_to_do.items[i].string);
+                       if (s->hints)
+                               status_printf_ln(s, color,
+                                       _("  (use \"git rebase --edit-todo\" to view and edit)"));
+               }
+               string_list_clear(&yet_to_do, 0);
+               string_list_clear(&have_done, 0);
+       }
+}
+
+static void print_rebase_state(struct wt_status *s,
+                               struct wt_status_state *state,
+                               const char *color)
+{
+       if (state->branch)
+               status_printf_ln(s, color,
+                                _("You are currently rebasing branch '%s' on '%s'."),
+                                state->branch,
+                                state->onto);
+       else
+               status_printf_ln(s, color,
+                                _("You are currently rebasing."));
+}
+
 static void show_rebase_in_progress(struct wt_status *s,
                                struct wt_status_state *state,
                                const char *color)
 {
        struct stat st;
 
+       show_rebase_information(s, state, color);
        if (has_unmerged(s)) {
-               if (state->branch)
-                       status_printf_ln(s, color,
-                                        _("You are currently rebasing branch '%s' on '%s'."),
-                                        state->branch,
-                                        state->onto);
-               else
-                       status_printf_ln(s, color,
-                                        _("You are currently rebasing."));
+               print_rebase_state(s, state, color);
                if (s->hints) {
                        status_printf_ln(s, color,
                                _("  (fix conflicts and then run \"git rebase --continue\")"));
@@ -1050,15 +1171,8 @@ static void show_rebase_in_progress(struct wt_status *s,
                        status_printf_ln(s, color,
                                _("  (use \"git rebase --abort\" to check out the original branch)"));
                }
-       } else if (state->rebase_in_progress || !stat(git_path("MERGE_MSG"), &st)) {
-               if (state->branch)
-                       status_printf_ln(s, color,
-                                        _("You are currently rebasing branch '%s' on '%s'."),
-                                        state->branch,
-                                        state->onto);
-               else
-                       status_printf_ln(s, color,
-                                        _("You are currently rebasing."));
+       } else if (state->rebase_in_progress || !stat(git_path_merge_msg(), &st)) {
+               print_rebase_state(s, state, color);
                if (s->hints)
                        status_printf_ln(s, color,
                                _("  (all conflicts fixed: run \"git rebase --continue\")"));
@@ -1254,7 +1368,7 @@ void wt_status_get_state(struct wt_status_state *state,
        struct stat st;
        unsigned char sha1[20];
 
-       if (!stat(git_path("MERGE_HEAD"), &st)) {
+       if (!stat(git_path_merge_head(), &st)) {
                state->merge_in_progress = 1;
        } else if (!stat(git_path("rebase-apply"), &st)) {
                if (!stat(git_path("rebase-apply/applying"), &st)) {
@@ -1273,7 +1387,7 @@ void wt_status_get_state(struct wt_status_state *state,
                        state->rebase_in_progress = 1;
                state->branch = read_and_strip_branch("rebase-merge/head-name");
                state->onto = read_and_strip_branch("rebase-merge/onto");
-       } else if (!stat(git_path("CHERRY_PICK_HEAD"), &st) &&
+       } else if (!stat(git_path_cherry_pick_head(), &st) &&
                        !get_sha1("CHERRY_PICK_HEAD", sha1)) {
                state->cherry_pick_in_progress = 1;
                hashcpy(state->cherry_pick_head_sha1, sha1);
@@ -1282,7 +1396,7 @@ void wt_status_get_state(struct wt_status_state *state,
                state->bisect_in_progress = 1;
                state->branch = read_and_strip_branch("BISECT_START");
        }
-       if (!stat(git_path("REVERT_HEAD"), &st) &&
+       if (!stat(git_path_revert_head(), &st) &&
            !get_sha1("REVERT_HEAD", sha1)) {
                state->revert_in_progress = 1;
                hashcpy(state->revert_head_sha1, sha1);
@@ -1328,7 +1442,10 @@ void wt_status_print(struct wt_status *s)
                else if (!strcmp(branch_name, "HEAD")) {
                        branch_status_color = color(WT_STATUS_NOBRANCH, s);
                        if (state.rebase_in_progress || state.rebase_interactive_in_progress) {
-                               on_what = _("rebase in progress; onto ");
+                               if (state.rebase_interactive_in_progress)
+                                       on_what = _("interactive rebase in progress; onto ");
+                               else
+                                       on_what = _("rebase in progress; onto ");
                                branch_name = state.onto;
                        } else if (state.detached_from) {
                                branch_name = state.detached_from;