Merge branch 'bw/ref-prefix-for-configured-refspec'
authorJunio C Hamano <gitster@pobox.com>
Fri, 1 Jun 2018 06:15:35 +0000 (15:15 +0900)
committerJunio C Hamano <gitster@pobox.com>
Fri, 1 Jun 2018 06:15:35 +0000 (15:15 +0900)
* bw/ref-prefix-for-configured-refspec:
fetch: do not pass ref-prefixes for fetch by exact SHA1

425 files changed:
.gitattributes
.gitignore
.mailmap
.travis.yml
Documentation/Makefile
Documentation/RelNotes/2.13.7.txt [new file with mode: 0644]
Documentation/RelNotes/2.14.4.txt [new file with mode: 0644]
Documentation/RelNotes/2.15.2.txt
Documentation/RelNotes/2.16.4.txt [new file with mode: 0644]
Documentation/RelNotes/2.17.1.txt [new file with mode: 0644]
Documentation/RelNotes/2.18.0.txt
Documentation/config.txt
Documentation/diff-config.txt
Documentation/fetch-options.txt
Documentation/git-add.txt
Documentation/git-annotate.txt
Documentation/git-apply.txt
Documentation/git-branch.txt
Documentation/git-bundle.txt
Documentation/git-check-attr.txt
Documentation/git-check-ignore.txt
Documentation/git-check-mailmap.txt
Documentation/git-clone.txt
Documentation/git-credential-cache.txt
Documentation/git-credential-store.txt
Documentation/git-cvsserver.txt
Documentation/git-diff-index.txt
Documentation/git-diff-tree.txt
Documentation/git-diff.txt
Documentation/git-fast-export.txt
Documentation/git-fast-import.txt
Documentation/git-filter-branch.txt
Documentation/git-fmt-merge-msg.txt
Documentation/git-gc.txt
Documentation/git-grep.txt
Documentation/git-help.txt
Documentation/git-http-push.txt
Documentation/git-imap-send.txt
Documentation/git-index-pack.txt
Documentation/git-interpret-trailers.txt
Documentation/git-ls-files.txt
Documentation/git-ls-remote.txt
Documentation/git-name-rev.txt
Documentation/git-p4.txt
Documentation/git-pack-objects.txt
Documentation/git-prune.txt
Documentation/git-pull.txt
Documentation/git-push.txt
Documentation/git-read-tree.txt
Documentation/git-rebase.txt
Documentation/git-receive-pack.txt
Documentation/git-remote-ext.txt
Documentation/git-remote.txt
Documentation/git-repack.txt
Documentation/git-replace.txt
Documentation/git-request-pull.txt
Documentation/git-rev-parse.txt
Documentation/git-send-email.txt
Documentation/git-send-pack.txt
Documentation/git-shell.txt
Documentation/git-show-branch.txt
Documentation/git-show-ref.txt
Documentation/git-show.txt
Documentation/git-status.txt
Documentation/git-submodule.txt
Documentation/git-svn.txt
Documentation/git-tag.txt
Documentation/git-update-index.txt
Documentation/git-update-ref.txt
Documentation/git-var.txt
Documentation/git-web--browse.txt
Documentation/git-worktree.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/gitcli.txt
Documentation/githooks.txt
Documentation/gitmodules.txt
Documentation/gitrevisions.txt
Documentation/merge-config.txt
Documentation/merge-strategies.txt
Documentation/revisions.txt
Documentation/technical/api-config.txt
Documentation/technical/api-oid-array.txt
Documentation/technical/pack-format.txt
Documentation/technical/protocol-v2.txt
Documentation/technical/shallow.txt
GIT-VERSION-GEN
Makefile
advice.c
advice.h
alias.c
alias.h [new file with mode: 0644]
apply.c
archive-tar.c
archive-zip.c
archive.c
argv-array.c
argv-array.h
attr.c
blame.c
builtin/add.c
builtin/am.c
builtin/blame.c
builtin/branch.c
builtin/cat-file.c
builtin/checkout.c
builtin/clone.c
builtin/column.c
builtin/commit.c
builtin/config.c
builtin/count-objects.c
builtin/describe.c
builtin/diff.c
builtin/difftool.c
builtin/fast-export.c
builtin/fetch.c
builtin/fsck.c
builtin/gc.c
builtin/grep.c
builtin/help.c
builtin/index-pack.c
builtin/init-db.c
builtin/log.c
builtin/ls-files.c
builtin/ls-remote.c
builtin/ls-tree.c
builtin/merge.c
builtin/mktree.c
builtin/mv.c
builtin/notes.c
builtin/pack-objects.c
builtin/pack-redundant.c
builtin/prune-packed.c
builtin/prune.c
builtin/pull.c
builtin/read-tree.c
builtin/rebase--helper.c
builtin/receive-pack.c
builtin/reflog.c
builtin/remote.c
builtin/repack.c
builtin/replace.c
builtin/reset.c
builtin/rev-parse.c
builtin/rm.c
builtin/submodule--helper.c
builtin/tag.c
builtin/unpack-objects.c
builtin/update-index.c
builtin/worktree.c
bulk-checkin.c
bundle.c
cache-tree.c
cache.h
ci/lib-travisci.sh
ci/run-build-and-tests.sh
color.c
column.c
command-list.txt
commit-graph.c
commit-graph.h
commit.c
commit.h
config.c
config.mak.uname
connect.c
contrib/coccinelle/commit.cocci [new file with mode: 0644]
contrib/completion/git-completion.bash
contrib/completion/git-completion.zsh
contrib/convert-grafts-to-replace-refs.sh [deleted file]
contrib/credential/netrc/Makefile
contrib/credential/netrc/git-credential-netrc
contrib/credential/netrc/t-git-credential-netrc.sh [new file with mode: 0755]
contrib/credential/netrc/test.command-option-gpg [new file with mode: 0755]
contrib/credential/netrc/test.git-config-gpg [new file with mode: 0755]
contrib/credential/netrc/test.netrc.gpg [new file with mode: 0644]
contrib/credential/netrc/test.pl
contrib/fast-import/import-tars.perl
date.c
diff.c
diff.h
diffcore-pickaxe.c
dir-iterator.c
dir.c
dir.h
fast-import.c
fetch-pack.c
fetch-pack.h
fsck.c
fsck.h
fsmonitor.c
generate-cmdlist.sh
git-add--interactive.perl
git-compat-util.h
git-filter-branch.sh
git-merge-one-file.sh
git-p4.py
git-rebase--interactive.sh
git-rebase.sh
git-send-email.perl
git-submodule.sh
git.c
grep.c
help.c
help.h
http-push.c
http.c
http.h
imap-send.c
line-log.c
list-objects-filter.c
list-objects.c
lockfile.c
log-tree.c
log-tree.h
mailinfo.c
merge-recursive.c
merge-recursive.h
merge.c
notes-merge.c
object-store.h
object.c
object.h
pack-bitmap-write.c
pack-bitmap.c
pack-bitmap.h
pack-check.c
pack-objects.c
pack-objects.h
packfile.c
packfile.h
pager.c
path.c
pathspec.c
pkt-line.c
pretty.c
prio-queue.c
reachable.c
read-cache.c
ref-filter.c
refs.c
refs/files-backend.c
refs/iterator.c
refs/packed-backend.c
refs/ref-cache.c
remote-curl.c
remote.c
remote.h
replace-object.c
repository.c
rerere.c
resolve-undo.c
revision.c
run-command.c
sequencer.c
sequencer.h
serve.c
server-info.c
setup.c
sha1-array.c
sha1-array.h
sha1-file.c
sha1-lookup.c
sha1-name.c
shallow.c
shell.c
sigchain.c
split-index.c
split-index.h
strbuf.c
strbuf.h
streaming.c
submodule-config.c
submodule-config.h
submodule.c
submodule.h
t/README
t/diff-lib.sh
t/helper/test-drop-caches.c
t/helper/test-dump-split-index.c
t/helper/test-dump-untracked-cache.c
t/helper/test-example-decorate.c
t/helper/test-path-utils.c
t/helper/test-scrap-cache-tree.c
t/helper/test-tool.c
t/helper/test-write-cache.c
t/lib-diff-alternative.sh
t/lib-pack.sh
t/lib-t6000.sh
t/perf/aggregate.perl
t/perf/bisect_run_script
t/t0000-basic.sh
t/t0012-help.sh
t/t0060-path-utils.sh
t/t0090-cache-tree.sh
t/t1000-read-tree-m-3way.sh
t/t1001-read-tree-m-2way.sh
t/t1002-read-tree-m-u-2way.sh
t/t1006-cat-file.sh
t/t1007-hash-object.sh
t/t1012-read-tree-df.sh
t/t1307-config-blob.sh
t/t1400-update-ref.sh
t/t1401-symbolic-ref.sh
t/t1407-worktree-ref-store.sh
t/t1450-fsck.sh
t/t1501-work-tree.sh
t/t1512-rev-parse-disambiguation.sh
t/t1601-index-bogus.sh
t/t1700-split-index.sh
t/t2011-checkout-invalid-head.sh
t/t2025-worktree-add.sh
t/t2027-worktree-list.sh
t/t2107-update-index-basic.sh
t/t2201-add-update-typechange.sh
t/t2203-add-intent.sh
t/t3034-merge-recursive-rename-options.sh
t/t3100-ls-tree-restrict.sh
t/t3101-ls-tree-dirname.sh
t/t3103-ls-tree-misc.sh
t/t3200-branch.sh
t/t3404-rebase-interactive.sh
t/t3418-rebase-continue.sh
t/t3421-rebase-topology-linear.sh
t/t3430-rebase-merges.sh [new file with mode: 0755]
t/t3501-revert-cherry-pick.sh
t/t3510-cherry-pick-sequence.sh
t/t3702-add-edit.sh
t/t3903-stash.sh
t/t3905-stash-include-untracked.sh
t/t4001-diff-rename.sh
t/t4002-diff-basic.sh
t/t4006-diff-mode.sh
t/t4007-rename-3.sh
t/t4008-diff-break-rewrite.sh
t/t4014-format-patch.sh
t/t4020-diff-external.sh
t/t4022-diff-rewrite.sh
t/t4027-diff-submodule.sh
t/t4029-diff-trailing-space.sh
t/t4030-diff-textconv.sh
t/t4042-diff-textconv-caching.sh
t/t4044-diff-index-unique-abbrev.sh
t/t4045-diff-relative.sh
t/t4046-diff-unmerged.sh
t/t4054-diff-bogus-tree.sh
t/t4058-diff-duplicates.sh
t/t4150-am.sh
t/t4200-rerere.sh
t/t4201-shortlog.sh
t/t4205-log-pretty-formats.sh
t/t4208-log-magic-pathspec.sh
t/t5150-request-pull.sh
t/t5300-pack-object.sh
t/t5308-pack-detect-duplicates.sh
t/t5309-pack-delta-cycles.sh
t/t5310-pack-bitmaps.sh
t/t5500-fetch-pack.sh
t/t5510-fetch.sh
t/t5512-ls-remote.sh
t/t5516-fetch-push.sh
t/t5527-fetch-odd-refs.sh
t/t5551-http-fetch-smart.sh
t/t5571-pre-push-hook.sh
t/t5701-git-serve.sh
t/t5702-protocol-v2.sh
t/t6001-rev-list-graft.sh
t/t6006-rev-list-format.sh
t/t6010-merge-base.sh
t/t6012-rev-list-simplify.sh
t/t6022-merge-rename.sh
t/t6043-merge-rename-directories.sh [new file with mode: 0755]
t/t6046-merge-skip-unneeded-updates.sh [new file with mode: 0755]
t/t6050-replace.sh
t/t6101-rev-parse-parents.sh
t/t6111-rev-list-treesame.sh
t/t6120-describe.sh
t/t6300-for-each-ref.sh
t/t6301-for-each-ref-errors.sh
t/t6500-gc.sh
t/t7004-tag.sh
t/t7005-editor.sh
t/t7009-filter-branch-null-sha1.sh
t/t7011-skip-worktree-reading.sh
t/t7064-wtstatus-pv2.sh
t/t7201-co.sh
t/t7400-submodule-basic.sh
t/t7408-submodule-reference.sh
t/t7415-submodule-names.sh [new file with mode: 0755]
t/t7506-status-submodule.sh
t/t7525-status-rename.sh [new file with mode: 0755]
t/t7607-merge-overwrite.sh
t/t7700-repack.sh
t/t8012-blame-colors.sh [new file with mode: 0755]
t/t9010-svn-fe.sh
t/t9104-git-svn-follow-parent.sh
t/t9300-fast-import.sh
t/t9832-unshelve.sh [new file with mode: 0755]
t/t9902-completion.sh
t/t9903-bash-prompt.sh
t/test-lib-functions.sh
t/test-lib.sh
tag.c
templates/hooks--pre-commit.sample
tmp-objdir.c
trailer.c
transport.c
transport.h
tree-walk.c
tree-walk.h
tree.c
unpack-trees.c
unpack-trees.h
upload-pack.c
usage.c
utf8.c
utf8.h
vcs-svn/fast_export.c
walker.c
worktree.c
wrap-for-bin.sh
wrapper.c
wt-status.c
wt-status.h
zlib.c
index 8ce9c6b8888fe6c12949d30e3e8b461cb67bb43f..1bdc91e282c5393c527b3902a208227c19971b84 100644 (file)
@@ -1,8 +1,10 @@
 * whitespace=!indent,trail,space
 *.[ch] whitespace=indent,trail,space diff=cpp
 *.sh whitespace=indent,trail,space eol=lf
-*.perl eol=lf
-*.pm eol=lf
+*.perl eol=lf diff=perl
+*.pl eof=lf diff=perl
+*.pm eol=lf diff=perl
+*.py eol=lf diff=python
 /Documentation/git-*.txt eol=lf
 /command-list.txt eol=lf
 /GIT-VERSION-GEN eol=lf
index b2a1ae4a1d6293004b10d14c33bb64fd9d14fe8b..388cc4beee54faf9f06e9cfae75e7a256d368739 100644 (file)
 /gitweb/gitweb.cgi
 /gitweb/static/gitweb.js
 /gitweb/static/gitweb.min.*
-/common-cmds.h
+/command-list.h
 *.tar.gz
 *.dsc
 *.deb
index 7c71e88ea51c52d453b0d6c08a3415f4c03de22b..df7cf6313c7dd0c5c065e448fd7c725ff537a08b 100644 (file)
--- a/.mailmap
+++ b/.mailmap
@@ -25,8 +25,8 @@ Ben Walton <bdwalton@gmail.com> <bwalton@artsci.utoronto.ca>
 Benoit Sigoure <tsunanet@gmail.com> <tsuna@lrde.epita.fr>
 Bernt Hansen <bernt@norang.ca> <bernt@alumni.uwaterloo.ca>
 Brandon Casey <drafnel@gmail.com> <casey@nrlssc.navy.mil>
-brian m. carlson <sandals@crustytoothpaste.ath.cx> Brian M. Carlson <sandals@crustytoothpaste.ath.cx>
-brian m. carlson <sandals@crustytoothpaste.ath.cx> <sandals@crustytoothpaste.net>
+brian m. carlson <sandals@crustytoothpaste.net> Brian M. Carlson <sandals@crustytoothpaste.ath.cx>
+brian m. carlson <sandals@crustytoothpaste.net> <sandals@crustytoothpaste.ath.cx>
 Bryan Larsen <bryan@larsen.st> <bryan.larsen@gmail.com>
 Bryan Larsen <bryan@larsen.st> <bryanlarsen@yahoo.com>
 Cheng Renquan <crquan@gmail.com>
index 5f5ee4f3bd86f3f7d6cfce4e99453f68f0673eab..4d4e26c9df519c7d708e779401b5076878af022d 100644 (file)
@@ -16,10 +16,13 @@ compiler:
 
 addons:
   apt:
+    sources:
+    - ubuntu-toolchain-r-test
     packages:
     - language-pack-is
     - git-svn
     - apache2
+    - gcc-8
 
 matrix:
   include:
index fa9e5c0acd762d3623a5b436349b8d87e41aeb32..d079d7c73aca1fd91ac18045aab46b2446f43a16 100644 (file)
@@ -185,7 +185,7 @@ ASCIIDOC = asciidoctor
 ASCIIDOC_CONF =
 ASCIIDOC_HTML = xhtml5
 ASCIIDOC_DOCBOOK = docbook45
-ASCIIDOC_EXTRA += -acompat-mode
+ASCIIDOC_EXTRA += -acompat-mode -atabsize=8
 ASCIIDOC_EXTRA += -I. -rasciidoctor-extensions
 ASCIIDOC_EXTRA += -alitdd='&\#x2d;&\#x2d;'
 DBLATEX_COMMON =
diff --git a/Documentation/RelNotes/2.13.7.txt b/Documentation/RelNotes/2.13.7.txt
new file mode 100644 (file)
index 0000000..09fc014
--- /dev/null
@@ -0,0 +1,20 @@
+Git v2.13.7 Release Notes
+=========================
+
+Fixes since v2.13.6
+-------------------
+
+ * Submodule "names" come from the untrusted .gitmodules file, but we
+   blindly append them to $GIT_DIR/modules to create our on-disk repo
+   paths. This means you can do bad things by putting "../" into the
+   name. We now enforce some rules for submodule names which will cause
+   Git to ignore these malicious names (CVE-2018-11235).
+
+   Credit for finding this vulnerability and the proof of concept from
+   which the test script was adapted goes to Etienne Stalmans.
+
+ * It was possible to trick the code that sanity-checks paths on NTFS
+   into reading random piece of memory (CVE-2018-11233).
+
+Credit for fixing for these bugs goes to Jeff King, Johannes
+Schindelin and others.
diff --git a/Documentation/RelNotes/2.14.4.txt b/Documentation/RelNotes/2.14.4.txt
new file mode 100644 (file)
index 0000000..97755a8
--- /dev/null
@@ -0,0 +1,5 @@
+Git v2.14.4 Release Notes
+=========================
+
+This release is to forward-port the fixes made in the v2.13.7 version
+of Git.  See its release notes for details.
index 9f7e28f8a2f2d32a964f3e47bb1d5f4399d4d0b9..b480e56b684de64c68013025bdd0165e2ffbf1dc 100644 (file)
@@ -43,5 +43,8 @@ Fixes since v2.15.1
  * Clarify and enhance documentation for "merge-base --fork-point", as
    it was clear what it computed but not why/what for.
 
+ * This release also contains the fixes made in the v2.13.7 version of
+   Git.  See its release notes for details.
+
 
 Also contains various documentation updates and code clean-ups.
diff --git a/Documentation/RelNotes/2.16.4.txt b/Documentation/RelNotes/2.16.4.txt
new file mode 100644 (file)
index 0000000..6be538b
--- /dev/null
@@ -0,0 +1,5 @@
+Git v2.16.4 Release Notes
+=========================
+
+This release is to forward-port the fixes made in the v2.13.7 version
+of Git.  See its release notes for details.
diff --git a/Documentation/RelNotes/2.17.1.txt b/Documentation/RelNotes/2.17.1.txt
new file mode 100644 (file)
index 0000000..e01384f
--- /dev/null
@@ -0,0 +1,16 @@
+Git v2.17.1 Release Notes
+=========================
+
+Fixes since v2.17
+-----------------
+
+ * This release contains the same fixes made in the v2.13.7 version of
+   Git, covering CVE-2018-11233 and 11235, and forward-ported to
+   v2.14.4, v2.15.2 and v2.16.4 releases.  See release notes to
+   v2.13.7 for details.
+
+ * In addition to the above fixes, this release has support on the
+   server side to reject pushes to repositories that attempt to create
+   such problematic .gitmodules file etc. as tracked contents, to help
+   hosting sites protect their customers by preventing malicious
+   contents from spreading.
index fccc2f34ef06c728a91465d0827fe0da9e4bae50..fd5aecf8e96ec807c6dd4e131704da8f2ee576ea 100644 (file)
@@ -12,7 +12,9 @@ UI, Workflows & Features
    want to move to z/d by taking the hint that the entire directory
    'x' moved to 'z'.  A bug causing dirty files involved in a rename
    to be overwritten during merge has also been fixed as part of this
-   work.
+   work.  Incidentally, this also avoids updating a file in the
+   working tree after a (non-trivial) merge whose result matches what
+   our side originally had.
 
  * "git filter-branch" learned to use a different exit code to allow
    the callers to tell the case where there was no new commits to
@@ -82,6 +84,66 @@ UI, Workflows & Features
    "feature" to fetch only commits and/or trees, which nobody used.
    This has been removed.
 
+ * The functionality of "$GIT_DIR/info/grafts" has been superseded by
+   the "refs/replace/" mechanism for some time now, but the internal
+   code had support for it in many places, which has been cleaned up
+   in order to drop support of the "grafts" mechanism.
+
+ * "git worktree add" learned to check out an existing branch.
+
+ * "git --no-pager cmd" did not have short-and-sweet single letter
+   option. Now it does as "-P".
+   (merge 7213c28818 js/no-pager-shorthand later to maint).
+
+ * "git rebase" learned "--rebase-merges" to transplant the whole
+   topology of commit graph elsewhere.
+
+ * "git status" learned to pay attention to UI related diff
+   configuration variables such as diff.renames.
+
+ * The command line completion mechanism (in contrib/) learned to load
+   custom completion file for "git $command" where $command is a
+   custom "git-$command" that the end user has on the $PATH when using
+   newer version of bash.
+
+ * "git send-email" can sometimes offer confirmation dialog "Send this
+   email?" with choices 'Yes', 'No', 'Quit', and 'All'.  A new action
+   'Edit' has been added to this dialog's choice.
+
+ * With merge.renames configuration set to false, the recursive merge
+   strategy can be told not to spend cycles trying to find renamed
+   paths and merge them accordingly.
+
+ * "git status" learned to honor a new status.renames configuration to
+   skip rename detection, which could be useful for those who want to
+   do so without disabling the default rename detection done by the
+   "git diff" command.
+
+ * Command line completion (in contrib/) learned to complete pathnames
+   for various commands better.
+
+ * "git blame" learns to unhighlight uninteresting metadata from the
+   originating commit on lines that are the same as the previous one,
+   and also paint lines in different colors depending on the age of
+   the commit.
+
+ * Transfer protocol v2 learned to support the partial clone.
+
+ * When a short hexadecimal string is used to name an object but there
+   are multiple objects that share the string as the prefix of their
+   names, the code lists these ambiguous candidates in a help message.
+   These object names are now sorted according to their types for
+   easier eyeballing.
+
+ * "git fetch $there $refspec" that talks over protocol v2 can take
+   advantage of server-side ref filtering; the code has been extended
+   so that this mechanism triggers also when fetching with configured
+   refspec.
+
+ * Our HTTP client code used to advertise that we accept gzip encoding
+   from the other side; instead, just let cURL library to advertise
+   and negotiate the best one.
+
 
 Performance, Internal Implementation, Development Support etc.
 
@@ -179,6 +241,55 @@ Performance, Internal Implementation, Development Support etc.
  * The code to interface to GPG has been restructured somewhat to make
    it cleaner to integrate with other types of signature systems later.
 
+ * The code has been taught to use the duplicated information stored
+   in the commit-graph file to learn the tree object name for a commit
+   to avoid opening and parsing the commit object when it makes sense
+   to do so.
+
+ * "git gc" in a large repository takes a lot of time as it considers
+   to repack all objects into one pack by default.  The command has
+   been taught to pretend as if the largest existing packfile is
+   marked with ".keep" so that it is left untouched while objects in
+   other packs and loose ones are repacked.
+
+ * The transport protocol v2 is getting updated further.
+
+ * The codepath around object-info API has been taught to take the
+   repository object (which in turn tells the API which object store
+   the objects are to be located).
+
+ * "git pack-objects" needs to allocate tons of "struct object_entry"
+   while doing its work, and shrinking its size helps the performance
+   quite a bit.
+
+ * The implementation of "git rebase -i --root" has been updated to use
+   the sequencer machinery more.
+
+ * Developer support update, by using BUG() macro instead of die() to
+   mark codepaths that should not happen more clearly.
+
+ * Developer support.  Use newer GCC on one of the builds done at
+   TravisCI.org to get more warnings and errors diagnosed.
+
+ * Conversion from uchar[20] to struct object_id continues.
+
+ * By code restructuring of submodule merge in merge-recursive,
+   informational messages from the codepath are now given using the
+   same mechanism as other output, and honor the merge.verbosity
+   configuration.  The code also learned to give a few new messages
+   when a submodule three-way merge resolves cleanly when one side
+   records a descendant of the commit chosen by the other side.
+
+ * Avoid unchecked snprintf() to make future code auditing easier.
+   (merge ac4896f007 jk/snprintf-truncation later to maint).
+
+ * Many tests hardcode the raw object names, which would change once
+   we migrate away from SHA-1.  While some of them must test against
+   exact object names, most of them do not have to use hardcoded
+   constants in the test.  The latter kind of tests have been updated
+   to test the moral equivalent of the original without hardcoding the
+   actual object names.
+
 
 Also contains various documentation updates and code clean-ups.
 
@@ -292,6 +403,91 @@ Fixes since v2.17
    some merge commits in certain cases, which has been corrected.
    (merge be011bbe00 ma/fast-export-skip-merge-fix later to maint).
 
+ * The code did not propagate the terminal width to subprocesses via
+   COLUMNS environment variable, which it now does.  This caused
+   trouble to "git column" helper subprocess when "git tag --column=row"
+   tried to list the existing tags on a display with non-default width.
+   (merge b5d5a567fb nd/term-columns later to maint).
+
+ * We learned that our source files with ".pl" and ".py" extensions
+   are Perl and Python files respectively and changes to them are
+   better viewed as such with appropriate diff drivers.
+   (merge 7818b619e2 ab/perl-python-attrs later to maint).
+
+ * "git rebase -i" sometimes left intermediate "# This is a
+   combination of N commits" message meant for the human consumption
+   inside an editor in the final result in certain corner cases, which
+   has been fixed.
+   (merge 15ef69314d js/rebase-i-clean-msg-after-fixup-continue later to maint).
+
+ * A test to see if the filesystem normalizes UTF-8 filename has been
+   updated to check what we need to know in a more direct way, i.e. a
+   path created in NFC form can be accessed with NFD form (or vice
+   versa) to cope with APFS as well as HFS.
+   (merge 742ae10e35 tb/test-apfs-utf8-normalization later to maint).
+
+ * "git format-patch --cover --attach" created a broken MIME multipart
+   message for the cover letter, which has been fixed by keeping the
+   cover letter as plain text file.
+   (merge 50cd54ef4e bc/format-patch-cover-no-attach later to maint).
+
+ * The split-index feature had a long-standing and dormant bug in
+   certain use of the in-core merge machinery, which has been fixed.
+   (merge 7db118303a en/unpack-trees-split-index-fix later to maint).
+
+ * Asciidoctor gives a reasonable imitation for AsciiDoc, but does not
+   render illustration in a literal block correctly when indented with
+   HT by default. The problem is fixed by forcing 8-space tabs.
+   (merge 379805051d bc/asciidoctor-tab-width later to maint).
+
+ * Code clean-up to adjust to a more recent lockfile API convention that
+   allows lockfile instances kept on the stack.
+   (merge 0fa5a2ed8d ma/lockfile-cleanup later to maint).
+
+ * the_repository->index is not a allocated piece of memory but
+   repo_clear() indiscriminately attempted to free(3) it, which has
+   been corrected.
+   (merge 74373b5f10 nd/repo-clear-keep-the-index later to maint).
+
+ * Code clean-up to avoid non-standard-conformant pointer arithmetic.
+   (merge c112084af9 rs/no-null-ptr-arith-in-fast-export later to maint).
+
+ * Code clean-up to turn history traversal more robust in a
+   semi-corrupt repository.
+   (merge 8702b30fd7 jk/unavailable-can-be-missing later to maint).
+
+ * "git update-ref A B" is supposed to ensure that ref A does not yet
+   exist when B is a NULL OID, but this check was not done correctly
+   for pseudo-refs outside refs/ hierarchy, e.g. MERGE_HEAD.
+
+ * "git submodule update" and "git submodule add" supported the
+   "--reference" option to borrow objects from a neighbouring local
+   repository like "git clone" does, but lacked the more recent
+   invention "--dissociate".  Also "git submodule add" has been taught
+   to take the "--progress" option.
+   (merge a0ef29341a cf/submodule-progress-dissociate later to maint).
+
+ * Update credential-netrc helper (in contrib/) to allow customizing
+   the GPG used to decrypt the encrypted .netrc file.
+   (merge 786ef50a23 lm/credential-netrc later to maint).
+
+ * "git submodule update" attempts two different kinds of "git fetch"
+   against the upstream repository to grab a commit bound at the
+   submodule's path, but it incorrectly gave up if the first kind
+   (i.e. a normal fetch) failed, making the second "last resort" one
+   (i.e. fetching an exact commit object by object name) ineffective.
+   This has been corrected.
+   (merge e30d833671 sb/submodule-update-try-harder later to maint).
+
+ * Error behaviour of "git grep" when it cannot read the index was
+   inconsistent with other commands that uses the index, which has
+   been corrected to error out early.
+   (merge b2aa84c789 sb/grep-die-on-unreadable-index later to maint).
+
+ * We used to call regfree() after regcomp() failed in some codepaths,
+   which have been corrected.
+   (merge 17154b1576 ma/regex-no-regfree-after-comp-fail later to maint).
+
  * Other minor doc, test and build updates and code cleanups.
    (merge 248f66ed8e nd/trace-with-env later to maint).
    (merge 14ced5562c ys/bisect-object-id-missing-conversion-fix later to maint).
@@ -310,3 +506,16 @@ Fixes since v2.17
    (merge adc887221f tq/t1510 later to maint).
    (merge bed21a8ad6 sg/doc-gc-quote-mismatch-fix later to maint).
    (merge 73364e4f10 tz/doc-git-urls-reference later to maint).
+   (merge cd1e606bad bc/mailmap-self later to maint).
+   (merge f7997e3682 ao/config-api-doc later to maint).
+   (merge ee930754d8 jk/apply-p-doc later to maint).
+   (merge 011b648646 nd/pack-format-doc later to maint).
+   (merge 87a6bb701a sg/t5310-jgit-bitmap-test later to maint).
+   (merge f6b82970aa sg/t5516-fixes later to maint).
+   (merge 4362da078e sg/t7005-spaces-in-filenames-cleanup later to maint).
+   (merge 7d0ee47c11 js/test-unset-prereq later to maint).
+   (merge 5356a3c354 ah/misc-doc-updates later to maint).
+   (merge 92c4a7a129 nd/completion-aliasfiletype-typofix later to maint).
+   (merge 58bd77b66a nd/pack-unreachable-objects-doc later to maint).
+   (merge 4ed79d5203 sg/t6500-no-redirect-of-stdin later to maint).
+   (merge 17b8a2d6cd jk/config-blob-sans-repo later to maint).
index a05a88fec10eb3c4c36b94889ac6d58b8f441cf7..ab641bf5a9984b7ab2dfea787a0db5704f79a448 100644 (file)
@@ -1068,6 +1068,10 @@ branch.<name>.rebase::
        "git pull" is run. See "pull.rebase" for doing this in a non
        branch-specific manner.
 +
+When `merges`, pass the `--rebase-merges` option to 'git rebase'
+so that the local merge commits are included in the rebase (see
+linkgit:git-rebase[1] for details).
++
 When preserve, also pass `--preserve-merges` along to 'git rebase'
 so that locally committed merge commits will not be flattened
 by running 'git pull'.
@@ -1247,6 +1251,33 @@ color.status.<slot>::
        status short-format), or
        `unmerged` (files which have unmerged changes).
 
+color.blame.repeatedLines::
+       Use the customized color for the part of git-blame output that
+       is repeated meta information per line (such as commit id,
+       author name, date and timezone). Defaults to cyan.
+
+color.blame.highlightRecent::
+       This can be used to color the metadata of a blame line depending
+       on age of the line.
++
+This setting should be set to a comma-separated list of color and date settings,
+starting and ending with a color, the dates should be set from oldest to newest.
+The metadata will be colored given the colors if the the line was introduced
+before the given timestamp, overwriting older timestamped colors.
++
+Instead of an absolute timestamp relative timestamps work as well, e.g.
+2.weeks.ago is valid to address anything older than 2 weeks.
++
+It defaults to 'blue,12 month ago,white,1 month ago,red', which colors
+everything older than one year blue, recent changes between one month and
+one year old are kept white, and lines introduced within the last month are
+colored red.
+
+blame.coloring::
+       This determines the coloring scheme to be applied to blame
+       output. It can be 'repeatedLines', 'highlightRecent',
+       or 'none' which is the default.
+
 color.transport::
        A boolean to enable/disable color when pushes are rejected. May be
        set to `always`, `false` (or `never`) or `auto` (or `true`), in which
@@ -1381,6 +1412,14 @@ credential.<url>.*::
 credentialCache.ignoreSIGHUP::
        Tell git-credential-cache--daemon to ignore SIGHUP, instead of quitting.
 
+completion.commands::
+       This is only used by git-completion.bash to add or remove
+       commands from the list of completed commands. Normally only
+       porcelain commands and a few select others are completed. You
+       can add more commands, separated by space, in this
+       variable. Prefixing the command with '-' will remove it from
+       the existing list.
+
 include::diff-config.txt[]
 
 difftool.<tool>.path::
@@ -1596,6 +1635,18 @@ gc.autoDetach::
        Make `git gc --auto` return immediately and run in background
        if the system supports it. Default is true.
 
+gc.bigPackThreshold::
+       If non-zero, all packs larger than this limit are kept when
+       `git gc` is run. This is very similar to `--keep-base-pack`
+       except that all packs that meet the threshold are kept, not
+       just the base pack. Defaults to zero. Common unit suffixes of
+       'k', 'm', or 'g' are supported.
++
+Note that if the number of kept packs is more than gc.autoPackLimit,
+this configuration variable is ignored, all packs except the base pack
+will be repacked. After this the number of packs should go below
+gc.autoPackLimit and gc.bigPackThreshold should be respected again.
+
 gc.logExpiry::
        If the file gc.log exists, then `git gc --auto` won't run
        unless that file is more than 'gc.logExpiry' old.  Default is
@@ -2460,6 +2511,7 @@ pack.window::
 pack.depth::
        The maximum delta depth used by linkgit:git-pack-objects[1] when no
        maximum depth is given on the command line. Defaults to 50.
+       Maximum value is 4095.
 
 pack.windowMemory::
        The maximum size of memory that is consumed by each thread
@@ -2496,7 +2548,8 @@ pack.deltaCacheLimit::
        The maximum size of a delta, that is cached in
        linkgit:git-pack-objects[1]. This cache is used to speed up the
        writing object phase by not having to recompute the final delta
-       result once the best match for all objects is found. Defaults to 1000.
+       result once the best match for all objects is found.
+       Defaults to 1000. Maximum value is 65535.
 
 pack.threads::
        Specifies the number of threads to spawn when searching for best
@@ -2655,6 +2708,10 @@ pull.rebase::
        pull" is run. See "branch.<name>.rebase" for setting this on a
        per-branch basis.
 +
+When `merges`, pass the `--rebase-merges` option to 'git rebase'
+so that the local merge commits are included in the rebase (see
+linkgit:git-rebase[1] for details).
++
 When preserve, also pass `--preserve-merges` along to 'git rebase'
 so that locally committed merge commits will not be flattened
 by running 'git pull'.
@@ -3157,6 +3214,18 @@ status.displayCommentPrefix::
        behavior of linkgit:git-status[1] in Git 1.8.4 and previous.
        Defaults to false.
 
+status.renameLimit::
+       The number of files to consider when performing rename detection
+       in linkgit:git-status[1] and linkgit:git-commit[1]. Defaults to
+       the value of diff.renameLimit.
+
+status.renames::
+       Whether and how Git detects renames in linkgit:git-status[1] and
+       linkgit:git-commit[1] .  If set to "false", rename detection is
+       disabled. If set to "true", basic rename detection is enabled.
+       If set to "copies" or "copy", Git will detect copies, as well.
+       Defaults to the value of diff.renames.
+
 status.showStash::
        If set to true, linkgit:git-status[1] will display the number of
        entries currently stashed away.
index 5ca942ab5e2d5dfd67ceb06c5991f446b207de90..77caa66c2f768b6e43322c4662f45b4191a163c0 100644 (file)
@@ -112,7 +112,8 @@ diff.orderFile::
 
 diff.renameLimit::
        The number of files to consider when performing the copy/rename
-       detection; equivalent to the 'git diff' option `-l`.
+       detection; equivalent to the 'git diff' option `-l`. This setting
+       has no effect if rename detection is turned off.
 
 diff.renames::
        Whether and how Git detects renames.  If set to "false",
index 8631e365f437fd85058bed3dbd0cebde15756ccc..97d3217df9ac3f048073f62a0d5356c4546354ff 100644 (file)
@@ -188,6 +188,14 @@ endif::git-pull[]
        is specified. This flag forces progress status even if the
        standard error stream is not directed to a terminal.
 
+-o <option>::
+--server-option=<option>::
+       Transmit the given string to the server when communicating using
+       protocol version 2.  The given string must not contain a NUL or LF
+       character.
+       When multiple `--server-option=<option>` are given, they are all
+       sent to the other side in the order listed on the command line.
+
 -4::
 --ipv4::
        Use IPv4 addresses only, ignoring IPv6 addresses.
index d50fa339dcc523158896fda8cdccd1a2784dfdd7..45652fe4a6a51c251f773776576956745701d677 100644 (file)
@@ -193,7 +193,7 @@ for "git add --no-all <pathspec>...", i.e. ignored removed files.
        for command-line options).
 
 
-Configuration
+CONFIGURATION
 -------------
 
 The optional configuration variable `core.excludesFile` indicates a path to a
@@ -226,7 +226,7 @@ Because this example lets the shell expand the asterisk (i.e. you are
 listing the files explicitly), it does not consider
 `subdir/git-foo.sh`.
 
-Interactive mode
+INTERACTIVE MODE
 ----------------
 When the command enters the interactive mode, it shows the
 output of the 'status' subcommand, and then goes into its
index 05fd482b74ed00445613c7cc9b8a0b019ae31550..e44a831339d4a6d7f9ca3d9879dbb5a9b4ffac0a 100644 (file)
@@ -8,7 +8,7 @@ git-annotate - Annotate file lines with commit information
 SYNOPSIS
 --------
 [verse]
-'git annotate' [options] file [revision]
+'git annotate' [<options>] <file> [<revision>]
 
 DESCRIPTION
 -----------
index 4ebc3d32719dfefa988d34b41871f0e9fb969471..67228494c00e1df676723072d0884f7705e532f1 100644 (file)
@@ -113,8 +113,10 @@ explained for the configuration variable `core.quotePath` (see
 linkgit:git-config[1]).
 
 -p<n>::
-       Remove <n> leading slashes from traditional diff paths. The
-       default is 1.
+       Remove <n> leading path components (separated by slashes) from
+       traditional diff paths. E.g., with `-p2`, a patch against
+       `a/dir/file` will be applied directly to `file`. The default is
+       1.
 
 -C<n>::
        Ensure at least <n> lines of surrounding context match before
@@ -240,7 +242,7 @@ When `git apply` is used as a "better GNU patch", the user can pass
 the `--unsafe-paths` option to override this safety check.  This option
 has no effect when `--index` or `--cached` is in use.
 
-Configuration
+CONFIGURATION
 -------------
 
 apply.ignoreWhitespace::
@@ -251,7 +253,7 @@ apply.whitespace::
        When no `--whitespace` flag is given from the command
        line, this configuration item is used as the default.
 
-Submodules
+SUBMODULES
 ----------
 If the patch contains any changes to submodules then 'git apply'
 treats these changes as follows.
index b3084c99c1cabdccc690e4cec5071b22774bf6c8..02eccbb931eb1ffe6e4c018927eebce8eb98f124 100644 (file)
@@ -287,7 +287,7 @@ CONFIGURATION
 `--list` is used or implied. The default is to use a pager.
 See linkgit:git-config[1].
 
-Examples
+EXAMPLES
 --------
 
 Start development from a known tag::
@@ -318,7 +318,7 @@ See linkgit:git-fetch[1].
 is currently checked out) does not have all commits from the test branch.
 
 
-Notes
+NOTES
 -----
 
 If you are creating a branch that you want to checkout immediately, it is
index 3a8120c3b3795784cb05211bcde959a224f0a867..7d6c9dcd177b6a1fa4a6184b230480aa08e07120 100644 (file)
@@ -92,8 +92,8 @@ It is okay to err on the side of caution, causing the bundle file
 to contain objects already in the destination, as these are ignored
 when unpacking at the destination.
 
-EXAMPLE
--------
+EXAMPLES
+--------
 
 Assume you want to transfer the history from a repository R1 on machine A
 to another repository R2 on machine B.
index aa3b2bf2fcf764ca22f44bcfc081afd4e09328bb..3c0578217ba7f92f823e545021c5c06c31566f0d 100644 (file)
@@ -9,8 +9,8 @@ git-check-attr - Display gitattributes information
 SYNOPSIS
 --------
 [verse]
-'git check-attr' [-a | --all | attr...] [--] pathname...
-'git check-attr' --stdin [-z] [-a | --all | attr...]
+'git check-attr' [-a | --all | <attr>...] [--] <pathname>...
+'git check-attr' --stdin [-z] [-a | --all | <attr>...]
 
 DESCRIPTION
 -----------
index 611754f10bb86d62edcb3265cc29ccdc97c259ae..8b42cb3fb20a78cde2eb4c6a7546a334cc71ec4b 100644 (file)
@@ -9,8 +9,8 @@ git-check-ignore - Debug gitignore / exclude files
 SYNOPSIS
 --------
 [verse]
-'git check-ignore' [options] pathname...
-'git check-ignore' [options] --stdin
+'git check-ignore' [<options>] <pathname>...
+'git check-ignore' [<options>] --stdin
 
 DESCRIPTION
 -----------
index 39028ee1a34e63325d68bb900854b4fe8aa66457..aa2055dbebcd4237e7557db7926700c933f8ac94 100644 (file)
@@ -9,7 +9,7 @@ git-check-mailmap - Show canonical names and email addresses of contacts
 SYNOPSIS
 --------
 [verse]
-'git check-mailmap' [options] <contact>...
+'git check-mailmap' [<options>] <contact>...
 
 
 DESCRIPTION
index b844b9957cea30dcca244e863a5f41f7eaf51d8e..a55536f0bfb2dfa3774e1b952ac4dee1eea6e505 100644 (file)
@@ -273,7 +273,7 @@ or `--mirror` is given)
 :git-clone: 1
 include::urls.txt[]
 
-Examples
+EXAMPLES
 --------
 
 * Clone from upstream:
index 2b8582639332acb4d3e81273c6a90163a3a8b378..0216c18ef80c9a9d32095ec3969091aff1c20344 100644 (file)
@@ -8,7 +8,7 @@ git-credential-cache - Helper to temporarily store passwords in memory
 SYNOPSIS
 --------
 -----------------------------
-git config credential.helper 'cache [options]'
+git config credential.helper 'cache [<options>]'
 -----------------------------
 
 DESCRIPTION
index 25fb963f4b06695bb0c5fcf9a1feed97dfc549ed..693dd9d9d760fe2a113d4e15ab7816f34b2cd47d 100644 (file)
@@ -8,7 +8,7 @@ git-credential-store - Helper to store credentials on disk
 SYNOPSIS
 --------
 -------------------
-git config credential.helper 'store [options]'
+git config credential.helper 'store [<options>]'
 -------------------
 
 DESCRIPTION
index ba90066f108cf525eb7781c1607d65358c617b0e..f98b7c6ed7c0fe4df8a0e3211eaa53ea7c95d5d9 100644 (file)
@@ -22,7 +22,7 @@ cvspserver stream tcp nowait nobody /usr/bin/git-cvsserver git-cvsserver pserver
 Usage:
 
 [verse]
-'git-cvsserver' [options] [pserver|server] [<directory> ...]
+'git-cvsserver' [<options>] [pserver|server] [<directory> ...]
 
 OPTIONS
 -------
@@ -207,7 +207,7 @@ allowing access over SSH.
 ------
 
 [[dbbackend]]
-Database Backend
+DATABASE BACKEND
 ----------------
 
 'git-cvsserver' uses one database per Git head (i.e. CVS module) to
@@ -321,7 +321,7 @@ git-cvsserver, as described above.
 When these environment variables are set, the corresponding
 command-line arguments may not be used.
 
-Eclipse CVS Client Notes
+ECLIPSE CVS CLIENT NOTES
 ------------------------
 
 To get a checkout with the Eclipse CVS client:
@@ -346,7 +346,7 @@ offer. In that case CVS_SERVER is ignored, and you will have to replace
 the cvs utility on the server with 'git-cvsserver' or manipulate your `.bashrc`
 so that calling 'cvs' effectively calls 'git-cvsserver'.
 
-Clients known to work
+CLIENTS KNOWN TO WORK
 ---------------------
 
 - CVS 1.12.9 on Debian
@@ -354,7 +354,7 @@ Clients known to work
 - Eclipse 3.0, 3.1.2 on MacOSX (see Eclipse CVS Client Notes)
 - TortoiseCVS
 
-Operations supported
+OPERATIONS SUPPORTED
 --------------------
 
 All the operations required for normal use are supported, including
@@ -424,7 +424,7 @@ For best consistency with 'cvs', it is probably best to override the
 defaults by setting `gitcvs.usecrlfattr` to true,
 and `gitcvs.allBinary` to "guess".
 
-Dependencies
+DEPENDENCIES
 ------------
 'git-cvsserver' depends on DBD::SQLite.
 
index b380677718ae129b4643814d7c1a16351ce4d779..f4bd8155c0a707308162e050d5d59b5dbd5ca7a6 100644 (file)
@@ -37,14 +37,14 @@ include::diff-options.txt[]
 
 include::diff-format.txt[]
 
-Operating Modes
+OPERATING MODES
 ---------------
 You can choose whether you want to trust the index file entirely
 (using the `--cached` flag) or ask the diff logic to show any files
 that don't match the stat state as being "tentatively changed".  Both
 of these operations are very useful indeed.
 
-Cached Mode
+CACHED MODE
 -----------
 If `--cached` is specified, it allows you to ask:
 
@@ -77,7 +77,7 @@ So doing a `git diff-index --cached` is basically very useful when you are
 asking yourself "what have I already marked for being committed, and
 what's the difference to a previous tree".
 
-Non-cached Mode
+NON-CACHED MODE
 ---------------
 The "non-cached" mode takes a different approach, and is potentially
 the more useful of the two in that what it does can't be emulated with
index 7870e175b7683ffcdbdc98d3f669998c615c2ffd..2319b2b19209467bf5796abf8df425c6118dadac 100644 (file)
@@ -116,7 +116,7 @@ include::pretty-options.txt[]
 include::pretty-formats.txt[]
 
 
-Limiting Output
+LIMITING OUTPUT
 ---------------
 If you're only interested in differences in a subset of files, for
 example some architecture-specific files, you might do:
index b0c1bb95c83b8e8e6cf6fa863e74108e3e5fc35f..b180f1fa5bf5599efd8c8fefe29e3249cd387715 100644 (file)
@@ -9,11 +9,11 @@ git-diff - Show changes between commits, commit and working tree, etc
 SYNOPSIS
 --------
 [verse]
-'git diff' [options] [<commit>] [--] [<path>...]
-'git diff' [options] --cached [<commit>] [--] [<path>...]
-'git diff' [options] <commit> <commit> [--] [<path>...]
-'git diff' [options] <blob> <blob>
-'git diff' [options] [--no-index] [--] <path> <path>
+'git diff' [<options>] [<commit>] [--] [<path>...]
+'git diff' [<options>] --cached [<commit>] [--] [<path>...]
+'git diff' [<options>] <commit> <commit> [--] [<path>...]
+'git diff' [<options>] <blob> <blob>
+'git diff' [<options>] --no-index [--] <path> <path>
 
 DESCRIPTION
 -----------
@@ -21,7 +21,7 @@ Show changes between the working tree and the index or a tree, changes
 between the index and a tree, changes between two trees, changes between
 two blob objects, or changes between two files on disk.
 
-'git diff' [--options] [--] [<path>...]::
+'git diff' [<options>] [--] [<path>...]::
 
        This form is to view the changes you made relative to
        the index (staging area for the next commit).  In other
@@ -29,7 +29,7 @@ two blob objects, or changes between two files on disk.
        further add to the index but you still haven't.  You can
        stage these changes by using linkgit:git-add[1].
 
-'git diff' --no-index [--options] [--] [<path>...]::
+'git diff' [<options>] --no-index [--] <path> <path>::
 
        This form is to compare the given two paths on the
        filesystem.  You can omit the `--no-index` option when
@@ -38,7 +38,7 @@ two blob objects, or changes between two files on disk.
        or when running the command outside a working tree
        controlled by Git.
 
-'git diff' [--options] --cached [<commit>] [--] [<path>...]::
+'git diff' [<options>] --cached [<commit>] [--] [<path>...]::
 
        This form is to view the changes you staged for the next
        commit relative to the named <commit>.  Typically you
@@ -48,7 +48,7 @@ two blob objects, or changes between two files on disk.
        <commit> is not given, it shows all staged changes.
        --staged is a synonym of --cached.
 
-'git diff' [--options] <commit> [--] [<path>...]::
+'git diff' [<options>] <commit> [--] [<path>...]::
 
        This form is to view the changes you have in your
        working tree relative to the named <commit>.  You can
@@ -56,18 +56,18 @@ two blob objects, or changes between two files on disk.
        branch name to compare with the tip of a different
        branch.
 
-'git diff' [--options] <commit> <commit> [--] [<path>...]::
+'git diff' [<options>] <commit> <commit> [--] [<path>...]::
 
        This is to view the changes between two arbitrary
        <commit>.
 
-'git diff' [--options] <commit>..<commit> [--] [<path>...]::
+'git diff' [<options>] <commit>..<commit> [--] [<path>...]::
 
        This is synonymous to the previous form.  If <commit> on
        one side is omitted, it will have the same effect as
        using HEAD instead.
 
-'git diff' [--options] <commit>\...<commit> [--] [<path>...]::
+'git diff' [<options>] <commit>\...<commit> [--] [<path>...]::
 
        This form is to view the changes on the branch containing
        and up to the second <commit>, starting at a common ancestor
@@ -87,7 +87,7 @@ and the range notations ("<commit>..<commit>" and
 "<commit>\...<commit>") do not mean a range as defined in the
 "SPECIFYING RANGES" section in linkgit:gitrevisions[7].
 
-'git diff' [options] <blob> <blob>::
+'git diff' [<options>] <blob> <blob>::
 
        This form is to view the differences between the raw
        contents of two blob objects.
index ed57c684dbc82c587ca1cf6b66ae46f3760ecfba..ce954be5321384b55687791acb32562850a5a6fa 100644 (file)
@@ -9,7 +9,7 @@ git-fast-export - Git data exporter
 SYNOPSIS
 --------
 [verse]
-'git fast-export [options]' | 'git fast-import'
+'git fast-export [<options>]' | 'git fast-import'
 
 DESCRIPTION
 -----------
@@ -202,7 +202,7 @@ smaller output, and it is usually easy to quickly confirm that there is
 no private data in the stream.
 
 
-Limitations
+LIMITATIONS
 -----------
 
 Since 'git fast-import' cannot tag trees, you will not be
index 3d3d219e58e5cc05b44c9e0675628bdbd9f0b07f..e81117d27f477591b92480352731fb0c4faf3410 100644 (file)
@@ -9,7 +9,7 @@ git-fast-import - Backend for fast Git data importers
 SYNOPSIS
 --------
 [verse]
-frontend | 'git fast-import' [options]
+frontend | 'git fast-import' [<options>]
 
 DESCRIPTION
 -----------
@@ -139,7 +139,7 @@ Performance and Compression Tuning
 fastimport.unpackLimit::
        See linkgit:git-config[1]
 
-Performance
+PERFORMANCE
 -----------
 The design of fast-import allows it to import large projects in a minimum
 amount of memory usage and processing time.  Assuming the frontend
@@ -155,7 +155,7 @@ faster if the source data is stored on a different drive than the
 destination Git repository (due to less IO contention).
 
 
-Development Cost
+DEVELOPMENT COST
 ----------------
 A typical frontend for fast-import tends to weigh in at approximately 200
 lines of Perl/Python/Ruby code.  Most developers have been able to
@@ -165,7 +165,7 @@ an ideal situation, given that most conversion tools are throw-away
 (use once, and never look back).
 
 
-Parallel Operation
+PARALLEL OPERATION
 ------------------
 Like 'git push' or 'git fetch', imports handled by fast-import are safe to
 run alongside parallel `git repack -a -d` or `git gc` invocations,
@@ -186,7 +186,7 @@ this only be used on an otherwise quiet repository.  Using --force
 is not necessary for an initial import into an empty repository.
 
 
-Technical Discussion
+TECHNICAL DISCUSSION
 --------------------
 fast-import tracks a set of branches in memory.  Any branch can be created
 or modified at any point during the import process by sending a
@@ -204,7 +204,7 @@ directory also allows fast-import to run very quickly, as it does not
 need to perform any costly file update operations when switching
 between branches.
 
-Input Format
+INPUT FORMAT
 ------------
 With the exception of raw file data (which Git does not interpret)
 the fast-import input format is text (ASCII) based.  This text based
@@ -1131,7 +1131,7 @@ If the `--done` command-line option or `feature done` command is
 in use, the `done` command is mandatory and marks the end of the
 stream.
 
-Responses To Commands
+RESPONSES TO COMMANDS
 ---------------------
 New objects written by fast-import are not available immediately.
 Most fast-import commands have no visible effect until the next
@@ -1160,7 +1160,7 @@ To avoid deadlock, such frontends must completely consume any
 pending output from `progress`, `ls`, `get-mark`, and `cat-blob` before
 performing writes to fast-import that might block.
 
-Crash Reports
+CRASH REPORTS
 -------------
 If fast-import is supplied invalid input it will terminate with a
 non-zero exit status and create a crash report in the top level of
@@ -1247,7 +1247,7 @@ An example crash:
        END OF CRASH REPORT
 ====
 
-Tips and Tricks
+TIPS AND TRICKS
 ---------------
 The following tips and tricks have been collected from various
 users of fast-import, and are offered here as suggestions.
@@ -1349,7 +1349,7 @@ Your users will feel better knowing how much of the data stream
 has been processed.
 
 
-Packfile Optimization
+PACKFILE OPTIMIZATION
 ---------------------
 When packing a blob fast-import always attempts to deltify against the last
 blob written.  Unless specifically arranged for by the frontend,
@@ -1380,7 +1380,7 @@ to force recomputation of all deltas can significantly reduce the
 final packfile size (30-50% smaller can be quite typical).
 
 
-Memory Utilization
+MEMORY UTILIZATION
 ------------------
 There are a number of factors which affect how much memory fast-import
 requires to perform an import.  Like critical sections of core
@@ -1458,7 +1458,7 @@ and lazy loading of subtrees, allows fast-import to efficiently import
 projects with 2,000+ branches and 45,114+ files in a very limited
 memory footprint (less than 2.7 MiB per active branch).
 
-Signals
+SIGNALS
 -------
 Sending *SIGUSR1* to the 'git fast-import' process ends the current
 packfile early, simulating a `checkpoint` command.  The impatient
index b634043183b453a89b3f56e0544503b06ccdbdec..e6f08ab189489ec1631169d0ad0b190428883235 100644 (file)
@@ -230,7 +230,7 @@ rewrite, the exit status is `2`.  On any other error, the exit status may be
 any other non-zero value.
 
 
-Examples
+EXAMPLES
 --------
 
 Suppose you want to remove a file (containing confidential information
@@ -288,7 +288,7 @@ git filter-branch --parent-filter \
 or even simpler:
 
 -----------------------------------------------
-echo "$commit-id $graft-id" >> .git/info/grafts
+git replace --graft $commit-id $graft-id
 git filter-branch $graft-id..HEAD
 -----------------------------------------------
 
@@ -406,7 +406,7 @@ git filter-branch --index-filter \
 
 
 
-Checklist for Shrinking a Repository
+CHECKLIST FOR SHRINKING A REPOSITORY
 ------------------------------------
 
 git-filter-branch can be used to get rid of a subset of files,
@@ -445,7 +445,7 @@ warned.
   (or if your git-gc is not new enough to support arguments to
   `--prune`, use `git repack -ad; git prune` instead).
 
-Notes
+NOTES
 -----
 
 git-filter-branch allows you to make complex shell-scripted rewrites
index 44892c447e79f1d06435357b30f4ddbbb465e5f1..423b6e033ba512f8a8ed6fe3f0ee6ef158dd5234 100644 (file)
@@ -57,8 +57,8 @@ merge.summary::
        Synonym to `merge.log`; this is deprecated and will be removed in
        the future.
 
-EXAMPLE
--------
+EXAMPLES
+--------
 
 ---------
 $ git fetch origin master
index 7c8a2edd48fcdf8cd4fd8eca5d833983b6ccfbae..24b2dd44fe445a66121fa957f0af8e2209a85676 100644 (file)
@@ -9,7 +9,7 @@ git-gc - Cleanup unnecessary files and optimize the local repository
 SYNOPSIS
 --------
 [verse]
-'git gc' [--aggressive] [--auto] [--quiet] [--prune=<date> | --no-prune] [--force]
+'git gc' [--aggressive] [--auto] [--quiet] [--prune=<date> | --no-prune] [--force] [--keep-largest-pack]
 
 DESCRIPTION
 -----------
@@ -56,10 +56,16 @@ single pack using `git repack -d -l`.  Setting the value of `gc.auto`
 to 0 disables automatic packing of loose objects.
 +
 If the number of packs exceeds the value of `gc.autoPackLimit`,
-then existing packs (except those marked with a `.keep` file)
+then existing packs (except those marked with a `.keep` file
+or over `gc.bigPackThreshold` limit)
 are consolidated into a single pack by using the `-A` option of
-'git repack'. Setting `gc.autoPackLimit` to 0 disables
-automatic consolidation of packs.
+'git repack'.
+If the amount of memory is estimated not enough for `git repack` to
+run smoothly and `gc.bigPackThreshold` is not set, the largest
+pack will also be excluded (this is the equivalent of running `git gc`
+with `--keep-base-pack`).
+Setting `gc.autoPackLimit` to 0 disables automatic consolidation of
+packs.
 +
 If houskeeping is required due to many loose objects or packs, all
 other housekeeping tasks (e.g. rerere, working trees, reflog...) will
@@ -84,7 +90,12 @@ be performed as well.
        Force `git gc` to run even if there may be another `git gc`
        instance running on this repository.
 
-Configuration
+--keep-largest-pack::
+       All packs except the largest pack and those marked with a
+       `.keep` files are consolidated into a single pack. When this
+       option is used, `gc.bigPackThreshold` is ignored.
+
+CONFIGURATION
 -------------
 
 The optional configuration variable `gc.reflogExpire` can be
@@ -144,7 +155,7 @@ old a stale working tree should be before `git worktree prune` deletes
 it. Default is "3 months ago".
 
 
-Notes
+NOTES
 -----
 
 'git gc' tries very hard not to delete objects that are referenced
index 18b494731f51145e9d4a4d078264bedfd3beb00f..312409a607231d66bd296c1d955bb62c47ecffcf 100644 (file)
@@ -293,7 +293,7 @@ providing this option will cause it to die.
 For more details about the <pathspec> syntax, see the 'pathspec' entry
 in linkgit:gitglossary[7].
 
-Examples
+EXAMPLES
 --------
 
 `git grep 'time_t' -- '*.[ch]'`::
index 40d328a4b3e7e03720cbefc5100ee8e752f6a586..a40fc38d8b425fae0a1ca074ecc8d603efc80f14 100644 (file)
@@ -8,7 +8,7 @@ git-help - Display help information about Git
 SYNOPSIS
 --------
 [verse]
-'git help' [-a|--all] [-g|--guide]
+'git help' [-a|--all [--verbose]] [-g|--guide]
           [-i|--info|-m|--man|-w|--web] [COMMAND|GUIDE]
 
 DESCRIPTION
@@ -42,6 +42,8 @@ OPTIONS
 --all::
        Prints all the available commands on the standard output. This
        option overrides any given command or guide name.
+       When used with `--verbose` print description for all recognized
+       commands.
 
 -g::
 --guides::
index 2aceb6f26da2299d87afe6aeeaf54d18d1487488..ea03a4eeb0fd3124e784553f55c3dccac84a03c7 100644 (file)
@@ -55,7 +55,7 @@ OPTIONS
        The remote refs to update.
 
 
-Specifying the Refs
+SPECIFYING THE REFS
 -------------------
 
 A '<ref>' specification can be either a single pattern, or a pair
index 5d1e4c80cd5d479a43c39ffb12b66a7302e754e7..032613c420cf3cbcf75e82fc89abed6ec8d85355 100644 (file)
@@ -136,8 +136,8 @@ Using direct mode with SSL:
 .........................
 
 
-EXAMPLE
--------
+EXAMPLES
+--------
 To submit patches using GMail's IMAP interface, first, edit your ~/.gitconfig
 to specify your account settings:
 
index 138edb47b6a17ab925ef3206c1aec6336ff380fe..d5b7560bfe2d51370c313ee006a9f0ed4388eda4 100644 (file)
@@ -93,8 +93,8 @@ OPTIONS
 --max-input-size=<size>::
        Die, if the pack is larger than <size>.
 
-Note
-----
+NOTES
+-----
 
 Once the index has been created, the list of object names is sorted
 and the SHA-1 hash of that list is printed to stdout. If --stdin was
index ff446f15f79f270a8979d1a398450a50ba4a4e95..9111c47a1bf2c22466bb74d3b3bca540c4fb0eb2 100644 (file)
@@ -8,8 +8,8 @@ git-interpret-trailers - add or parse structured information in commit messages
 SYNOPSIS
 --------
 [verse]
-'git interpret-trailers' [options] [(--trailer <token>[(=|:)<value>])...] [<file>...]
-'git interpret-trailers' [options] [--parse] [<file>...]
+'git interpret-trailers' [<options>] [(--trailer <token>[(=|:)<value>])...] [<file>...]
+'git interpret-trailers' [<options>] [--parse] [<file>...]
 
 DESCRIPTION
 -----------
index 3ac3e3a77d171d55a2992b263e03350cf4c08b3d..5298f1bc3052f47e390eee780efe665083744309 100644 (file)
@@ -53,7 +53,8 @@ OPTIONS
        Show only ignored files in the output. When showing files in the
        index, print only those matched by an exclude pattern. When
        showing "other" files, show only those matched by an exclude
-       pattern.
+       pattern. Standard ignore rules are not automatically activated,
+       therefore at least one of the `--exclude*` options is required.
 
 -s::
 --stage::
@@ -183,7 +184,7 @@ followed by the  ("attr/<eolattr>").
        Files to show. If no files are given all files which match the other
        specified criteria are shown.
 
-Output
+OUTPUT
 ------
 'git ls-files' just outputs the filenames unless `--stage` is specified in
 which case it outputs:
@@ -208,7 +209,7 @@ quoted as explained for the configuration variable `core.quotePath`
 verbatim and the line is terminated by a NUL byte.
 
 
-Exclude Patterns
+EXCLUDE PATTERNS
 ----------------
 
 'git ls-files' can use a list of "exclude patterns" when
index 6ad1e34afca4cd569798bfcbae9f99d038693b64..b9fd3770a6ce19c341c421e07b68985d89d94df5 100644 (file)
@@ -70,6 +70,14 @@ OPTIONS
        themselves will not work for refs whose objects have not yet been
        fetched from the remote, and will give a `missing object` error.
 
+-o <option>::
+--server-option=<option>::
+       Transmit the given string to the server when communicating using
+       protocol version 2.  The given string must not contain a NUL or LF
+       character.
+       When multiple `--server-option=<option>` are given, they are all
+       sent to the other side in the order listed on the command line.
+
 <repository>::
        The "remote" repository to query.  This parameter can be
        either a URL or the name of a remote (see the GIT URLS and
index e8e68f528cf2fa5705e7cedce8d3d807fa20057d..5cb0eb0855fefe582721baeb3295beb610a74891 100644 (file)
@@ -61,8 +61,8 @@ OPTIONS
 --always::
        Show uniquely abbreviated commit object as fallback.
 
-EXAMPLE
--------
+EXAMPLES
+--------
 
 Given a commit, find out where it is relative to the local refs. Say somebody
 wrote you about that fantastic commit 33db5f4d9027a10e477ccf054b2c1ab94f74c85a.
index d8c8f11c9f2dc94a2f4d46445e33c4f1494ab39e..6646d5e6cc1cc1ebceba98d1c984e6a6fe9b2602 100644 (file)
@@ -29,8 +29,8 @@ Submit Git changes back to p4 using 'git p4 submit'.  The command
 the updated p4 remote branch.
 
 
-EXAMPLE
--------
+EXAMPLES
+--------
 * Clone a repository:
 +
 ------------
@@ -164,6 +164,31 @@ $ git p4 submit --shelve
 $ git p4 submit --update-shelve 1234 --update-shelve 2345
 ----
 
+
+Unshelve
+~~~~~~~~
+Unshelving will take a shelved P4 changelist, and produce the equivalent git commit
+in the branch refs/remotes/p4/unshelved/<changelist>.
+
+The git commit is created relative to the current origin revision (HEAD by default).
+If the shelved changelist's parent revisions differ, git-p4 will refuse to unshelve;
+you need to be unshelving onto an equivalent tree.
+
+The origin revision can be changed with the "--origin" option.
+
+If the target branch in refs/remotes/p4/unshelved already exists, the old one will
+be renamed.
+
+----
+$ git p4 sync
+$ git p4 unshelve 12345
+$ git show refs/remotes/p4/unshelved/12345
+<submit more changes via p4 to the same files>
+$ git p4 unshelve 12345
+<refuses to unshelve until git is in sync with p4 again>
+
+----
+
 OPTIONS
 -------
 
@@ -337,6 +362,13 @@ These options can be used to modify 'git p4 rebase' behavior.
 --import-labels::
        Import p4 labels.
 
+Unshelve options
+~~~~~~~~~~~~~~~~
+
+--origin::
+    Sets the git refspec against which the shelved P4 changelist is compared.
+    Defaults to p4/master.
+
 DEPOT PATH SYNTAX
 -----------------
 The p4 depot path argument to 'git p4 sync' and 'git p4 clone' can
index 81bc490ac52eb9414015979d8c244ce063c838b5..d95b472d16828b2bea304727e7c5daaa2b75ae89 100644 (file)
@@ -12,7 +12,7 @@ SYNOPSIS
 'git pack-objects' [-q | --progress | --all-progress] [--all-progress-implied]
        [--no-reuse-delta] [--delta-base-offset] [--non-empty]
        [--local] [--incremental] [--window=<n>] [--depth=<n>]
-       [--revs [--unpacked | --all]]
+       [--revs [--unpacked | --all]] [--keep-pack=<pack-name>]
        [--stdout [--filter=<filter-spec>] | base-name]
        [--shallow] [--keep-true-parents] < object-list
 
@@ -96,7 +96,9 @@ base-name::
        it too deep affects the performance on the unpacker
        side, because delta data needs to be applied that many
        times to get to the necessary object.
-       The default value for --window is 10 and --depth is 50.
++
+The default value for --window is 10 and --depth is 50. The maximum
+depth is 4095.
 
 --window-memory=<n>::
        This option provides an additional limit on top of `--window`;
@@ -126,6 +128,13 @@ base-name::
        has a .keep file to be ignored, even if it would have
        otherwise been packed.
 
+--keep-pack=<pack-name>::
+       This flag causes an object already in the given pack to be
+       ignored, even if it would have otherwise been
+       packed. `<pack-name>` is the the pack file name without
+       leading directory (e.g. `pack-123.pack`). The option could be
+       specified multiple times to keep multiple packs.
+
 --incremental::
        This flag causes an object already in a pack to be ignored
        even if it would have otherwise been packed.
@@ -267,6 +276,19 @@ Unexpected missing object will raise an error.
        locally created objects [without .promisor] and objects from the
        promisor remote [with .promisor].)  This is used with partial clone.
 
+--keep-unreachable::
+       Objects unreachable from the refs in packs named with
+       --unpacked= option are added to the resulting pack, in
+       addition to the reachable objects that are not in packs marked
+       with *.keep files. This implies `--revs`.
+
+--pack-loose-unreachable::
+       Pack unreachable loose objects (and their loose counterparts
+       removed). This implies `--revs`.
+
+--unpack-unreachable::
+       Keep unreachable objects in loose form. This implies `--revs`.
+
 SEE ALSO
 --------
 linkgit:git-rev-list[1]
index a37c0af9313e8e47806c89a584c70c09517244d1..03552dd86fc412b622aff2bcf8feda8e71711b3e 100644 (file)
@@ -56,8 +56,8 @@ OPTIONS
        reachable from any of our references, keep objects
        reachable from listed <head>s.
 
-EXAMPLE
--------
+EXAMPLES
+--------
 
 To prune objects not used by your repository or another that
 borrows from your repository via its
@@ -67,7 +67,7 @@ borrows from your repository via its
 $ git prune $(cd ../another && git rev-parse --all)
 ------------
 
-Notes
+NOTES
 -----
 
 In most cases, users will not need to call 'git prune' directly, but
index ce05b7a5b13eadb6870f16a7168559434d376c20..118d9d86f79427373eb92753abf7e134ed925fe8 100644 (file)
@@ -9,7 +9,7 @@ git-pull - Fetch from and integrate with another repository or a local branch
 SYNOPSIS
 --------
 [verse]
-'git pull' [options] [<repository> [<refspec>...]]
+'git pull' [<options>] [<repository> [<refspec>...]]
 
 
 DESCRIPTION
@@ -101,13 +101,17 @@ Options related to merging
 include::merge-options.txt[]
 
 -r::
---rebase[=false|true|preserve|interactive]::
+--rebase[=false|true|merges|preserve|interactive]::
        When true, rebase the current branch on top of the upstream
        branch after fetching. If there is a remote-tracking branch
        corresponding to the upstream branch and the upstream branch
        was rebased since last fetched, the rebase uses that information
        to avoid rebasing non-local changes.
 +
+When set to `merges`, rebase using `git rebase --rebase-merges` so that
+the local merge commits are included in the rebase (see
+linkgit:git-rebase[1] for details).
++
 When set to preserve, rebase with the `--preserve-merges` option passed
 to `git rebase` so that locally created merge commits will not be flattened.
 +
index 34410f9fcad6eaac3a4ef036ed88c63596928b41..55277a97811fa6933c76c1bd2c96114672f6d5cd 100644 (file)
@@ -11,7 +11,7 @@ SYNOPSIS
 [verse]
 'git push' [--all | --mirror | --tags] [--follow-tags] [--atomic] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
           [--repo=<repository>] [-f | --force] [-d | --delete] [--prune] [-v | --verbose]
-          [-u | --set-upstream] [--push-option=<string>]
+          [-u | --set-upstream] [-o <string> | --push-option=<string>]
           [--[no-]signed|--signed=(true|false|if-asked)]
           [--force-with-lease[=<refname>[:<expect>]]]
           [--no-verify] [<repository> [<refspec>...]]
@@ -123,6 +123,7 @@ already exists on the remote side.
        will be tab-separated and sent to stdout instead of stderr.  The full
        symbolic names of the refs will be given.
 
+-d::
 --delete::
        All listed refs are deleted from the remote repository. This is
        the same as prefixing all refs with a colon.
@@ -423,7 +424,7 @@ reason::
        refs, no explanation is needed. For a failed ref, the reason for
        failure is described.
 
-Note about fast-forwards
+NOTE ABOUT FAST-FORWARDS
 ------------------------
 
 When an update changes a branch (or more in general, a ref) that used to
@@ -510,7 +511,7 @@ overwrite it. In other words, "git push --force" is a method reserved for
 a case where you do mean to lose history.
 
 
-Examples
+EXAMPLES
 --------
 
 `git push`::
index f2a07d54d694b42657aa38e71dc0eeb5fc94e9a0..5c70bc2878fc2f68698b931c6bf951d0097ef813 100644 (file)
@@ -132,7 +132,7 @@ OPTIONS
        The id of the tree object(s) to be read/merged.
 
 
-Merging
+MERGING
 -------
 If `-m` is specified, 'git read-tree' can perform 3 kinds of
 merge, a single tree merge if only 1 tree is given, a
@@ -382,7 +382,7 @@ middle of doing, and when your working tree is ready (i.e. you
 have finished your work-in-progress), attempt the merge again.
 
 
-Sparse checkout
+SPARSE CHECKOUT
 ---------------
 
 "Sparse checkout" allows populating the working directory sparsely.
index dd852068b1d06f8f40566da65579564144fef718..0e20a66e73cf420c83b156d1a0aaa8f60cd4702f 100644 (file)
@@ -8,9 +8,9 @@ git-rebase - Reapply commits on top of another base tip
 SYNOPSIS
 --------
 [verse]
-'git rebase' [-i | --interactive] [options] [--exec <cmd>] [--onto <newbase>]
+'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]
        [<upstream> [<branch>]]
-'git rebase' [-i | --interactive] [options] [--exec <cmd>] [--onto <newbase>]
+'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]
        --root [<branch>]
 'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch
 
@@ -379,6 +379,33 @@ 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.
 
+-r::
+--rebase-merges[=(rebase-cousins|no-rebase-cousins)]::
+       By default, a rebase will simply drop merge commits from the todo
+       list, and put the rebased commits into a single, linear branch.
+       With `--rebase-merges`, the rebase will instead try to preserve
+       the branching structure within the commits that are to be rebased,
+       by recreating the merge commits. Any resolved merge conflicts or
+       manual amendments in these merge commits will have to be
+       resolved/re-applied manually.
++
+By default, or when `no-rebase-cousins` was specified, commits which do not
+have `<upstream>` as direct ancestor will keep their original branch point,
+i.e. commits that would be excluded by gitlink:git-log[1]'s
+`--ancestry-path` option will keep their original ancestry by default. If
+the `rebase-cousins` mode is turned on, such commits are instead rebased
+onto `<upstream>` (or `<onto>`, if specified).
++
+The `--rebase-merges` mode is similar in spirit to `--preserve-merges`, but
+in contrast to that option works well in interactive rebases: commits can be
+reordered, inserted and dropped at will.
++
+It is currently only possible to recreate the merge commits using the
+`recursive` merge strategy; Different merge strategies can be used only via
+explicit `exec git merge -s <strategy> [...]` commands.
++
+See also REBASING MERGES below.
+
 -p::
 --preserve-merges::
        Recreate merge commits instead of flattening the history by replaying
@@ -776,12 +803,146 @@ The ripple effect of a "hard case" recovery is especially bad:
 'everyone' downstream from 'topic' will now have to perform a "hard
 case" recovery too!
 
+REBASING MERGES
+-----------------
+
+The interactive rebase command was originally designed to handle
+individual patch series. As such, it makes sense to exclude merge
+commits from the todo list, as the developer may have merged the
+then-current `master` while working on the branch, only to rebase
+all the commits onto `master` eventually (skipping the merge
+commits).
+
+However, there are legitimate reasons why a developer may want to
+recreate merge commits: to keep the branch structure (or "commit
+topology") when working on multiple, inter-related branches.
+
+In the following example, the developer works on a topic branch that
+refactors the way buttons are defined, and on another topic branch
+that uses that refactoring to implement a "Report a bug" button. The
+output of `git log --graph --format=%s -5` may look like this:
+
+------------
+*   Merge branch 'report-a-bug'
+|\
+| * Add the feedback button
+* | Merge branch 'refactor-button'
+|\ \
+| |/
+| * Use the Button class for all buttons
+| * Extract a generic Button class from the DownloadButton one
+------------
+
+The developer might want to rebase those commits to a newer `master`
+while keeping the branch topology, for example when the first topic
+branch is expected to be integrated into `master` much earlier than the
+second one, say, to resolve merge conflicts with changes to the
+DownloadButton class that made it into `master`.
+
+This rebase can be performed using the `--rebase-merges` option.
+It will generate a todo list looking like this:
+
+------------
+label onto
+
+# Branch: refactor-button
+reset onto
+pick 123456 Extract a generic Button class from the DownloadButton one
+pick 654321 Use the Button class for all buttons
+label refactor-button
+
+# Branch: report-a-bug
+reset refactor-button # Use the Button class for all buttons
+pick abcdef Add the feedback button
+label report-a-bug
+
+reset onto
+merge -C a1b2c3 refactor-button # Merge 'refactor-button'
+merge -C 6f5e4d report-a-bug # Merge 'report-a-bug'
+------------
+
+In contrast to a regular interactive rebase, there are `label`, `reset`
+and `merge` commands in addition to `pick` ones.
+
+The `label` command associates a label with the current HEAD when that
+command is executed. These labels are created as worktree-local refs
+(`refs/rewritten/<label>`) that will be deleted when the rebase
+finishes. That way, rebase operations in multiple worktrees linked to
+the same repository do not interfere with one another. If the `label`
+command fails, it is rescheduled immediately, with a helpful message how
+to proceed.
+
+The `reset` command resets the HEAD, index and worktree to the specified
+revision. It is isimilar to an `exec git reset --hard <label>`, but
+refuses to overwrite untracked files. If the `reset` command fails, it is
+rescheduled immediately, with a helpful message how to edit the todo list
+(this typically happens when a `reset` command was inserted into the todo
+list manually and contains a typo).
+
+The `merge` command will merge the specified revision into whatever is
+HEAD at that time. With `-C <original-commit>`, the commit message of
+the specified merge commit will be used. When the `-C` is changed to
+a lower-case `-c`, the message will be opened in an editor after a
+successful merge so that the user can edit the message.
+
+If a `merge` command fails for any reason other than merge conflicts (i.e.
+when the merge operation did not even start), it is rescheduled immediately.
+
+At this time, the `merge` command will *always* use the `recursive`
+merge strategy, with no way to choose a different one. To work around
+this, an `exec` command can be used to call `git merge` explicitly,
+using the fact that the labels are worktree-local refs (the ref
+`refs/rewritten/onto` would correspond to the label `onto`, for example).
+
+Note: the first command (`label onto`) labels the revision onto which
+the commits are rebased; The name `onto` is just a convention, as a nod
+to the `--onto` option.
+
+It is also possible to introduce completely new merge commits from scratch
+by adding a command of the form `merge <merge-head>`. This form will
+generate a tentative commit message and always open an editor to let the
+user edit it. This can be useful e.g. when a topic branch turns out to
+address more than a single concern and wants to be split into two or
+even more topic branches. Consider this todo list:
+
+------------
+pick 192837 Switch from GNU Makefiles to CMake
+pick 5a6c7e Document the switch to CMake
+pick 918273 Fix detection of OpenSSL in CMake
+pick afbecd http: add support for TLS v1.3
+pick fdbaec Fix detection of cURL in CMake on Windows
+------------
+
+The one commit in this list that is not related to CMake may very well
+have been motivated by working on fixing all those bugs introduced by
+switching to CMake, but it addresses a different concern. To split this
+branch into two topic branches, the todo list could be edited like this:
+
+------------
+label onto
+
+pick afbecd http: add support for TLS v1.3
+label tlsv1.3
+
+reset onto
+pick 192837 Switch from GNU Makefiles to CMake
+pick 918273 Fix detection of OpenSSL in CMake
+pick fdbaec Fix detection of cURL in CMake on Windows
+pick 5a6c7e Document the switch to CMake
+label cmake
+
+reset onto
+merge tlsv1.3
+merge cmake
+------------
+
 BUGS
 ----
 The todo list presented by `--preserve-merges --interactive` does not
 represent the topology of the revision graph.  Editing commits and
 rewording their commit messages should work fine, but attempts to
-reorder commits tend to produce counterintuitive results.
+reorder commits tend to produce counterintuitive results. Use
+`--rebase-merges` in such scenarios instead.
 
 For example, an attempt to rearrange
 ------------
index 86a4b32f0f1cbb8b69ff8b935edade6f14e7d5e2..dedf97efbb2282a15abfee41926afd893e59955c 100644 (file)
@@ -41,7 +41,7 @@ OPTIONS
 <directory>::
        The repository to sync into.
 
-pre-receive Hook
+PRE-RECEIVE HOOK
 ----------------
 Before any ref is updated, if $GIT_DIR/hooks/pre-receive file exists
 and is executable, it will be invoked once with no parameters.  The
@@ -116,7 +116,7 @@ bail out if the update is not to be supported.
 
 See the notes on the quarantine environment below.
 
-update Hook
+UPDATE HOOK
 -----------
 Before each ref is updated, if $GIT_DIR/hooks/update file exists
 and is executable, it is invoked once per ref, with three parameters:
@@ -138,7 +138,7 @@ ensure the ref will actually be updated, it is only a prerequisite.
 As such it is not a good idea to send notices (e.g. email) from
 this hook.  Consider using the post-receive hook instead.
 
-post-receive Hook
+POST-RECEIVE HOOK
 -----------------
 After all refs were updated (or attempted to be updated), if any
 ref update was successful, and if $GIT_DIR/hooks/post-receive
@@ -198,7 +198,7 @@ after it was updated by 'git-receive-pack', but before the hook was able
 to evaluate it.  It is recommended that hooks rely on sha1-new
 rather than the current value of refname.
 
-post-update Hook
+POST-UPDATE HOOK
 ----------------
 After all other processing, if at least one ref was updated, and
 if $GIT_DIR/hooks/post-update file exists and is executable, then
@@ -216,7 +216,7 @@ if the repository is packed and is served via a dumb transport.
        exec git update-server-info
 
 
-Quarantine Environment
+QUARANTINE ENVIRONMENT
 ----------------------
 
 When `receive-pack` takes in objects, they are placed into a temporary
index b25d0b5996b560837648c13c742bfc5ad09f0310..3fc5d94336f7c706bab12513e14744947a0bcae2 100644 (file)
@@ -55,14 +55,14 @@ some tunnel.
        the vhost field in the git:// service request (to rest of the argument).
        Default is not to send vhost in such request (if sent).
 
-ENVIRONMENT VARIABLES:
-----------------------
+ENVIRONMENT VARIABLES
+---------------------
 
 GIT_TRANSLOOP_DEBUG::
        If set, prints debugging information about various reads/writes.
 
-ENVIRONMENT VARIABLES PASSED TO COMMAND:
-----------------------------------------
+ENVIRONMENT VARIABLES PASSED TO COMMAND
+---------------------------------------
 
 GIT_EXT_SERVICE::
        Set to long name (git-upload-pack, etc...) of service helper needs
@@ -73,8 +73,8 @@ GIT_EXT_SERVICE_NOPREFIX::
        to invoke.
 
 
-EXAMPLES:
----------
+EXAMPLES
+--------
 This remote helper is transparently used by Git when
 you use commands such as "git fetch <URL>", "git clone <URL>",
 , "git push <URL>" or "git remote add <nick> <URL>", where <URL>
index 4feddc0293bd7eb9827ce45f11519335a484f013..595948da53093459635fd3dd8c79fe756020eea8 100644 (file)
@@ -203,7 +203,7 @@ The remote configuration is achieved using the `remote.origin.url` and
 `remote.origin.fetch` configuration variables.  (See
 linkgit:git-config[1]).
 
-Examples
+EXAMPLES
 --------
 
 * Add a new remote, fetch, and check out a branch from it
index ae750e9e1149f512dd5889a8081452055ccdb6d7..d90e7907f4843a048caf11a2fae42a973d893d9a 100644 (file)
@@ -9,7 +9,7 @@ git-repack - Pack unpacked objects in a repository
 SYNOPSIS
 --------
 [verse]
-'git repack' [-a] [-A] [-d] [-f] [-F] [-l] [-n] [-q] [-b] [--window=<n>] [--depth=<n>] [--threads=<n>]
+'git repack' [-a] [-A] [-d] [-f] [-F] [-l] [-n] [-q] [-b] [--window=<n>] [--depth=<n>] [--threads=<n>] [--keep-pack=<pack-name>]
 
 DESCRIPTION
 -----------
@@ -90,7 +90,9 @@ other objects in that pack they already have locally.
        space. `--depth` limits the maximum delta depth; making it too deep
        affects the performance on the unpacker side, because delta data needs
        to be applied that many times to get to the necessary object.
-       The default value for --window is 10 and --depth is 50.
++
+The default value for --window is 10 and --depth is 50. The maximum
+depth is 4095.
 
 --threads=<n>::
        This option is passed through to `git pack-objects`.
@@ -133,6 +135,13 @@ other objects in that pack they already have locally.
        with `-b` or `repack.writeBitmaps`, as it ensures that the
        bitmapped packfile has the necessary objects.
 
+--keep-pack=<pack-name>::
+       Exclude the given pack from repacking. This is the equivalent
+       of having `.keep` file on the pack. `<pack-name>` is the the
+       pack file name without leading directory (e.g. `pack-123.pack`).
+       The option could be specified multiple times to keep multiple
+       packs.
+
 --unpack-unreachable=<when>::
        When loosening unreachable objects, do not bother loosening any
        objects older than `<when>`. This can be used to optimize out
index e5c57ae6ef4afd71944e23f895e9a0f354eaf6b1..246dc9943c223d34c72d7b90f40ee8f523b7d77e 100644 (file)
@@ -11,6 +11,7 @@ SYNOPSIS
 'git replace' [-f] <object> <replacement>
 'git replace' [-f] --edit <object>
 'git replace' [-f] --graft <commit> [<parent>...]
+'git replace' [-f] --convert-graft-file
 'git replace' -d <object>...
 'git replace' [--format=<format>] [-l [<pattern>]]
 
@@ -87,9 +88,13 @@ OPTIONS
        content as <commit> except that its parents will be
        [<parent>...] instead of <commit>'s parents. A replacement ref
        is then created to replace <commit> with the newly created
-       commit. See contrib/convert-grafts-to-replace-refs.sh for an
-       example script based on this option that can convert grafts to
-       replace refs.
+       commit. Use `--convert-graft-file` to convert a
+       `$GIT_DIR/info/grafts` file and use replace refs instead.
+
+--convert-graft-file::
+       Creates graft commits for all entries in `$GIT_DIR/info/grafts`
+       and deletes that file upon success. The purpose is to help users
+       with transitioning off of the now-deprecated graft file.
 
 -l <pattern>::
 --list <pattern>::
index c32cb0bea1d6c057dabdd3065417744f5209ba46..4d4392d0f841b7e447b536ef1281fcbe2e49786d 100644 (file)
@@ -46,8 +46,8 @@ ref that is different from the ref you have locally, you can use the
 its remote name.
 
 
-EXAMPLE
--------
+EXAMPLES
+--------
 
 Imagine that you built your work on your `master` branch on top of
 the `v1.0` release, and want it to be integrated to the project.
index 95326b85ff68edbda3a6661816251351029bdfda..e72d332b8316763d40e7bcfe0e32bcaa61842cf8 100644 (file)
@@ -9,7 +9,7 @@ git-rev-parse - Pick out and massage parameters
 SYNOPSIS
 --------
 [verse]
-'git rev-parse' [ --option ] <args>...
+'git rev-parse' [<options>] <args>...
 
 DESCRIPTION
 -----------
@@ -360,7 +360,7 @@ Example
 
 ------------
 OPTS_SPEC="\
-some-command [options] <args>...
+some-command [<options>] <args>...
 
 some-command does foo and bar!
 --
@@ -385,7 +385,7 @@ When `"$@"` is `-h` or `--help` in the above example, the following
 usage text would be shown:
 
 ------------
-usage: some-command [options] <args>...
+usage: some-command [<options>] <args>...
 
     some-command does foo and bar!
 
index 60cf96f4c1405e956f6cbbe9ca35959710a46d70..4f3efde80cd8e4f0b1a5b6699ad3e569f19ca516 100644 (file)
@@ -9,7 +9,7 @@ git-send-email - Send a collection of patches as emails
 SYNOPSIS
 --------
 [verse]
-'git send-email' [options] <file|directory|rev-list options>...
+'git send-email' [<options>] <file|directory|rev-list options>...
 'git send-email' --dump-aliases
 
 
@@ -458,8 +458,8 @@ sendemail.confirm::
        one of 'always', 'never', 'cc', 'compose', or 'auto'. See `--confirm`
        in the previous section for the meaning of these values.
 
-EXAMPLE
--------
+EXAMPLES
+--------
 Use gmail as the smtp server
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 To use 'git send-email' to send your patches through the GMail SMTP server,
index f51c64939b48b7b082752a294b17aee6d92c4fa0..44fd146b9120305112c59b800c9e989bbc60f450 100644 (file)
@@ -99,7 +99,7 @@ be in a separate packet, and the list must end with a flush packet.
        The remote refs to update.
 
 
-Specifying the Refs
+SPECIFYING THE REFS
 -------------------
 
 There are three ways to specify which refs to update on the
index 54cf2560bebbfc538644c636f696d4e500f1d852..11361f33e93937429a8d1791efaca5e00888f236 100644 (file)
@@ -62,8 +62,8 @@ permissions.
 If a `no-interactive-login` command exists, then it is run and the
 interactive shell is aborted.
 
-EXAMPLE
--------
+EXAMPLES
+--------
 
 To disable interactive logins, displaying a greeting instead:
 
index 7818e0f09853f9c2a2ad0923d1fc13b698cad065..262db049d772d4249983da5ca0d21d8f6b0f8d92 100644 (file)
@@ -173,8 +173,8 @@ The "fixes" branch adds one commit "Introduce "reset type" flag to
 The current branch is "master".
 
 
-EXAMPLE
--------
+EXAMPLES
+--------
 
 If you keep your primary branches immediately under
 `refs/heads`, and topic branches in subdirectories of
index c0aa871c9e8b06ea297e0a723167767338081453..d28e6154c6863d2d87c52251669e305caf7f995a 100644 (file)
@@ -120,8 +120,8 @@ $ git show-ref --heads --hash
 ...
 -----------------------------------------------------------------------------
 
-EXAMPLE
--------
+EXAMPLES
+--------
 
 To show all references called "master", whether tags or heads or anything
 else, and regardless of how deep in the reference naming hierarchy they are,
index e73ef540176b65f6c049a3b5fcfd7e8faa6a6b58..fcf528c1b30d8045592d117ccc3d5f9248d0edbd 100644 (file)
@@ -9,7 +9,7 @@ git-show - Show various types of objects
 SYNOPSIS
 --------
 [verse]
-'git show' [options] [<object>...]
+'git show' [<options>] [<object>...]
 
 DESCRIPTION
 -----------
@@ -77,7 +77,7 @@ EXAMPLES
        Concatenates the contents of said Makefiles in the head
        of the branch `master`.
 
-Discussion
+DISCUSSION
 ----------
 
 include::i18n.txt[]
index c16e27e63d4cf6522d29c9d0ff099e06cfcd38a4..c4467ffb98a95fa6bb5dc86a92dee3317be9c0ea 100644 (file)
@@ -135,6 +135,16 @@ ignored, then the directory is not shown, but all contents are shown.
        Display or do not display detailed ahead/behind counts for the
        branch relative to its upstream branch.  Defaults to true.
 
+--renames::
+--no-renames::
+       Turn on/off rename detection regardless of user configuration.
+       See also linkgit:git-diff[1] `--no-renames`.
+
+--find-renames[=<n>]::
+       Turn on rename detection, optionally setting the similarity
+       threshold.
+       See also linkgit:git-diff[1] `--find-renames`.
+
 <pathspec>...::
        See the 'pathspec' entry in linkgit:gitglossary[7].
 
index 630999f41a902d8d2043e17f797666489792fd57..4a5cc38a6f0e10ba5232190942572e4a540e4ecb 100644 (file)
@@ -239,6 +239,13 @@ OPTIONS
 --quiet::
        Only print error messages.
 
+--progress::
+       This option is only valid for add and update commands.
+       Progress status is reported on the standard error stream
+       by default when it is attached to a terminal, unless -q
+       is specified. This flag forces progress status even if the
+       standard error stream is not directed to a terminal.
+
 --all::
        This option is only valid for the deinit command. Unregister all
        submodules in the working tree.
@@ -362,7 +369,15 @@ the submodule itself.
        this option will be passed to the linkgit:git-clone[1] command.
 +
 *NOTE*: Do *not* use this option unless you have read the note
-for linkgit:git-clone[1]'s `--reference` and `--shared` options carefully.
+for linkgit:git-clone[1]'s `--reference`, `--shared`, and `--dissociate`
+options carefully.
+
+--dissociate::
+       This option is only valid for add and update commands.  These
+       commands sometimes need to clone a remote repository. In this case,
+       this option will be passed to the linkgit:git-clone[1] command.
++
+*NOTE*: see the NOTE for the `--reference` option.
 
 --recursive::
        This option is only valid for foreach, update, status and sync commands.
index d59379ee2344690926f5e0bee282c81445d3061d..7ea24fc942c18c032048edc10dfe00b56bff991e 100644 (file)
@@ -8,7 +8,7 @@ git-svn - Bidirectional operation between a Subversion repository and Git
 SYNOPSIS
 --------
 [verse]
-'git svn' <command> [options] [arguments]
+'git svn' <command> [<options>] [<arguments>]
 
 DESCRIPTION
 -----------
@@ -707,7 +707,7 @@ creating the branch or tag.
 config key: svn.useLogAuthor
 
 --add-author-from::
-       When committing to svn from Git (as part of 'commit-diff', 'set-tree' or 'dcommit'
+       When committing to svn from Git (as part of 'set-tree' or 'dcommit'
        operations), if the existing log message doesn't already have a
        `From:` or `Signed-off-by:` line, append a `From:` line based on the
        Git commit's author string.  If you use this, then `--use-log-author`
index 1d17101bac39d64cc3d4aff89cd588955dd00735..87c4288ffc47ddd73162c85a27fe22c161b4a441 100644 (file)
@@ -34,8 +34,8 @@ in the tag message.
 If `-m <msg>` or `-F <file>` is given and `-a`, `-s`, and `-u <keyid>`
 are absent, `-a` is implied.
 
-Otherwise just a tag reference for the SHA-1 object name of the commit object is
-created (i.e. a lightweight tag).
+Otherwise, a tag reference that points directly at the given object
+(i.e., a lightweight tag) is created.
 
 A GnuPG signed tag object will be created when `-s` or `-u
 <keyid>` is used.  When `-u <keyid>` is not used, the
index 3897a59ee94bc424c2c66cb5d05bc0193fb6eaf2..4e8e762e68690cab880460fbe1fef0077c8351e2 100644 (file)
@@ -228,7 +228,7 @@ will remove the intended effect of the option.
        cleaner names.
        The same applies to directories ending '/' and paths with '//'
 
-Using --refresh
+USING --REFRESH
 ---------------
 `--refresh` does not calculate a new sha1 file or bring the index
 up to date for mode/content changes. But what it *does* do is to
@@ -239,7 +239,7 @@ the stat entry is out of date.
 For example, you'd want to do this after doing a 'git read-tree', to link
 up the stat index details with the proper files.
 
-Using --cacheinfo or --info-only
+USING --CACHEINFO OR --INFO-ONLY
 --------------------------------
 `--cacheinfo` is used to register a file that is not in the
 current working directory.  This is useful for minimum-checkout
@@ -261,7 +261,7 @@ useful when the file is available, but you do not wish to update the
 object database.
 
 
-Using --index-info
+USING --INDEX-INFO
 ------------------
 
 `--index-info` is a more powerful mechanism that lets you feed
@@ -317,7 +317,7 @@ $ git ls-files -s
 ------------
 
 
-Using ``assume unchanged'' bit
+USING ``ASSUME UNCHANGED'' BIT
 ------------------------------
 
 Many operations in Git depend on your filesystem to have an
@@ -350,7 +350,7 @@ the index (use `git update-index --really-refresh` if you want
 to mark them as "assume unchanged").
 
 
-Examples
+EXAMPLES
 --------
 To update and refresh only the files already checked out:
 
@@ -387,7 +387,7 @@ M foo.c
 <9> now it checks with lstat(2) and finds it has been changed.
 
 
-Skip-worktree bit
+SKIP-WORKTREE BIT
 -----------------
 
 Skip-worktree bit can be defined in one (long) sentence: When reading
@@ -407,7 +407,7 @@ Although this bit looks similar to assume-unchanged bit, its goal is
 different from assume-unchanged bit's. Skip-worktree also takes
 precedence over assume-unchanged bit when both are set.
 
-Split index
+SPLIT INDEX
 -----------
 
 This mode is designed for repositories with very large indexes, and
@@ -432,7 +432,7 @@ To avoid deleting a shared index file that is still used, its
 modification time is updated to the current time everytime a new split
 index based on the shared index file is either created or read from.
 
-Untracked cache
+UNTRACKED CACHE
 ---------------
 
 This cache is meant to speed up commands that involve determining
@@ -490,7 +490,7 @@ As with the bug described above the solution is to one-off do a "git
 status" run with `core.untrackedCache=false` to flush out the leftover
 bad data.
 
-File System Monitor
+FILE SYSTEM MONITOR
 -------------------
 
 This feature is intended to speed up git operations for repos that have
@@ -518,7 +518,7 @@ file system monitor is added to or removed from the index the next time
 a command reads the index. When `--[no-]fsmonitor` are used, the file
 system monitor is immediately added to or removed from the index.
 
-Configuration
+CONFIGURATION
 -------------
 
 The command honors `core.filemode` configuration variable.  If
index 969bfab2ab422ca8b7bdf2eb3ba45edc92fcd00b..bc8fdfd4691326b44e2cbdea3dc4925cf7440515 100644 (file)
@@ -120,7 +120,7 @@ modifications are performed.  Note that while each individual
 <ref> is updated or deleted atomically, a concurrent reader may
 still see a subset of the modifications.
 
-Logging Updates
+LOGGING UPDATES
 ---------------
 If config parameter "core.logAllRefUpdates" is true and the ref is one under
 "refs/heads/", "refs/remotes/", "refs/notes/", or the symbolic ref HEAD; or
index 44ff9541df1f5dd432d37b5828bfb800f66dfae8..6072f936ab5e3a8b3aeafcaeddd81b7b6d5e0b01 100644 (file)
@@ -23,14 +23,14 @@ OPTIONS
        as well. (However, the configuration variables listing functionality
        is deprecated in favor of `git config -l`.)
 
-EXAMPLE
+EXAMPLES
 --------
        $ git var GIT_AUTHOR_IDENT
        Eric W. Biederman <ebiederm@lnxi.com> 1121223278 -0600
 
 
 VARIABLES
-----------
+---------
 GIT_AUTHOR_IDENT::
     The author of a piece of code.
 
index 2d6b09a43cd63e3ad768f9b49ba67f1deda05111..fd952a5ff92b72e67eac219fdfbbe5d7c708be81 100644 (file)
@@ -8,7 +8,7 @@ git-web--browse - Git helper script to launch a web browser
 SYNOPSIS
 --------
 [verse]
-'git web{litdd}browse' [OPTIONS] URL/FILE ...
+'git web{litdd}browse' [<options>] <url|file>...
 
 DESCRIPTION
 -----------
@@ -84,7 +84,7 @@ variable exists then 'git web{litdd}browse' will treat the specified tool
 as a custom command and will use a shell eval to run the command with
 the URLs passed as arguments.
 
-Note about konqueror
+NOTE ABOUT KONQUEROR
 --------------------
 
 When 'konqueror' is specified by a command-line option or a
index 9920d9c06ed213ad12c89050742b2da949303445..afc6576a14d56ea49e37d1251a5665bf77457f89 100644 (file)
@@ -61,8 +61,13 @@ $ git worktree add --track -b <branch> <path> <remote>/<branch>
 ------------
 +
 If `<commit-ish>` is omitted and neither `-b` nor `-B` nor `--detach` used,
-then, as a convenience, a new branch based at HEAD is created automatically,
-as if `-b $(basename <path>)` was specified.
+then, as a convenience, the new worktree is associated with a branch
+(call it `<branch>`) named after `$(basename <path>)`.  If `<branch>`
+doesn't exist, a new branch based on HEAD is automatically created as
+if `-b <branch>` was given.  If `<branch>` does exist, it will be
+checked out in the new worktree, if it's not checked out anywhere
+else, otherwise the command will refuse to create the worktree (unless
+`--force` is used).
 
 list::
 
index 4767860e72f46d4e4df883f2fdbb4a46eb8e8eda..dba7f0c18e33e7e26ca100992236e39b3d5b8a91 100644 (file)
@@ -11,7 +11,7 @@ SYNOPSIS
 [verse]
 'git' [--version] [--help] [-C <path>] [-c <name>=<value>]
     [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
-    [-p|--paginate|--no-pager] [--no-replace-objects] [--bare]
+    [-p|--paginate|-P|--no-pager] [--no-replace-objects] [--bare]
     [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
     [--super-prefix=<path>]
     <command> [<args>]
@@ -103,6 +103,7 @@ foo.bar= ...`) sets `foo.bar` to the empty string which `git config
        configuration options (see the "Configuration Mechanism" section
        below).
 
+-P::
 --no-pager::
        Do not pipe Git output into a pager.
 
@@ -163,6 +164,16 @@ foo.bar= ...`) sets `foo.bar` to the empty string which `git config
        Do not perform optional operations that require locks. This is
        equivalent to setting the `GIT_OPTIONAL_LOCKS` to `0`.
 
+--list-cmds=group[,group...]::
+       List commands by group. This is an internal/experimental
+       option and may change or be removed in the future. Supported
+       groups are: builtins, parseopt (builtin commands that use
+       parse-options), main (all commands in libexec directory),
+       others (all other commands in `$PATH` that have git- prefix),
+       list-<category> (see categories in command-list.txt),
+       nohelpers (exclude helper commands), alias and config
+       (retrieve command list from config variable completion.commands)
+
 GIT COMMANDS
 ------------
 
index ee210be3ecff794389764ebca2aff1d7658018f5..92010b062e08678fcfb0a143f75bbd4b07bfcf4b 100644 (file)
@@ -3,7 +3,7 @@ gitattributes(5)
 
 NAME
 ----
-gitattributes - defining attributes per path
+gitattributes - Defining attributes per path
 
 SYNOPSIS
 --------
@@ -1229,8 +1229,8 @@ to:
 ------------
 
 
-EXAMPLE
--------
+EXAMPLES
+--------
 
 If you have these three `gitattributes` file:
 
index 9f13266a685176bf142d9e381d7a9d10ea12aa7b..592e06d839d87f28cf6cbbdcf3aa3253da2eb9f0 100644 (file)
@@ -110,8 +110,8 @@ couple of magic command-line options:
 +
 ---------------------------------------------
 $ git describe -h
-usage: git describe [options] <commit-ish>*
-   or: git describe [options] --dirty
+usage: git describe [<options>] <commit-ish>*
+   or: git describe [<options>] --dirty
 
     --contains            find the tag that comes after the commit
     --debug               debug search strategy on stderr
index f877f7b7cd19c3fb1d2ce1263d64c94798c5130d..e3c283a174c46352f26e3e6a957e72b76bf82680 100644 (file)
@@ -31,7 +31,7 @@ Hooks can get their arguments via the environment, command-line
 arguments, and stdin. See the documentation for each hook below for
 details.
 
-'git init' may copy hooks to the new repository, depending on its
+`git init` may copy hooks to the new repository, depending on its
 configuration. See the "TEMPLATE DIRECTORY" section in
 linkgit:git-init[1] for details. When the rest of this document refers
 to "default hooks" it's talking about the default template shipped
@@ -45,9 +45,9 @@ HOOKS
 applypatch-msg
 ~~~~~~~~~~~~~~
 
-This hook is invoked by 'git am'.  It takes a single
+This hook is invoked by linkgit:git-am[1].  It takes a single
 parameter, the name of the file that holds the proposed commit
-log message.  Exiting with a non-zero status causes 'git am' to abort
+log message.  Exiting with a non-zero status causes `git am` to abort
 before applying the patch.
 
 The hook is allowed to edit the message file in place, and can
@@ -61,7 +61,7 @@ The default 'applypatch-msg' hook, when enabled, runs the
 pre-applypatch
 ~~~~~~~~~~~~~~
 
-This hook is invoked by 'git am'.  It takes no parameter, and is
+This hook is invoked by linkgit:git-am[1].  It takes no parameter, and is
 invoked after the patch is applied, but before a commit is made.
 
 If it exits with non-zero status, then the working tree will not be
@@ -76,33 +76,33 @@ The default 'pre-applypatch' hook, when enabled, runs the
 post-applypatch
 ~~~~~~~~~~~~~~~
 
-This hook is invoked by 'git am'.  It takes no parameter,
+This hook is invoked by linkgit:git-am[1].  It takes no parameter,
 and is invoked after the patch is applied and a commit is made.
 
 This hook is meant primarily for notification, and cannot affect
-the outcome of 'git am'.
+the outcome of `git am`.
 
 pre-commit
 ~~~~~~~~~~
 
-This hook is invoked by 'git commit', and can be bypassed
+This hook is invoked by linkgit:git-commit[1], and can be bypassed
 with the `--no-verify` option.  It takes no parameters, and is
 invoked before obtaining the proposed commit log message and
 making a commit.  Exiting with a non-zero status from this script
-causes the 'git commit' command to abort before creating a commit.
+causes the `git commit` command to abort before creating a commit.
 
 The default 'pre-commit' hook, when enabled, catches introduction
 of lines with trailing whitespaces and aborts the commit when
 such a line is found.
 
-All the 'git commit' hooks are invoked with the environment
+All the `git commit` hooks are invoked with the environment
 variable `GIT_EDITOR=:` if the command will not bring up an editor
 to modify the commit message.
 
 prepare-commit-msg
 ~~~~~~~~~~~~~~~~~~
 
-This hook is invoked by 'git commit' right after preparing the
+This hook is invoked by linkgit:git-commit[1] right after preparing the
 default log message, and before the editor is started.
 
 It takes one to three parameters.  The first is the name of the file
@@ -114,7 +114,7 @@ commit is a merge or a `.git/MERGE_MSG` file exists); `squash`
 (if a `.git/SQUASH_MSG` file exists); or `commit`, followed by
 a commit SHA-1 (if a `-c`, `-C` or `--amend` option was given).
 
-If the exit status is non-zero, 'git commit' will abort.
+If the exit status is non-zero, `git commit` will abort.
 
 The purpose of the hook is to edit the message file in place, and
 it is not suppressed by the `--no-verify` option.  A non-zero exit
@@ -127,7 +127,7 @@ help message found in the commented portion of the commit template.
 commit-msg
 ~~~~~~~~~~
 
-This hook is invoked by 'git commit' and 'git merge', and can be
+This hook is invoked by linkgit:git-commit[1] and linkgit:git-merge[1], and can be
 bypassed with the `--no-verify` option.  It takes a single parameter,
 the name of the file that holds the proposed commit log message.
 Exiting with a non-zero status causes the command to abort.
@@ -143,16 +143,16 @@ The default 'commit-msg' hook, when enabled, detects duplicate
 post-commit
 ~~~~~~~~~~~
 
-This hook is invoked by 'git commit'. It takes no parameters, and is
+This hook is invoked by linkgit:git-commit[1]. It takes no parameters, and is
 invoked after a commit is made.
 
 This hook is meant primarily for notification, and cannot affect
-the outcome of 'git commit'.
+the outcome of `git commit`.
 
 pre-rebase
 ~~~~~~~~~~
 
-This hook is called by 'git rebase' and can be used to prevent a
+This hook is called by linkgit:git-rebase[1] and can be used to prevent a
 branch from getting rebased.  The hook may be called with one or
 two parameters.  The first parameter is the upstream from which
 the series was forked.  The second parameter is the branch being
@@ -161,17 +161,17 @@ rebased, and is not set when rebasing the current branch.
 post-checkout
 ~~~~~~~~~~~~~
 
-This hook is invoked when a 'git checkout' is run after having updated the
+This hook is invoked when a linkgit:git-checkout[1] is run after having updated the
 worktree.  The hook is given three parameters: the ref of the previous HEAD,
 the ref of the new HEAD (which may or may not have changed), and a flag
 indicating whether the checkout was a branch checkout (changing branches,
 flag=1) or a file checkout (retrieving a file from the index, flag=0).
-This hook cannot affect the outcome of 'git checkout'.
+This hook cannot affect the outcome of `git checkout`.
 
-It is also run after 'git clone', unless the --no-checkout (-n) option is
+It is also run after linkgit:git-clone[1], unless the `--no-checkout` (`-n`) option is
 used. The first parameter given to the hook is the null-ref, the second the
-ref of the new HEAD and the flag is always 1. Likewise for 'git worktree add'
-unless --no-checkout is used.
+ref of the new HEAD and the flag is always 1. Likewise for `git worktree add`
+unless `--no-checkout` is used.
 
 This hook can be used to perform repository validity checks, auto-display
 differences from the previous HEAD if different, or set working dir metadata
@@ -180,10 +180,10 @@ properties.
 post-merge
 ~~~~~~~~~~
 
-This hook is invoked by 'git merge', which happens when a 'git pull'
+This hook is invoked by linkgit:git-merge[1], which happens when a `git pull`
 is done on a local repository.  The hook takes a single parameter, a status
 flag specifying whether or not the merge being done was a squash merge.
-This hook cannot affect the outcome of 'git merge' and is not executed,
+This hook cannot affect the outcome of `git merge` and is not executed,
 if the merge failed due to conflicts.
 
 This hook can be used in conjunction with a corresponding pre-commit hook to
@@ -194,10 +194,10 @@ for an example of how to do this.
 pre-push
 ~~~~~~~~
 
-This hook is called by 'git push' and can be used to prevent a push from taking
-place.  The hook is called with two parameters which provide the name and
-location of the destination remote, if a named remote is not being used both
-values will be the same.
+This hook is called by linkgit:git-push[1] and can be used to prevent
+a push from taking place.  The hook is called with two parameters
+which provide the name and location of the destination remote, if a
+named remote is not being used both values will be the same.
 
 Information about what is to be pushed is provided on the hook's standard
 input with lines of the form:
@@ -216,7 +216,7 @@ SHA-1>` will be 40 `0`.  If the local commit was specified by something other
 than a name which could be expanded (such as `HEAD~`, or a SHA-1) it will be
 supplied as it was originally given.
 
-If this hook exits with a non-zero status, 'git push' will abort without
+If this hook exits with a non-zero status, `git push` will abort without
 pushing anything.  Information about why the push is rejected may be sent
 to the user by writing to standard error.
 
@@ -224,8 +224,8 @@ to the user by writing to standard error.
 pre-receive
 ~~~~~~~~~~~
 
-This hook is invoked by 'git-receive-pack' when it reacts to
-'git push' and updates reference(s) in its repository.
+This hook is invoked by linkgit:git-receive-pack[1] when it reacts to
+`git push` and updates reference(s) in its repository.
 Just before starting to update refs on the remote repository, the
 pre-receive hook is invoked.  Its exit status determines the success
 or failure of the update.
@@ -246,7 +246,7 @@ updated. If the hook exits with zero, updating of individual refs can
 still be prevented by the <<update,'update'>> hook.
 
 Both standard output and standard error output are forwarded to
-'git send-pack' on the other end, so you can simply `echo` messages
+`git send-pack` on the other end, so you can simply `echo` messages
 for the user.
 
 The number of push options given on the command line of
@@ -265,8 +265,8 @@ linkgit:git-receive-pack[1] for some caveats.
 update
 ~~~~~~
 
-This hook is invoked by 'git-receive-pack' when it reacts to
-'git push' and updates reference(s) in its repository.
+This hook is invoked by linkgit:git-receive-pack[1] when it reacts to
+`git push` and updates reference(s) in its repository.
 Just before updating the ref on the remote repository, the update hook
 is invoked.  Its exit status determines the success or failure of
 the ref update.
@@ -279,7 +279,7 @@ three parameters:
  - and the new object name to be stored in the ref.
 
 A zero exit from the update hook allows the ref to be updated.
-Exiting with a non-zero status prevents 'git-receive-pack'
+Exiting with a non-zero status prevents `git receive-pack`
 from updating that ref.
 
 This hook can be used to prevent 'forced' update on certain refs by
@@ -299,7 +299,7 @@ membership. See linkgit:git-shell[1] for how you might use the login
 shell to restrict the user's access to only git commands.
 
 Both standard output and standard error output are forwarded to
-'git send-pack' on the other end, so you can simply `echo` messages
+`git send-pack` on the other end, so you can simply `echo` messages
 for the user.
 
 The default 'update' hook, when enabled--and with
@@ -310,8 +310,8 @@ unannotated tags to be pushed.
 post-receive
 ~~~~~~~~~~~~
 
-This hook is invoked by 'git-receive-pack' when it reacts to
-'git push' and updates reference(s) in its repository.
+This hook is invoked by linkgit:git-receive-pack[1] when it reacts to
+`git push` and updates reference(s) in its repository.
 It executes on the remote repository once after all the refs have
 been updated.
 
@@ -320,7 +320,7 @@ arguments, but gets the same information as the
 <<pre-receive,'pre-receive'>>
 hook does on its standard input.
 
-This hook does not affect the outcome of 'git-receive-pack', as it
+This hook does not affect the outcome of `git receive-pack`, as it
 is called after the real work is done.
 
 This supersedes the <<post-update,'post-update'>> hook in that it gets
@@ -328,7 +328,7 @@ both old and new values of all the refs in addition to their
 names.
 
 Both standard output and standard error output are forwarded to
-'git send-pack' on the other end, so you can simply `echo` messages
+`git send-pack` on the other end, so you can simply `echo` messages
 for the user.
 
 The default 'post-receive' hook is empty, but there is
@@ -349,8 +349,8 @@ will be set to zero, `GIT_PUSH_OPTION_COUNT=0`.
 post-update
 ~~~~~~~~~~~
 
-This hook is invoked by 'git-receive-pack' when it reacts to
-'git push' and updates reference(s) in its repository.
+This hook is invoked by linkgit:git-receive-pack[1] when it reacts to
+`git push` and updates reference(s) in its repository.
 It executes on the remote repository once after all the refs have
 been updated.
 
@@ -358,7 +358,7 @@ It takes a variable number of parameters, each of which is the
 name of ref that was actually updated.
 
 This hook is meant primarily for notification, and cannot affect
-the outcome of 'git-receive-pack'.
+the outcome of `git receive-pack`.
 
 The 'post-update' hook can tell what are the heads that were pushed,
 but it does not know what their original and updated values are,
@@ -368,20 +368,20 @@ updated values of the refs. You might consider it instead if you need
 them.
 
 When enabled, the default 'post-update' hook runs
-'git update-server-info' to keep the information used by dumb
+`git update-server-info` to keep the information used by dumb
 transports (e.g., HTTP) up to date.  If you are publishing
 a Git repository that is accessible via HTTP, you should
 probably enable this hook.
 
 Both standard output and standard error output are forwarded to
-'git send-pack' on the other end, so you can simply `echo` messages
+`git send-pack` on the other end, so you can simply `echo` messages
 for the user.
 
 push-to-checkout
 ~~~~~~~~~~~~~~~~
 
-This hook is invoked by 'git-receive-pack' when it reacts to
-'git push' and updates reference(s) in its repository, and when
+This hook is invoked by linkgit:git-receive-pack[1] when it reacts to
+`git push` and updates reference(s) in its repository, and when
 the push tries to update the branch that is currently checked out
 and the `receive.denyCurrentBranch` configuration variable is set to
 `updateInstead`.  Such a push by default is refused if the working
@@ -400,8 +400,8 @@ when the tip of the current branch is updated to the new commit, and
 exit with a zero status.
 
 For example, the hook can simply run `git read-tree -u -m HEAD "$1"`
-in order to emulate 'git fetch' that is run in the reverse direction
-with `git push`, as the two-tree form of `read-tree -u -m` is
+in order to emulate `git fetch` that is run in the reverse direction
+with `git push`, as the two-tree form of `git read-tree -u -m` is
 essentially the same as `git checkout` that switches branches while
 keeping the local changes in the working tree that do not interfere
 with the difference between the branches.
@@ -410,15 +410,16 @@ with the difference between the branches.
 pre-auto-gc
 ~~~~~~~~~~~
 
-This hook is invoked by 'git gc --auto'. It takes no parameter, and
-exiting with non-zero status from this script causes the 'git gc --auto'
-to abort.
+This hook is invoked by `git gc --auto` (see linkgit:git-gc[1]). It
+takes no parameter, and exiting with non-zero status from this script
+causes the `git gc --auto` to abort.
 
 post-rewrite
 ~~~~~~~~~~~~
 
-This hook is invoked by commands that rewrite commits (`git commit
---amend`, 'git-rebase'; currently 'git-filter-branch' does 'not' call
+This hook is invoked by commands that rewrite commits
+(linkgit:git-commit[1] when called with `--amend` and
+linkgit:git-rebase[1]; currently `git filter-branch` does 'not' call
 it!).  Its first argument denotes the command it was invoked by:
 currently one of `amend` or `rebase`.  Further command-dependent
 arguments may be passed in the future.
@@ -450,16 +451,16 @@ processed by rebase.
 sendemail-validate
 ~~~~~~~~~~~~~~~~~~
 
-This hook is invoked by 'git send-email'.  It takes a single parameter,
+This hook is invoked by linkgit:git-send-email[1].  It takes a single parameter,
 the name of the file that holds the e-mail to be sent.  Exiting with a
-non-zero status causes 'git send-email' to abort before sending any
+non-zero status causes `git send-email` to abort before sending any
 e-mails.
 
 fsmonitor-watchman
 ~~~~~~~~~~~~~~~~~~
 
-This hook is invoked when the configuration option core.fsmonitor is
-set to .git/hooks/fsmonitor-watchman.  It takes two arguments, a version
+This hook is invoked when the configuration option `core.fsmonitor` is
+set to `.git/hooks/fsmonitor-watchman`.  It takes two arguments, a version
 (currently 1) and the time in elapsed nanoseconds since midnight,
 January 1, 1970.
 
@@ -478,7 +479,7 @@ directories are checked for untracked files based on the path names
 given.
 
 An optimized way to tell git "all files have changed" is to return
-the filename '/'.
+the filename `/`.
 
 The exit status determines whether git will use the data from the
 hook to limit its search.  On error, it will fall back to verifying
index db5d47eb19b8f2aa21c11a9d3eedf40b9d71e84e..4d63def2069a8358afa05154c18653175dda6703 100644 (file)
@@ -3,7 +3,7 @@ gitmodules(5)
 
 NAME
 ----
-gitmodules - defining submodule properties
+gitmodules - Defining submodule properties
 
 SYNOPSIS
 --------
index 27dec5b91d08bbdb114400dd3c86a8cf515e8774..1f6cceaefb028e5f96d802703a25a16e950a3e17 100644 (file)
@@ -3,7 +3,7 @@ gitrevisions(7)
 
 NAME
 ----
-gitrevisions - specifying revisions and ranges for Git
+gitrevisions - Specifying revisions and ranges for Git
 
 SYNOPSIS
 --------
index 12b6bbf591ee801e0723f59db0e0abe7dc88ec73..662c2713cab4b9762dded702897d9e92ee0a0549 100644 (file)
@@ -35,7 +35,13 @@ include::fmt-merge-msg-config.txt[]
 merge.renameLimit::
        The number of files to consider when performing rename detection
        during a merge; if not specified, defaults to the value of
-       diff.renameLimit.
+       diff.renameLimit. This setting has no effect if rename detection
+       is turned off.
+
+merge.renames::
+       Whether and how Git detects renames.  If set to "false",
+       rename detection is disabled. If set to "true", basic rename
+       detection is enabled.  Defaults to the value of diff.renames.
 
 merge.renormalize::
        Tell Git that canonical representation of files in the
index 4a58aad4b83b9e365b57a8afa71f1ab939c47aee..aa66cbe41eaf070a74d98801ba973daeb8d15106 100644 (file)
@@ -23,8 +23,9 @@ recursive::
        causing mismerges by tests done on actual merge commits
        taken from Linux 2.6 kernel development history.
        Additionally this can detect and handle merges involving
-       renames.  This is the default merge strategy when
-       pulling or merging one branch.
+       renames, but currently cannot make use of detected
+       copies.  This is the default merge strategy when pulling
+       or merging one branch.
 +
 The 'recursive' strategy can take the following options:
 
@@ -84,12 +85,14 @@ no-renormalize;;
        `merge.renormalize` configuration variable.
 
 no-renames;;
-       Turn off rename detection.
+       Turn off rename detection. This overrides the `merge.renames`
+       configuration variable.
        See also linkgit:git-diff[1] `--no-renames`.
 
 find-renames[=<n>];;
        Turn on rename detection, optionally setting the similarity
-       threshold.  This is the default.
+       threshold.  This is the default. This overrides the
+       'merge.renames' configuration variable.
        See also linkgit:git-diff[1] `--find-renames`.
 
 rename-threshold=<n>;;
index dfcc49c72c0fe8f90c5e66d8de30d7ad27582c2b..7d1bd440944149bb8de03a9a202e46c85ec646f2 100644 (file)
@@ -7,6 +7,10 @@ syntax.  Here are various ways to spell object names.  The
 ones listed near the end of this list name trees and
 blobs contained in a commit.
 
+NOTE: This document shows the "raw" syntax as seen by git. The shell
+and other UIs might require additional quoting to protect special
+characters and to avoid word splitting.
+
 '<sha1>', e.g. 'dae86e1950b1277e545cee180551750029cfe735', 'dae86e'::
   The full SHA-1 object name (40-byte hexadecimal string), or
   a leading substring that is unique within the repository.
@@ -186,6 +190,8 @@ existing tag object.
   is matched. ':/!-foo' performs a negative match, while ':/!!foo' matches a
   literal '!' character, followed by 'foo'. Any other sequence beginning with
   ':/!' is reserved for now.
+  Depending on the given text, the shell's word splitting rules might
+  require additional quoting.
 
 '<rev>:<path>', e.g. 'HEAD:README', ':README', 'master:./README'::
   A suffix ':' followed by a path names the blob or tree
@@ -345,6 +351,7 @@ Here are a handful of examples using the Loeliger illustration above,
 with each step in the notation's expansion and selection carefully
 spelt out:
 
+....
    Args   Expanded arguments    Selected commits
    D                            G H D
    D F                          G H I J D F
@@ -367,3 +374,4 @@ spelt out:
          = B ^B^1 ^B^2 ^B^3
          = B ^D ^E ^F          B
    F^! D  = F ^I ^J D           G H D F
+....
index 9a778b0cad02faab3ce6bd7f89c24a17de45b777..fa39ac9d719b57e0df33441b11144bede4822621 100644 (file)
@@ -47,21 +47,23 @@ will first feed the user-wide one to the callback, and then the
 repo-specific one; by overwriting, the higher-priority repo-specific
 value is left at the end).
 
-The `git_config_with_options` function lets the caller examine config
+The `config_with_options` function lets the caller examine config
 while adjusting some of the default behavior of `git_config`. It should
 almost never be used by "regular" Git code that is looking up
 configuration variables. It is intended for advanced callers like
 `git-config`, which are intentionally tweaking the normal config-lookup
 process. It takes two extra parameters:
 
-`filename`::
-If this parameter is non-NULL, it specifies the name of a file to
-parse for configuration, rather than looking in the usual files. Regular
-`git_config` defaults to `NULL`.
+`config_source`::
+If this parameter is non-NULL, it specifies the source to parse for
+configuration, rather than looking in the usual files. See `struct
+git_config_source` in `config.h` for details. Regular `git_config` defaults
+to `NULL`.
 
-`respect_includes`::
-Specify whether include directives should be followed in parsed files.
-Regular `git_config` defaults to `1`.
+`opts`::
+Specify options to adjust the behavior of parsing config files. See `struct
+config_options` in `config.h` for details. As an example: regular `git_config`
+sets `opts.respect_includes` to `1` by default.
 
 Reading Specific Files
 ----------------------
index b0c11f868da3c66ae263ca2755137420ce022b71..9febfb1d528b2764d6d603ea25eae0ac6b048f68 100644 (file)
@@ -35,13 +35,18 @@ Functions
        Free all memory associated with the array and return it to the
        initial, empty state.
 
+`oid_array_for_each`::
+       Iterate over each element of the list, executing the callback
+       function for each one. Does not sort the list, so any custom
+       hash order is retained. If the callback returns a non-zero
+       value, the iteration ends immediately and the callback's
+       return is propagated; otherwise, 0 is returned.
+
 `oid_array_for_each_unique`::
-       Efficiently iterate over each unique element of the list,
-       executing the callback function for each one. If the array is
-       not sorted, this function has the side effect of sorting it. If
-       the callback returns a non-zero value, the iteration ends
-       immediately and the callback's return is propagated; otherwise,
-       0 is returned.
+       Iterate over each unique element of the list in sorted order,
+       but otherwise behave like `oid_array_for_each`. If the array
+       is not sorted, this function has the side effect of sorting
+       it.
 
 Examples
 --------
index 8e5bf60be3f0689d61feb8ce43cb379b2417fd8f..70a99fd1423894255f5e0e8cdbb345276620ffde 100644 (file)
@@ -36,6 +36,98 @@ Git pack format
 
   - The trailer records 20-byte SHA-1 checksum of all of the above.
 
+=== Object types
+
+Valid object types are:
+
+- OBJ_COMMIT (1)
+- OBJ_TREE (2)
+- OBJ_BLOB (3)
+- OBJ_TAG (4)
+- OBJ_OFS_DELTA (6)
+- OBJ_REF_DELTA (7)
+
+Type 5 is reserved for future expansion. Type 0 is invalid.
+
+=== Deltified representation
+
+Conceptually there are only four object types: commit, tree, tag and
+blob. However to save space, an object could be stored as a "delta" of
+another "base" object. These representations are assigned new types
+ofs-delta and ref-delta, which is only valid in a pack file.
+
+Both ofs-delta and ref-delta store the "delta" to be applied to
+another object (called 'base object') to reconstruct the object. The
+difference between them is, ref-delta directly encodes 20-byte base
+object name. If the base object is in the same pack, ofs-delta encodes
+the offset of the base object in the pack instead.
+
+The base object could also be deltified if it's in the same pack.
+Ref-delta can also refer to an object outside the pack (i.e. the
+so-called "thin pack"). When stored on disk however, the pack should
+be self contained to avoid cyclic dependency.
+
+The delta data is a sequence of instructions to reconstruct an object
+from the base object. If the base object is deltified, it must be
+converted to canonical form first. Each instruction appends more and
+more data to the target object until it's complete. There are two
+supported instructions so far: one for copy a byte range from the
+source object and one for inserting new data embedded in the
+instruction itself.
+
+Each instruction has variable length. Instruction type is determined
+by the seventh bit of the first octet. The following diagrams follow
+the convention in RFC 1951 (Deflate compressed data format).
+
+==== Instruction to copy from base object
+
+  +----------+---------+---------+---------+---------+-------+-------+-------+
+  | 1xxxxxxx | offset1 | offset2 | offset3 | offset4 | size1 | size2 | size3 |
+  +----------+---------+---------+---------+---------+-------+-------+-------+
+
+This is the instruction format to copy a byte range from the source
+object. It encodes the offset to copy from and the number of bytes to
+copy. Offset and size are in little-endian order.
+
+All offset and size bytes are optional. This is to reduce the
+instruction size when encoding small offsets or sizes. The first seven
+bits in the first octet determines which of the next seven octets is
+present. If bit zero is set, offset1 is present. If bit one is set
+offset2 is present and so on.
+
+Note that a more compact instruction does not change offset and size
+encoding. For example, if only offset2 is omitted like below, offset3
+still contains bits 16-23. It does not become offset2 and contains
+bits 8-15 even if it's right next to offset1.
+
+  +----------+---------+---------+
+  | 10000101 | offset1 | offset3 |
+  +----------+---------+---------+
+
+In its most compact form, this instruction only takes up one byte
+(0x80) with both offset and size omitted, which will have default
+values zero. There is another exception: size zero is automatically
+converted to 0x10000.
+
+==== Instruction to add new data
+
+  +----------+============+
+  | 0xxxxxxx |    data    |
+  +----------+============+
+
+This is the instruction to construct target object without the base
+object. The following data is appended to the target object. The first
+seven bits of the first octet determines the size of data in
+bytes. The size must be non-zero.
+
+==== Reserved instruction
+
+  +----------+============
+  | 00000000 |
+  +----------+============
+
+This is the instruction reserved for future expansion.
+
 == Original (version 1) pack-*.idx files have the following format:
 
   - The header consists of 256 4-byte network byte order
index 136179d7d8bcaf72686bed4aa5e3cd9376ef77bd..49bda76d231d08173a48593d0910b3cbc934f01c 100644 (file)
@@ -290,6 +290,15 @@ included in the clients request as well as the potential addition of the
        Cannot be used with "deepen", but can be used with
        "deepen-since".
 
+If the 'filter' feature is advertised, the following argument can be
+included in the client's request:
+
+    filter <filter-spec>
+       Request that various objects from the packfile be omitted
+       using one of several filtering techniques. These are intended
+       for use with partial clone and partial fetch operations. See
+       `rev-list` for possible "filter-spec" values.
+
 The response of `fetch` is broken into a number of sections separated by
 delimiter packets (0001), with each section beginning with its section
 header.
@@ -393,3 +402,13 @@ header.
                1 - pack data
                2 - progress messages
                3 - fatal error message just before stream aborts
+
+ server-option
+~~~~~~~~~~~~~~~
+
+If advertised, indicates that any number of server specific options can be
+included in a request.  This is done by sending each option as a
+"server-option=<option>" capability line in the capability-list section of
+a request.
+
+The provided options must not contain a NUL or LF character.
index 5183b154229d7c25f0feab3d0afcd1d52ca652e0..01dedfe9ffedc28c2c6ac5ef15dbb3c5fe519cc0 100644 (file)
@@ -8,20 +8,22 @@ repo, and therefore grafts are introduced pretending that
 these commits have no parents.
 *********************************************************
 
-The basic idea is to write the SHA-1s of shallow commits into
-$GIT_DIR/shallow, and handle its contents like the contents
-of $GIT_DIR/info/grafts (with the difference that shallow
-cannot contain parent information).
-
-This information is stored in a new file instead of grafts, or
-even the config, since the user should not touch that file
-at all (even throughout development of the shallow clone, it
-was never manually edited!).
+$GIT_DIR/shallow lists commit object names and tells Git to
+pretend as if they are root commits (e.g. "git log" traversal
+stops after showing them; "git fsck" does not complain saying
+the commits listed on their "parent" lines do not exist).
 
 Each line contains exactly one SHA-1. When read, a commit_graft
 will be constructed, which has nr_parent < 0 to make it easier
 to discern from user provided grafts.
 
+Note that the shallow feature could not be changed easily to
+use replace refs: a commit containing a `mergetag` is not allowed
+to be replaced, not even by a root commit. Such a commit can be
+made shallow, though. Also, having a `shallow` file explicitly
+listing all the commits made shallow makes it a *lot* easier to
+do shallow-specific things such as to deepen the history.
+
 Since fsck-objects relies on the library to read the objects,
 it honours shallow commits automatically.
 
index 12ff59c2c78565a1ad9c491147d2b8e0615aa1e5..bdd758e40951273e71bbae2fd69c2104c85cfd3d 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v2.17.GIT
+DEF_VER=v2.18.0-rc0
 
 LF='
 '
index 4bca65383a784dca7da0092adb6c2eb754fa83cf..1d27f36365ae2485e3706548b2b0299436902cc6 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -795,7 +795,7 @@ LIB_FILE = libgit.a
 XDIFF_LIB = xdiff/lib.a
 VCSSVN_LIB = vcs-svn/lib.a
 
-GENERATED_H += common-cmds.h
+GENERATED_H += command-list.h
 
 LIB_H = $(shell $(FIND) . \
        -name .git -prune -o \
@@ -2006,9 +2006,9 @@ git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
                $(filter %.o,$^) $(LIBS)
 
-help.sp help.s help.o: common-cmds.h
+help.sp help.s help.o: command-list.h
 
-builtin/help.sp builtin/help.s builtin/help.o: common-cmds.h GIT-PREFIX
+builtin/help.sp builtin/help.s builtin/help.o: command-list.h GIT-PREFIX
 builtin/help.sp builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \
        '-DGIT_HTML_PATH="$(htmldir_relative_SQ)"' \
        '-DGIT_MAN_PATH="$(mandir_relative_SQ)"' \
@@ -2027,9 +2027,9 @@ $(BUILT_INS): git$X
        ln -s $< $@ 2>/dev/null || \
        cp $< $@
 
-common-cmds.h: generate-cmdlist.sh command-list.txt
+command-list.h: generate-cmdlist.sh command-list.txt
 
-common-cmds.h: $(wildcard Documentation/git-*.txt)
+command-list.h: $(wildcard Documentation/git*.txt)
        $(QUIET_GEN)$(SHELL_PATH) ./generate-cmdlist.sh command-list.txt >$@+ && mv $@+ $@
 
 SCRIPT_DEFINES = $(SHELL_PATH_SQ):$(DIFF_SQ):$(GIT_VERSION):\
@@ -2273,7 +2273,7 @@ else
 # Dependencies on header files, for platforms that do not support
 # the gcc -MMD option.
 #
-# Dependencies on automatically generated headers such as common-cmds.h
+# Dependencies on automatically generated headers such as command-list.h
 # should _not_ be included here, since they are necessary even when
 # building an object for the first time.
 
@@ -2653,7 +2653,7 @@ sparse: $(SP_OBJ)
 style:
        git clang-format --style file --diff --extensions c,h
 
-check: common-cmds.h
+check: command-list.h
        @if sparse; \
        then \
                echo >&2 "Use 'make sparse' instead"; \
@@ -2901,7 +2901,7 @@ clean: profile-clean coverage-clean
        $(RM) $(TEST_PROGRAMS) $(NO_INSTALL)
        $(RM) -r bin-wrappers $(dep_dirs)
        $(RM) -r po/build/
-       $(RM) *.pyc *.pyo */*.pyc */*.pyo common-cmds.h $(ETAGS_TARGET) tags cscope*
+       $(RM) *.pyc *.pyo */*.pyc */*.pyo command-list.h $(ETAGS_TARGET) tags cscope*
        $(RM) -r $(GIT_TARNAME) .doc-tmp-dir
        $(RM) $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz
        $(RM) $(htmldocs).tar.gz $(manpages).tar.gz
index 89fda1de55bfc80ba884ab45dd7a31087dea66df..370a56d0546bb3fc7aa4a62203c600e9a8af2d7a 100644 (file)
--- a/advice.c
+++ b/advice.c
@@ -20,6 +20,7 @@ int advice_rm_hints = 1;
 int advice_add_embedded_repo = 1;
 int advice_ignored_hook = 1;
 int advice_waiting_for_editor = 1;
+int advice_graft_file_deprecated = 1;
 
 static int advice_use_color = -1;
 static char advice_colors[][COLOR_MAXLEN] = {
@@ -70,6 +71,7 @@ static struct {
        { "addembeddedrepo", &advice_add_embedded_repo },
        { "ignoredhook", &advice_ignored_hook },
        { "waitingforeditor", &advice_waiting_for_editor },
+       { "graftfiledeprecated", &advice_graft_file_deprecated },
 
        /* make this an alias for backward compatibility */
        { "pushnonfastforward", &advice_push_update_rejected }
index 70568fa7922d8aea403e415f9b9b554a88ace293..9f5064e82a862e6daaabe3f23ea9ea896aad87e6 100644 (file)
--- a/advice.h
+++ b/advice.h
@@ -21,6 +21,7 @@ extern int advice_rm_hints;
 extern int advice_add_embedded_repo;
 extern int advice_ignored_hook;
 extern int advice_waiting_for_editor;
+extern int advice_graft_file_deprecated;
 
 int git_default_advice_config(const char *var, const char *value);
 __attribute__((format (printf, 1, 2)))
diff --git a/alias.c b/alias.c
index bf146e526329309df360b9f1028ca2f767743efa..a7e4e57130d46188a4a0ee93871375218041344c 100644 (file)
--- a/alias.c
+++ b/alias.c
@@ -1,9 +1,12 @@
 #include "cache.h"
+#include "alias.h"
 #include "config.h"
+#include "string-list.h"
 
 struct config_alias_data {
        const char *alias;
        char *v;
+       struct string_list *list;
 };
 
 static int config_alias_cb(const char *key, const char *value, void *d)
@@ -11,8 +14,16 @@ static int config_alias_cb(const char *key, const char *value, void *d)
        struct config_alias_data *data = d;
        const char *p;
 
-       if (skip_prefix(key, "alias.", &p) && !strcasecmp(p, data->alias))
-               return git_config_string((const char **)&data->v, key, value);
+       if (!skip_prefix(key, "alias.", &p))
+               return 0;
+
+       if (data->alias) {
+               if (!strcasecmp(p, data->alias))
+                       return git_config_string((const char **)&data->v,
+                                                key, value);
+       } else if (data->list) {
+               string_list_append(data->list, p);
+       }
 
        return 0;
 }
@@ -26,6 +37,13 @@ char *alias_lookup(const char *alias)
        return data.v;
 }
 
+void list_aliases(struct string_list *list)
+{
+       struct config_alias_data data = { NULL, NULL, list };
+
+       read_early_config(config_alias_cb, &data);
+}
+
 #define SPLIT_CMDLINE_BAD_ENDING 1
 #define SPLIT_CMDLINE_UNCLOSED_QUOTE 2
 static const char *split_cmdline_errors[] = {
diff --git a/alias.h b/alias.h
new file mode 100644 (file)
index 0000000..79933f2
--- /dev/null
+++ b/alias.h
@@ -0,0 +1,12 @@
+#ifndef __ALIAS_H__
+#define __ALIAS_H__
+
+struct string_list;
+
+char *alias_lookup(const char *alias);
+int split_cmdline(char *cmdline, const char ***argv);
+/* Takes a negative value returned by split_cmdline */
+const char *split_cmdline_strerror(int cmdline_errno);
+void list_aliases(struct string_list *list);
+
+#endif
diff --git a/apply.c b/apply.c
index 7e5792c996f430952b1b768f8267de851156ce83..d79e61591b3814339fb8b2a39fb7d1e211d622c6 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -2375,7 +2375,7 @@ static void update_pre_post_images(struct image *preimage,
        if (postlen
            ? postlen < new_buf - postimage->buf
            : postimage->len < new_buf - postimage->buf)
-               die("BUG: caller miscounted postlen: asked %d, orig = %d, used = %d",
+               BUG("caller miscounted postlen: asked %d, orig = %d, used = %d",
                    (int)postlen, (int) postimage->len, (int)(new_buf - postimage->buf));
 
        /* Fix the length of the whole thing */
@@ -3509,7 +3509,7 @@ static int load_current(struct apply_state *state,
        unsigned mode = patch->new_mode;
 
        if (!patch->is_new)
-               die("BUG: patch to %s is not a creation", patch->old_name);
+               BUG("patch to %s is not a creation", patch->old_name);
 
        pos = cache_name_pos(name, strlen(name));
        if (pos < 0)
@@ -3860,9 +3860,9 @@ static int check_unsafe_path(struct patch *patch)
        if (!patch->is_delete)
                new_name = patch->new_name;
 
-       if (old_name && !verify_path(old_name))
+       if (old_name && !verify_path(old_name, patch->old_mode))
                return error(_("invalid path '%s'"), old_name);
-       if (new_name && !verify_path(new_name))
+       if (new_name && !verify_path(new_name, patch->new_mode))
                return error(_("invalid path '%s'"), new_name);
        return 0;
 }
@@ -4058,7 +4058,7 @@ static int build_fake_ancestor(struct apply_state *state, struct patch *list)
 {
        struct patch *patch;
        struct index_state result = { NULL };
-       static struct lock_file lock;
+       struct lock_file lock = LOCK_INIT;
        int res;
 
        /* Once we start supporting the reverse patch, it may be
index 3563bcb9f263f7782a77c679bf9be32af6bd13df..b6f58ddf38767d86f866bd9413292e279c6cfa73 100644 (file)
@@ -276,7 +276,7 @@ static int write_tar_entry(struct archiver_args *args,
                memcpy(header.name, path, pathlen);
 
        if (S_ISREG(mode) && !args->convert &&
-           oid_object_info(oid, &size) == OBJ_BLOB &&
+           oid_object_info(the_repository, oid, &size) == OBJ_BLOB &&
            size > big_file_threshold)
                buffer = NULL;
        else if (S_ISLNK(mode) || S_ISREG(mode)) {
@@ -441,7 +441,7 @@ static int write_tar_filter_archive(const struct archiver *ar,
        int r;
 
        if (!ar->data)
-               die("BUG: tar-filter archiver called with no filter defined");
+               BUG("tar-filter archiver called with no filter defined");
 
        strbuf_addstr(&cmd, ar->data);
        if (args->compression_level >= 0)
index 6b20bce4d1cd78563037c8658dd8bd8f7690c47b..74f3fe9103420571c9f22273ae44ebb9d5715c78 100644 (file)
@@ -325,7 +325,8 @@ static int write_zip_entry(struct archiver_args *args,
                compressed_size = 0;
                buffer = NULL;
        } else if (S_ISREG(mode) || S_ISLNK(mode)) {
-               enum object_type type = oid_object_info(oid, &size);
+               enum object_type type = oid_object_info(the_repository, oid,
+                                                       &size);
 
                method = 0;
                attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) :
index 93ab175b0b4055bcfbd9334c7ccb36475c33e549..4fe7bec60c12b3b1ab27c35b716b23f4535d170c 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -411,11 +411,9 @@ static void parse_treeish_arg(const char **argv,
 }
 
 #define OPT__COMPR(s, v, h, p) \
-       { OPTION_SET_INT, (s), NULL, (v), NULL, (h), \
-         PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, (p) }
+       OPT_SET_INT_F(s, NULL, v, h, p, PARSE_OPT_NONEG)
 #define OPT__COMPR_HIDDEN(s, v, p) \
-       { OPTION_SET_INT, (s), NULL, (v), NULL, "", \
-         PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN, NULL, (p) }
+       OPT_SET_INT_F(s, NULL, v, "", p, PARSE_OPT_NONEG | PARSE_OPT_HIDDEN)
 
 static int parse_archive_args(int argc, const char **argv,
                const struct archiver **ar, struct archiver_args *args,
index 5d370fa3366163f8c0c81ca0b6b1a64a7030c696..f352ea9357d90e16c7506af9b5b047ac1e2721ac 100644 (file)
@@ -21,12 +21,13 @@ static void argv_array_push_nodup(struct argv_array *array, const char *value)
        array->argv[array->argc] = NULL;
 }
 
-void argv_array_push(struct argv_array *array, const char *value)
+const char *argv_array_push(struct argv_array *array, const char *value)
 {
        argv_array_push_nodup(array, xstrdup(value));
+       return array->argv[array->argc - 1];
 }
 
-void argv_array_pushf(struct argv_array *array, const char *fmt, ...)
+const char *argv_array_pushf(struct argv_array *array, const char *fmt, ...)
 {
        va_list ap;
        struct strbuf v = STRBUF_INIT;
@@ -36,6 +37,7 @@ void argv_array_pushf(struct argv_array *array, const char *fmt, ...)
        va_end(ap);
 
        argv_array_push_nodup(array, strbuf_detach(&v, NULL));
+       return array->argv[array->argc - 1];
 }
 
 void argv_array_pushl(struct argv_array *array, ...)
@@ -64,6 +66,26 @@ void argv_array_pop(struct argv_array *array)
        array->argc--;
 }
 
+void argv_array_split(struct argv_array *array, const char *to_split)
+{
+       while (isspace(*to_split))
+               to_split++;
+       for (;;) {
+               const char *p = to_split;
+
+               if (!*p)
+                       break;
+
+               while (*p && !isspace(*p))
+                       p++;
+               argv_array_push_nodup(array, xstrndup(to_split, p - to_split));
+
+               while (isspace(*p))
+                       p++;
+               to_split = p;
+       }
+}
+
 void argv_array_clear(struct argv_array *array)
 {
        if (array->argv != empty_argv) {
index 29056e49a1208b5506d0809c7311e4112dc1f7f3..a39ba43f57ce4969ef936646d30e5128ff28214f 100644 (file)
@@ -12,13 +12,15 @@ struct argv_array {
 #define ARGV_ARRAY_INIT { empty_argv, 0, 0 }
 
 void argv_array_init(struct argv_array *);
-void argv_array_push(struct argv_array *, const char *);
+const char *argv_array_push(struct argv_array *, const char *);
 __attribute__((format (printf,2,3)))
-void argv_array_pushf(struct argv_array *, const char *fmt, ...);
+const char *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 *);
+/* Splits by whitespace; does not handle quoted arguments! */
+void argv_array_split(struct argv_array *, const char *);
 void argv_array_clear(struct argv_array *);
 const char **argv_array_detach(struct argv_array *);
 
diff --git a/attr.c b/attr.c
index 03a678fa9be09ac71da4b654c99c1ab26bde3430..067fb9e0c08cefa2ba1a9b1d3444938c9bab6e3d 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -157,7 +157,7 @@ static void all_attrs_init(struct attr_hashmap *map, struct attr_check *check)
 
        size = hashmap_get_size(&map->map);
        if (size < check->all_attrs_nr)
-               die("BUG: interned attributes shouldn't be deleted");
+               BUG("interned attributes shouldn't be deleted");
 
        /*
         * If the number of attributes in the global dictionary has increased
@@ -541,7 +541,7 @@ static void check_vector_remove(struct attr_check *check)
                        break;
 
        if (i >= check_vector.nr)
-               die("BUG: no entry found");
+               BUG("no entry found");
 
        /* shift entries over */
        for (; i < check_vector.nr - 1; i++)
@@ -599,11 +599,11 @@ struct attr_check *attr_check_initl(const char *one, ...)
                const struct git_attr *attr;
                param = va_arg(params, const char *);
                if (!param)
-                       die("BUG: counted %d != ended at %d",
+                       BUG("counted %d != ended at %d",
                            check->nr, cnt);
                attr = git_attr(param);
                if (!attr)
-                       die("BUG: %s: not a valid attribute name", param);
+                       BUG("%s: not a valid attribute name", param);
                check->items[cnt].attr = attr;
        }
        va_end(params);
@@ -714,7 +714,7 @@ void git_attr_set_direction(enum git_attr_direction new_direction,
                            struct index_state *istate)
 {
        if (is_bare_repository() && new_direction != GIT_ATTR_INDEX)
-               die("BUG: non-INDEX attr direction in a bare repo");
+               BUG("non-INDEX attr direction in a bare repo");
 
        if (new_direction != direction)
                drop_all_attr_stacks();
diff --git a/blame.c b/blame.c
index 78c9808bd1a04a4c641b0f5f853540ea7618a522..14d0e0b5751c2abaad16dac81a71436ca29ddde4 100644 (file)
--- a/blame.c
+++ b/blame.c
@@ -81,7 +81,7 @@ static void verify_working_tree_path(struct commit *work_tree, const char *path)
                unsigned mode;
 
                if (!get_tree_entry(commit_oid, path, &blob_oid, &mode) &&
-                   oid_object_info(&blob_oid, NULL) == OBJ_BLOB)
+                   oid_object_info(the_repository, &blob_oid, NULL) == OBJ_BLOB)
                        return;
        }
 
@@ -504,7 +504,7 @@ static int fill_blob_sha1_and_mode(struct blame_origin *origin)
                return 0;
        if (get_tree_entry(&origin->commit->object.oid, origin->path, &origin->blob_oid, &origin->mode))
                goto error_out;
-       if (oid_object_info(&origin->blob_oid, NULL) != OBJ_BLOB)
+       if (oid_object_info(the_repository, &origin->blob_oid, NULL) != OBJ_BLOB)
                goto error_out;
        return 0;
  error_out:
@@ -551,10 +551,10 @@ static struct blame_origin *find_origin(struct commit *parent,
        diff_setup_done(&diff_opts);
 
        if (is_null_oid(&origin->commit->object.oid))
-               do_diff_cache(&parent->tree->object.oid, &diff_opts);
+               do_diff_cache(get_commit_tree_oid(parent), &diff_opts);
        else
-               diff_tree_oid(&parent->tree->object.oid,
-                             &origin->commit->tree->object.oid,
+               diff_tree_oid(get_commit_tree_oid(parent),
+                             get_commit_tree_oid(origin->commit),
                              "", &diff_opts);
        diffcore_std(&diff_opts);
 
@@ -620,10 +620,10 @@ static struct blame_origin *find_rename(struct commit *parent,
        diff_setup_done(&diff_opts);
 
        if (is_null_oid(&origin->commit->object.oid))
-               do_diff_cache(&parent->tree->object.oid, &diff_opts);
+               do_diff_cache(get_commit_tree_oid(parent), &diff_opts);
        else
-               diff_tree_oid(&parent->tree->object.oid,
-                             &origin->commit->tree->object.oid,
+               diff_tree_oid(get_commit_tree_oid(parent),
+                             get_commit_tree_oid(origin->commit),
                              "", &diff_opts);
        diffcore_std(&diff_opts);
 
@@ -1255,10 +1255,10 @@ static void find_copy_in_parent(struct blame_scoreboard *sb,
                diff_opts.flags.find_copies_harder = 1;
 
        if (is_null_oid(&target->commit->object.oid))
-               do_diff_cache(&parent->tree->object.oid, &diff_opts);
+               do_diff_cache(get_commit_tree_oid(parent), &diff_opts);
        else
-               diff_tree_oid(&parent->tree->object.oid,
-                             &target->commit->tree->object.oid,
+               diff_tree_oid(get_commit_tree_oid(parent),
+                             get_commit_tree_oid(target->commit),
                              "", &diff_opts);
 
        if (!diff_opts.flags.find_copies_harder)
@@ -1806,7 +1806,7 @@ void setup_scoreboard(struct blame_scoreboard *sb, const char *path, struct blam
                        l->item = c;
                        if (add_decoration(&sb->revs->children,
                                           &c->parents->item->object, l))
-                               die("BUG: not unique item in first-parent chain");
+                               BUG("not unique item in first-parent chain");
                        c = c->parents->item;
                }
 
index c9e2619a9ad8febc10e4132c88e7a74c6c9bbeaf..8a155dd41eccd8004a55b1302111293ae59ea542 100644 (file)
@@ -265,8 +265,6 @@ static int edit_patch(int argc, const char **argv, const char *prefix)
        return 0;
 }
 
-static struct lock_file lock_file;
-
 static const char ignore_error[] =
 N_("The following paths are ignored by one of your .gitignore files:\n");
 
@@ -393,6 +391,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
        int add_new_files;
        int require_pathspec;
        char *seen = NULL;
+       struct lock_file lock_file = LOCK_INIT;
 
        git_config(add_config, NULL);
 
index d834f9e62b6a0f5550909b5a0bb43cce0e48e8ec..2fc2d1e82c5ead598476964329b555e38de9a495 100644 (file)
@@ -403,11 +403,11 @@ 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");
+               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");
+               BUG("state file 'last' does not exist");
        state->last = strtol(sb.buf, NULL, 10);
 
        if (read_author_script(state) < 0)
@@ -986,7 +986,7 @@ static int split_mail(struct am_state *state, enum patch_format patch_format,
        case PATCH_FORMAT_MBOXRD:
                return split_mail_mbox(state, paths, keep_cr, 1);
        default:
-               die("BUG: invalid patch_format");
+               BUG("invalid patch_format");
        }
        return -1;
 }
@@ -1041,7 +1041,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
                str = "b";
                break;
        default:
-               die("BUG: invalid value for state->keep");
+               BUG("invalid value for state->keep");
        }
 
        write_state_text(state, "keep", str);
@@ -1058,7 +1058,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
                str = "t";
                break;
        default:
-               die("BUG: invalid value for state->scissors");
+               BUG("invalid value for state->scissors");
        }
        write_state_text(state, "scissors", str);
 
@@ -1216,7 +1216,7 @@ static int parse_mail(struct am_state *state, const char *mail)
                mi.keep_non_patch_brackets_in_subject = 1;
                break;
        default:
-               die("BUG: invalid value for state->keep");
+               BUG("invalid value for state->keep");
        }
 
        if (state->message_id)
@@ -1232,7 +1232,7 @@ static int parse_mail(struct am_state *state, const char *mail)
                mi.use_scissors = 1;
                break;
        default:
-               die("BUG: invalid value for state->scissors");
+               BUG("invalid value for state->scissors");
        }
 
        mi.input = xfopen(mail, "r");
@@ -1463,7 +1463,7 @@ static int run_apply(const struct am_state *state, const char *index_file)
        int options = 0;
 
        if (init_apply_state(&apply_state, NULL))
-               die("BUG: init_apply_state() failed");
+               BUG("init_apply_state() failed");
 
        argv_array_push(&apply_opts, "apply");
        argv_array_pushv(&apply_opts, state->git_apply_opts.argv);
@@ -1489,7 +1489,7 @@ static int run_apply(const struct am_state *state, const char *index_file)
                apply_state.apply_verbosity = verbosity_silent;
 
        if (check_apply_state(&apply_state, force_apply))
-               die("BUG: check_apply_state() failed");
+               BUG("check_apply_state() failed");
 
        argv_array_push(&apply_paths, am_path(state, "patch"));
 
@@ -1542,7 +1542,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
        char *their_tree_name;
 
        if (get_oid("HEAD", &our_tree) < 0)
-               hashcpy(our_tree.hash, EMPTY_TREE_SHA1_BIN);
+               oidcpy(&our_tree, the_hash_algo->empty_tree);
 
        if (build_fake_ancestor(state, index_path))
                return error("could not build fake ancestor");
@@ -2042,7 +2042,7 @@ static void am_skip(struct am_state *state)
        am_rerere_clear();
 
        if (get_oid("HEAD", &head))
-               hashcpy(head.hash, EMPTY_TREE_SHA1_BIN);
+               oidcpy(&head, the_hash_algo->empty_tree);
 
        if (clean_index(&head, &head))
                die(_("failed to clean index"));
@@ -2105,11 +2105,11 @@ static void am_abort(struct am_state *state)
        curr_branch = resolve_refdup("HEAD", 0, &curr_head, NULL);
        has_curr_head = curr_branch && !is_null_oid(&curr_head);
        if (!has_curr_head)
-               hashcpy(curr_head.hash, EMPTY_TREE_SHA1_BIN);
+               oidcpy(&curr_head, the_hash_algo->empty_tree);
 
        has_orig_head = !get_oid("ORIG_HEAD", &orig_head);
        if (!has_orig_head)
-               hashcpy(orig_head.hash, EMPTY_TREE_SHA1_BIN);
+               oidcpy(&orig_head, the_hash_algo->empty_tree);
 
        clean_index(&curr_head, &orig_head);
 
@@ -2231,12 +2231,12 @@ int cmd_am(int argc, const char **argv, const char *prefix)
                        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_SET_INT_F(0, "keep-cr", &keep_cr,
+                       N_("pass --keep-cr flag to git-mailsplit for mbox format"),
+                       1, PARSE_OPT_NONEG),
+               OPT_SET_INT_F(0, "no-keep-cr", &keep_cr,
+                       N_("do not pass --keep-cr flag to git-mailsplit independent of am.keepcr"),
+                       0, PARSE_OPT_NONEG),
                OPT_BOOL('c', "scissors", &state.scissors,
                        N_("strip everything before a scissors line")),
                OPT_PASSTHRU_ARGV(0, "whitespace", &state.git_apply_opts, N_("action"),
@@ -2407,7 +2407,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)
                ret = show_patch(&state);
                break;
        default:
-               die("BUG: invalid resume value");
+               BUG("invalid resume value");
        }
 
        am_state_release(&state);
index db38c0b307c5719ab3bd5e6b8597ec8810c0de3d..4202584f97922f39f8631e23b01981ed5687b793 100644 (file)
@@ -7,6 +7,7 @@
 
 #include "cache.h"
 #include "config.h"
+#include "color.h"
 #include "builtin.h"
 #include "commit.h"
 #include "diff.h"
@@ -23,6 +24,7 @@
 #include "dir.h"
 #include "progress.h"
 #include "blame.h"
+#include "string-list.h"
 
 static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>");
 
@@ -46,6 +48,8 @@ static int xdl_opts;
 static int abbrev = -1;
 static int no_whole_file_rename;
 static int show_progress;
+static char repeated_meta_color[COLOR_MAXLEN];
+static int coloring_mode;
 
 static struct date_mode blame_date_mode = { DATE_ISO8601 };
 static size_t blame_date_width;
@@ -316,10 +320,12 @@ static const char *format_time(timestamp_t time, const char *tz_str,
 #define OUTPUT_PORCELAIN       010
 #define OUTPUT_SHOW_NAME       020
 #define OUTPUT_SHOW_NUMBER     040
-#define OUTPUT_SHOW_SCORE      0100
-#define OUTPUT_NO_AUTHOR       0200
+#define OUTPUT_SHOW_SCORE      0100
+#define OUTPUT_NO_AUTHOR       0200
 #define OUTPUT_SHOW_EMAIL      0400
-#define OUTPUT_LINE_PORCELAIN 01000
+#define OUTPUT_LINE_PORCELAIN  01000
+#define OUTPUT_COLOR_LINE      02000
+#define OUTPUT_SHOW_AGE_WITH_COLOR     04000
 
 static void emit_porcelain_details(struct blame_origin *suspect, int repeat)
 {
@@ -367,6 +373,63 @@ static void emit_porcelain(struct blame_scoreboard *sb, struct blame_entry *ent,
                putchar('\n');
 }
 
+static struct color_field {
+       timestamp_t hop;
+       char col[COLOR_MAXLEN];
+} *colorfield;
+static int colorfield_nr, colorfield_alloc;
+
+static void parse_color_fields(const char *s)
+{
+       struct string_list l = STRING_LIST_INIT_DUP;
+       struct string_list_item *item;
+       enum { EXPECT_DATE, EXPECT_COLOR } next = EXPECT_COLOR;
+
+       colorfield_nr = 0;
+
+       /* Ideally this would be stripped and split at the same time? */
+       string_list_split(&l, s, ',', -1);
+       ALLOC_GROW(colorfield, colorfield_nr + 1, colorfield_alloc);
+
+       for_each_string_list_item(item, &l) {
+               switch (next) {
+               case EXPECT_DATE:
+                       colorfield[colorfield_nr].hop = approxidate(item->string);
+                       next = EXPECT_COLOR;
+                       colorfield_nr++;
+                       ALLOC_GROW(colorfield, colorfield_nr + 1, colorfield_alloc);
+                       break;
+               case EXPECT_COLOR:
+                       if (color_parse(item->string, colorfield[colorfield_nr].col))
+                               die(_("expecting a color: %s"), item->string);
+                       next = EXPECT_DATE;
+                       break;
+               }
+       }
+
+       if (next == EXPECT_COLOR)
+               die (_("must end with a color"));
+
+       colorfield[colorfield_nr].hop = TIME_MAX;
+}
+
+static void setup_default_color_by_age(void)
+{
+       parse_color_fields("blue,12 month ago,white,1 month ago,red");
+}
+
+static void determine_line_heat(struct blame_entry *ent, const char **dest_color)
+{
+       int i = 0;
+       struct commit_info ci;
+       get_commit_info(ent->suspect->commit, &ci, 1);
+
+       while (i < colorfield_nr && ci.author_time > colorfield[i].hop)
+               i++;
+
+       *dest_color = colorfield[i].col;
+}
+
 static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int opt)
 {
        int cnt;
@@ -375,15 +438,35 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int
        struct commit_info ci;
        char hex[GIT_MAX_HEXSZ + 1];
        int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP);
+       const char *default_color = NULL, *color = NULL, *reset = NULL;
 
        get_commit_info(suspect->commit, &ci, 1);
        oid_to_hex_r(hex, &suspect->commit->object.oid);
 
        cp = blame_nth_line(sb, ent->lno);
+
+       if (opt & OUTPUT_SHOW_AGE_WITH_COLOR) {
+               determine_line_heat(ent, &default_color);
+               color = default_color;
+               reset = GIT_COLOR_RESET;
+       }
+
        for (cnt = 0; cnt < ent->num_lines; cnt++) {
                char ch;
                int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? GIT_SHA1_HEXSZ : abbrev;
 
+               if (opt & OUTPUT_COLOR_LINE) {
+                       if (cnt > 0) {
+                               color = repeated_meta_color;
+                               reset = GIT_COLOR_RESET;
+                       } else  {
+                               color = default_color ? default_color : NULL;
+                               reset = default_color ? GIT_COLOR_RESET : NULL;
+                       }
+               }
+               if (color)
+                       fputs(color, stdout);
+
                if (suspect->commit->object.flags & UNINTERESTING) {
                        if (blank_boundary)
                                memset(hex, ' ', length);
@@ -433,6 +516,8 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int
                        printf(" %*d) ",
                               max_digits, ent->lno + 1 + cnt);
                }
+               if (reset)
+                       fputs(reset, stdout);
                do {
                        ch = *cp++;
                        putchar(ch);
@@ -607,6 +692,30 @@ static int git_blame_config(const char *var, const char *value, void *cb)
                parse_date_format(value, &blame_date_mode);
                return 0;
        }
+       if (!strcmp(var, "color.blame.repeatedlines")) {
+               if (color_parse_mem(value, strlen(value), repeated_meta_color))
+                       warning(_("invalid color '%s' in color.blame.repeatedLines"),
+                               value);
+               return 0;
+       }
+       if (!strcmp(var, "color.blame.highlightrecent")) {
+               parse_color_fields(value);
+               return 0;
+       }
+
+       if (!strcmp(var, "blame.coloring")) {
+               if (!strcmp(value, "repeatedLines")) {
+                       coloring_mode |= OUTPUT_COLOR_LINE;
+               } else if (!strcmp(value, "highlightRecent")) {
+                       coloring_mode |= OUTPUT_SHOW_AGE_WITH_COLOR;
+               } else if (!strcmp(value, "none")) {
+                       coloring_mode &= ~(OUTPUT_COLOR_LINE |
+                                           OUTPUT_SHOW_AGE_WITH_COLOR);
+               } else {
+                       warning(_("invalid value for blame.coloring"));
+                       return 0;
+               }
+       }
 
        if (git_diff_heuristic_config(var, value, cb) < 0)
                return -1;
@@ -655,7 +764,7 @@ static int is_a_rev(const char *name)
 
        if (get_oid(name, &oid))
                return 0;
-       return OBJ_NONE < oid_object_info(&oid, NULL);
+       return OBJ_NONE < oid_object_info(the_repository, &oid, NULL);
 }
 
 int cmd_blame(int argc, const char **argv, const char *prefix)
@@ -690,6 +799,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                OPT_BIT('s', NULL, &output_option, N_("Suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR),
                OPT_BIT('e', "show-email", &output_option, N_("Show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL),
                OPT_BIT('w', NULL, &xdl_opts, N_("Ignore whitespace differences"), XDF_IGNORE_WHITESPACE),
+               OPT_BIT(0, "color-lines", &output_option, N_("color redundant metadata from previous line differently"), OUTPUT_COLOR_LINE),
+               OPT_BIT(0, "color-by-age", &output_option, N_("color lines by age"), OUTPUT_SHOW_AGE_WITH_COLOR),
 
                /*
                 * The following two options are parsed by parse_revision_opt()
@@ -714,6 +825,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        unsigned int range_i;
        long anchor;
 
+       setup_default_color_by_age();
        git_config(git_blame_config, &output_option);
        init_revisions(&revs, NULL);
        revs.date_mode = blame_date_mode;
@@ -949,8 +1061,17 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
 
        blame_coalesce(&sb);
 
-       if (!(output_option & OUTPUT_PORCELAIN))
+       if (!(output_option & (OUTPUT_COLOR_LINE | OUTPUT_SHOW_AGE_WITH_COLOR)))
+               output_option |= coloring_mode;
+
+       if (!(output_option & OUTPUT_PORCELAIN)) {
                find_alignment(&sb, &output_option);
+               if (!*repeated_meta_color &&
+                   (output_option & OUTPUT_COLOR_LINE))
+                       strcpy(repeated_meta_color, GIT_COLOR_CYAN);
+       }
+       if (output_option & OUTPUT_ANNOTATE_COMPAT)
+               output_option &= ~(OUTPUT_COLOR_LINE | OUTPUT_SHOW_AGE_WITH_COLOR);
 
        output(&sb, output_option);
        free((void *)sb.final_buf);
index efc9ac1922c8c45e13cddb82a342885153ae0fba..d53f6e2ad4c30ef02262ff3fa01ecdb54be96f11 100644 (file)
@@ -500,7 +500,7 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
 
        if (!skip_prefix(oldref.buf, "refs/heads/", &interpreted_oldname) ||
            !skip_prefix(newref.buf, "refs/heads/", &interpreted_newname)) {
-               die("BUG: expected prefix missing for refs");
+               BUG("expected prefix missing for refs");
        }
 
        if (copy)
@@ -592,8 +592,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT__QUIET(&quiet, N_("suppress informational messages")),
                OPT_SET_INT('t', "track",  &track, N_("set up tracking mode (see git-pull(1))"),
                        BRANCH_TRACK_EXPLICIT),
-               { OPTION_SET_INT, 0, "set-upstream", &track, NULL, N_("do not use"),
-                       PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, BRANCH_TRACK_OVERRIDE },
+               OPT_SET_INT_F(0, "set-upstream", &track, N_("do not use"),
+                       BRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),
                OPT_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")),
                OPT_BOOL(0, "unset-upstream", &unset_upstream, N_("Unset the upstream info")),
                OPT__COLOR(&branch_use_color, N_("use colored output")),
index 2c46d257cd9a09a8f8ff05820b53224394385c7d..665b5819499b4d56a4f49996125cac919e477309 100644 (file)
@@ -77,7 +77,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
        switch (opt) {
        case 't':
                oi.type_name = &sb;
-               if (oid_object_info_extended(&oid, &oi, flags) < 0)
+               if (oid_object_info_extended(the_repository, &oid, &oi, flags) < 0)
                        die("git cat-file: could not get object info");
                if (sb.len) {
                        printf("%s\n", sb.buf);
@@ -88,7 +88,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
 
        case 's':
                oi.sizep = &size;
-               if (oid_object_info_extended(&oid, &oi, flags) < 0)
+               if (oid_object_info_extended(the_repository, &oid, &oi, flags) < 0)
                        die("git cat-file: could not get object info");
                printf("%lu\n", size);
                return 0;
@@ -116,7 +116,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
                /* else fallthrough */
 
        case 'p':
-               type = oid_object_info(&oid, NULL);
+               type = oid_object_info(the_repository, &oid, NULL);
                if (type < 0)
                        die("Not a valid object name %s", obj_name);
 
@@ -140,7 +140,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
        case 0:
                if (type_from_string(exp_type) == OBJ_BLOB) {
                        struct object_id blob_oid;
-                       if (oid_object_info(&oid, NULL) == OBJ_TAG) {
+                       if (oid_object_info(the_repository, &oid, NULL) == OBJ_TAG) {
                                char *buffer = read_object_file(&oid, &type,
                                                                &size);
                                const char *target;
@@ -151,7 +151,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
                        } else
                                oidcpy(&blob_oid, &oid);
 
-                       if (oid_object_info(&blob_oid, NULL) == OBJ_BLOB)
+                       if (oid_object_info(the_repository, &blob_oid, NULL) == OBJ_BLOB)
                                return stream_blob_to_fd(1, &blob_oid, NULL, 0);
                        /*
                         * we attempted to dereference a tag to a blob
@@ -312,7 +312,7 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d
                                        die("could not convert '%s' %s",
                                            oid_to_hex(oid), data->rest);
                        } else
-                               die("BUG: invalid cmdmode: %c", opt->cmdmode);
+                               BUG("invalid cmdmode: %c", opt->cmdmode);
                        batch_write(opt, contents, size);
                        free(contents);
                } else if (stream_blob_to_fd(1, oid, NULL, 0) < 0)
@@ -342,7 +342,7 @@ static void batch_object_write(const char *obj_name, struct batch_options *opt,
        struct strbuf buf = STRBUF_INIT;
 
        if (!data->skip_object_info &&
-           oid_object_info_extended(&data->oid, &data->info,
+           oid_object_info_extended(the_repository, &data->oid, &data->info,
                                     OBJECT_INFO_LOOKUP_REPLACE) < 0) {
                printf("%s missing\n",
                       obj_name ? obj_name : oid_to_hex(&data->oid));
@@ -387,7 +387,7 @@ static void batch_one_object(const char *obj_name, struct batch_options *opt,
                               (uintmax_t)strlen(obj_name), obj_name);
                        break;
                default:
-                       die("BUG: unknown get_sha1_with_context result %d\n",
+                       BUG("unknown get_sha1_with_context result %d\n",
                               result);
                        break;
                }
index b49b5820718335ba6a70b70ef339ece7157281cc..2e1d2376d24043a75adb9bbc69724591bcc2d5c3 100644 (file)
@@ -484,7 +484,8 @@ static int merge_working_tree(const struct checkout_opts *opts,
 
        resolve_undo_clear();
        if (opts->force) {
-               ret = reset_tree(new_branch_info->commit->tree, opts, 1, writeout_error);
+               ret = reset_tree(get_commit_tree(new_branch_info->commit),
+                                opts, 1, writeout_error);
                if (ret)
                        return ret;
        } else {
@@ -526,6 +527,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
                init_tree_desc(&trees[1], tree->buffer, tree->size);
 
                ret = unpack_trees(2, trees, &topts);
+               clear_unpack_trees_porcelain(&topts);
                if (ret == -1) {
                        /*
                         * Unpack couldn't do a trivial merge; either
@@ -570,18 +572,23 @@ static int merge_working_tree(const struct checkout_opts *opts,
                        o.verbosity = 0;
                        work = write_tree_from_memory(&o);
 
-                       ret = reset_tree(new_branch_info->commit->tree, opts, 1,
+                       ret = reset_tree(get_commit_tree(new_branch_info->commit),
+                                        opts, 1,
                                         writeout_error);
                        if (ret)
                                return ret;
                        o.ancestor = old_branch_info->name;
                        o.branch1 = new_branch_info->name;
                        o.branch2 = "local";
-                       ret = merge_trees(&o, new_branch_info->commit->tree, work,
-                               old_branch_info->commit->tree, &result);
+                       ret = merge_trees(&o,
+                                         get_commit_tree(new_branch_info->commit),
+                                         work,
+                                         get_commit_tree(old_branch_info->commit),
+                                         &result);
                        if (ret < 0)
                                exit(128);
-                       ret = reset_tree(new_branch_info->commit->tree, opts, 0,
+                       ret = reset_tree(get_commit_tree(new_branch_info->commit),
+                                        opts, 0,
                                         writeout_error);
                        strbuf_release(&o.obuf);
                        if (ret)
@@ -1002,7 +1009,7 @@ static int parse_branchname_arg(int argc, const char **argv,
                *source_tree = parse_tree_indirect(rev);
        } else {
                parse_commit_or_die(new_branch_info->commit);
-               *source_tree = new_branch_info->commit->tree;
+               *source_tree = get_commit_tree(new_branch_info->commit);
        }
 
        if (!*source_tree)                   /* case (1): want a tree */
index 8c5f4d8f074f13c62b7d6d5a3ea4deff80de2d58..99e73dae8595d37f704d0fe7f7fd84a190edfcf6 100644 (file)
@@ -824,7 +824,7 @@ static void write_refspec_config(const char *src_ref_prefix,
                        } else if (remote_head_points_at) {
                                const char *head = remote_head_points_at->name;
                                if (!skip_prefix(head, "refs/heads/", &head))
-                                       die("BUG: remote HEAD points at non-head?");
+                                       BUG("remote HEAD points at non-head?");
 
                                strbuf_addf(&value, "+%s:%s%s", remote_head_points_at->name,
                                                branch_top->buf, head);
index 0c3223d64b159580935bf24f8583a35a1ae903ff..5228ccf37a5c8f568091ebef6df86fda40aa93dc 100644 (file)
@@ -42,7 +42,6 @@ int cmd_column(int argc, const char **argv, const char *prefix)
                git_config(column_config, NULL);
 
        memset(&copts, 0, sizeof(copts));
-       copts.width = term_columns();
        copts.padding = 1;
        argc = parse_options(argc, argv, "", options, builtin_column_usage, 0);
        if (argc)
index 5571d4a3e2be5ee8a71b7d5dba72b782c848f15e..a842fea666a33bb2191d10cf452a255754bb44e6 100644 (file)
@@ -143,6 +143,16 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset)
        return 0;
 }
 
+static int opt_parse_rename_score(const struct option *opt, const char *arg, int unset)
+{
+       const char **value = opt->value;
+       if (arg != NULL && *arg == '=')
+               arg = arg + 1;
+
+       *value = arg;
+       return 0;
+}
+
 static void determine_whence(struct wt_status *s)
 {
        if (file_exists(git_path_merge_head()))
@@ -161,9 +171,9 @@ static void determine_whence(struct wt_status *s)
 static void status_init_config(struct wt_status *s, config_fn_t fn)
 {
        wt_status_prepare(s);
+       init_diff_ui_defaults();
        git_config(fn, s);
        determine_whence(s);
-       init_diff_ui_defaults();
        s->hints = advice_status_hints; /* must come after git_config() */
 }
 
@@ -495,7 +505,7 @@ static int is_a_merge(const struct commit *current_head)
 static void assert_split_ident(struct ident_split *id, const struct strbuf *buf)
 {
        if (split_ident_line(id, buf->buf, buf->len) || !id->date_begin)
-               die("BUG: unable to parse our own ident: %s", buf->buf);
+               BUG("unable to parse our own ident: %s", buf->buf);
 }
 
 static void export_one(const char *var, const char *s, const char *e, int hack)
@@ -1259,11 +1269,31 @@ static int git_status_config(const char *k, const char *v, void *cb)
                        return error(_("Invalid untracked files mode '%s'"), v);
                return 0;
        }
+       if (!strcmp(k, "diff.renamelimit")) {
+               if (s->rename_limit == -1)
+                       s->rename_limit = git_config_int(k, v);
+               return 0;
+       }
+       if (!strcmp(k, "status.renamelimit")) {
+               s->rename_limit = git_config_int(k, v);
+               return 0;
+       }
+       if (!strcmp(k, "diff.renames")) {
+               if (s->detect_rename == -1)
+                       s->detect_rename = git_config_rename(k, v);
+               return 0;
+       }
+       if (!strcmp(k, "status.renames")) {
+               s->detect_rename = git_config_rename(k, v);
+               return 0;
+       }
        return git_diff_ui_config(k, v, NULL);
 }
 
 int cmd_status(int argc, const char **argv, const char *prefix)
 {
+       static int no_renames = -1;
+       static const char *rename_score_arg = (const char *)-1;
        static struct wt_status s;
        int fd;
        struct object_id oid;
@@ -1297,6 +1327,10 @@ int cmd_status(int argc, const char **argv, const char *prefix)
                  N_("ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)"),
                  PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
                OPT_COLUMN(0, "column", &s.colopts, N_("list untracked files in columns")),
+               OPT_BOOL(0, "no-renames", &no_renames, N_("do not detect renames")),
+               { OPTION_CALLBACK, 'M', "find-renames", &rename_score_arg,
+                 N_("n"), N_("detect renames, optionally set similarity index"),
+                 PARSE_OPT_OPTARG, opt_parse_rename_score },
                OPT_END(),
        };
 
@@ -1336,6 +1370,14 @@ int cmd_status(int argc, const char **argv, const char *prefix)
        s.ignore_submodule_arg = ignore_submodule_arg;
        s.status_format = status_format;
        s.verbose = verbose;
+       if (no_renames != -1)
+               s.detect_rename = !no_renames;
+       if ((intptr_t)rename_score_arg != -1) {
+               if (s.detect_rename < DIFF_DETECT_RENAME)
+                       s.detect_rename = DIFF_DETECT_RENAME;
+               if (rename_score_arg)
+                       s.rename_score = parse_rename_score(&rename_score_arg);
+       }
 
        wt_status_collect(&s);
 
index 69e7270356c5a4da8372201ac80ec0d33e8909c2..b29d26dede79b9a5c46bdc4f951b22e7b88310b3 100644 (file)
@@ -398,7 +398,7 @@ static char *normalize_value(const char *key, const char *value)
                return xstrdup(value);
        }
 
-       die("BUG: cannot normalize type %d", type);
+       BUG("cannot normalize type %d", type);
 }
 
 static int get_color_found;
@@ -602,6 +602,9 @@ int cmd_config(int argc, const char **argv, const char *prefix)
        if (use_local_config && nongit)
                die(_("--local can only be used inside a git repository"));
 
+       if (given_config_source.blob && nongit)
+               die(_("--blob can only be used inside a git repository"));
+
        if (given_config_source.file &&
                        !strcmp(given_config_source.file, "-")) {
                given_config_source.file = NULL;
index b054713e1a1e7d83563df0ec82d46dbca6bdb666..d51e2ce1ec016ab35f1781675ff70793f0b974d5 100644 (file)
@@ -66,7 +66,7 @@ static int count_loose(const struct object_id *oid, const char *path, void *data
        else {
                loose_size += on_disk_bytes(st);
                loose++;
-               if (verbose && has_sha1_pack(oid->hash))
+               if (verbose && has_object_pack(oid))
                        packed_loose++;
        }
        return 0;
index b5afc4584632d35afe26f4c3d0dfb09e777518b1..cf1ae77d7c705cdfb34bdb5403bad8db4148eccf 100644 (file)
@@ -502,7 +502,7 @@ static void describe(const char *arg, int last_one)
 
        if (cmit)
                describe_commit(&oid, &sb);
-       else if (oid_object_info(&oid, NULL) == OBJ_BLOB)
+       else if (oid_object_info(the_repository, &oid, NULL) == OBJ_BLOB)
                describe_blob(oid, &sb);
        else
                die(_("%s is neither a commit nor blob"), arg);
@@ -612,7 +612,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
                                suffix = broken;
                        }
                } else if (dirty) {
-                       static struct lock_file index_lock;
+                       struct lock_file index_lock = LOCK_INIT;
                        struct rev_info revs;
                        struct argv_array args = ARGV_ARRAY_INIT;
                        int fd, result;
index 16bfb22f7381ee8e6967ab836686c5def7cff892..bfefff3a84896a79fbed42eec1121286edcc86dd 100644 (file)
@@ -398,7 +398,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
                if (!obj)
                        die(_("invalid object '%s' given."), name);
                if (obj->type == OBJ_COMMIT)
-                       obj = &((struct commit *)obj)->tree->object;
+                       obj = &get_commit_tree(((struct commit *)obj))->object;
 
                if (obj->type == OBJ_TREE) {
                        obj->flags |= flags;
index aad0e073ee61648a73843ceddffbab6047c9721b..bc97d4aef2db2d76e3ffddfc4759e52688f3974e 100644 (file)
@@ -610,7 +610,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
                        continue;
 
                if (!indices_loaded) {
-                       static struct lock_file lock;
+                       struct lock_file lock = LOCK_INIT;
                        strbuf_reset(&buf);
                        strbuf_addf(&buf, "%s/wtindex", tmpdir);
                        if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||
@@ -695,12 +695,11 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)
                         N_("use `diff.guitool` instead of `diff.tool`")),
                OPT_BOOL('d', "dir-diff", &dir_diff,
                         N_("perform a full-directory diff")),
-               { OPTION_SET_INT, 'y', "no-prompt", &prompt, NULL,
+               OPT_SET_INT_F('y', "no-prompt", &prompt,
                        N_("do not prompt before launching a diff tool"),
-                       PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},
-               { OPTION_SET_INT, 0, "prompt", &prompt, NULL, NULL,
-                       PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,
-                       NULL, 1 },
+                       0, PARSE_OPT_NONEG),
+               OPT_SET_INT_F(0, "prompt", &prompt, NULL,
+                       1, PARSE_OPT_NONEG | PARSE_OPT_HIDDEN),
                OPT_BOOL(0, "symlinks", &symlinks,
                         N_("use symlinks in dir-diff mode")),
                OPT_STRING('t', "tool", &difftool_cmd, N_("<tool>"),
index 41fe49e4d6b76d4032d5768960bcc2ec1b4399fb..6c9768742fd4faecb3719e9c87997df3d490502a 100644 (file)
@@ -156,15 +156,14 @@ static void anonymize_path(struct strbuf *out, const char *path,
        }
 }
 
-/* Since intptr_t is C99, we do not use it here */
-static inline uint32_t *mark_to_ptr(uint32_t mark)
+static inline void *mark_to_ptr(uint32_t mark)
 {
-       return ((uint32_t *)NULL) + mark;
+       return (void *)(uintptr_t)mark;
 }
 
 static inline uint32_t ptr_to_mark(void * mark)
 {
-       return (uint32_t *)mark - (uint32_t *)NULL;
+       return (uint32_t)(uintptr_t)mark;
 }
 
 static inline void mark_object(struct object *object, uint32_t mark)
@@ -517,7 +516,7 @@ static void anonymize_ident_line(const char **beg, const char **end)
        /* skip "committer", "author", "tagger", etc */
        end_of_header = strchr(*beg, ' ');
        if (!end_of_header)
-               die("BUG: malformed line fed to anonymize_ident_line: %.*s",
+               BUG("malformed line fed to anonymize_ident_line: %.*s",
                    (int)(*end - *beg), *beg);
        end_of_header++;
        strbuf_add(out, *beg, end_of_header - *beg);
@@ -578,11 +577,11 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
            get_object_mark(&commit->parents->item->object) != 0 &&
            !full_tree) {
                parse_commit_or_die(commit->parents->item);
-               diff_tree_oid(&commit->parents->item->tree->object.oid,
-                             &commit->tree->object.oid, "", &rev->diffopt);
+               diff_tree_oid(get_commit_tree_oid(commit->parents->item),
+                             get_commit_tree_oid(commit), "", &rev->diffopt);
        }
        else
-               diff_root_tree_oid(&commit->tree->object.oid,
+               diff_root_tree_oid(get_commit_tree_oid(commit),
                                   "", &rev->diffopt);
 
        /* Export the referenced blobs, and remember the marks. */
@@ -950,7 +949,7 @@ static void import_marks(char *input_file)
                if (last_idnum < mark)
                        last_idnum = mark;
 
-               type = oid_object_info(&oid, NULL);
+               type = oid_object_info(the_repository, &oid, NULL);
                if (type < 0)
                        die("object not found: %s", oid_to_hex(&oid));
 
index 7cc7a52deefd5c490103e4c154e864b211c2bd29..ea5b9669ad1f40da56a8565cada56e5991d24206 100644 (file)
@@ -62,6 +62,7 @@ static int recurse_submodules_default = RECURSE_SUBMODULES_ON_DEMAND;
 static int shown_url = 0;
 static struct refspec refmap = REFSPEC_INIT_FETCH;
 static struct list_objects_filter_options filter_options;
+static struct string_list server_options = STRING_LIST_INIT_DUP;
 
 static int git_fetch_config(const char *k, const char *v, void *cb)
 {
@@ -154,9 +155,9 @@ static struct option builtin_fetch_options[] = {
                        N_("deepen history of shallow clone, excluding rev")),
        OPT_INTEGER(0, "deepen", &deepen_relative,
                    N_("deepen history of shallow clone")),
-       { OPTION_SET_INT, 0, "unshallow", &unshallow, NULL,
-                  N_("convert to a complete repository"),
-                  PARSE_OPT_NONEG | PARSE_OPT_NOARG, NULL, 1 },
+       OPT_SET_INT_F(0, "unshallow", &unshallow,
+                     N_("convert to a complete repository"),
+                     1, PARSE_OPT_NONEG),
        { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, N_("dir"),
                   N_("prepend this to submodule path output"), PARSE_OPT_HIDDEN },
        { OPTION_CALLBACK, 0, "recurse-submodules-default",
@@ -168,6 +169,7 @@ static struct option builtin_fetch_options[] = {
                 N_("accept refs that update .git/shallow")),
        { OPTION_CALLBACK, 0, "refmap", NULL, N_("refmap"),
          N_("specify fetch refmap"), PARSE_OPT_NONEG, parse_refmap_arg },
+       OPT_STRING_LIST('o', "server-option", &server_options, N_("server-specific"), N_("option to transmit")),
        OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"),
                        TRANSPORT_FAMILY_IPV4),
        OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
@@ -646,7 +648,7 @@ static int update_local_ref(struct ref *ref,
        struct branch *current_branch = branch_get(NULL);
        const char *pretty_ref = prettify_refname(ref->name);
 
-       type = oid_object_info(&ref->new_oid, NULL);
+       type = oid_object_info(the_repository, &ref->new_oid, NULL);
        if (type < 0)
                die(_("object %s not found"), oid_to_hex(&ref->new_oid));
 
@@ -1401,6 +1403,9 @@ static int fetch_one(struct remote *remote, int argc, const char **argv, int pru
                }
        }
 
+       if (server_options.nr)
+               gtransport->server_options = &server_options;
+
        sigchain_push_common(unlock_pack_on_signal);
        atexit(unlock_pack);
        exit_code = do_fetch(gtransport, &rs);
index 087360a6757c607c4b38cce5fafad3bbb0822fc0..3ad4f160f9959a30262c81d5c3f85c36649d2895 100644 (file)
@@ -67,7 +67,8 @@ static const char *printable_type(struct object *obj)
        const char *ret;
 
        if (obj->type == OBJ_NONE) {
-               enum object_type type = oid_object_info(&obj->oid, NULL);
+               enum object_type type = oid_object_info(the_repository,
+                                                       &obj->oid, NULL);
                if (type > 0)
                        object_as_type(obj, type, 0);
        }
@@ -227,7 +228,7 @@ static void check_reachable_object(struct object *obj)
        if (!(obj->flags & HAS_OBJ)) {
                if (is_promisor_object(&obj->oid))
                        return;
-               if (has_sha1_pack(obj->oid.hash))
+               if (has_object_pack(&obj->oid))
                        return; /* it is in pack - forget about it */
                printf("missing %s %s\n", printable_type(obj),
                        describe_object(obj));
@@ -339,7 +340,7 @@ static void check_connectivity(void)
        }
 }
 
-static int fsck_obj(struct object *obj)
+static int fsck_obj(struct object *obj, void *buffer, unsigned long size)
 {
        int err;
 
@@ -353,7 +354,7 @@ static int fsck_obj(struct object *obj)
 
        if (fsck_walk(obj, NULL, &fsck_obj_options))
                objerror(obj, "broken links");
-       err = fsck_object(obj, NULL, 0, &fsck_obj_options);
+       err = fsck_object(obj, buffer, size, &fsck_obj_options);
        if (err)
                goto out;
 
@@ -398,7 +399,7 @@ static int fsck_obj_buffer(const struct object_id *oid, enum object_type type,
        }
        obj->flags &= ~(REACHABLE | SEEN);
        obj->flags |= HAS_OBJ;
-       return fsck_obj(obj);
+       return fsck_obj(obj, buffer, size);
 }
 
 static int default_refs;
@@ -506,44 +507,42 @@ static void get_default_heads(void)
        }
 }
 
-static struct object *parse_loose_object(const struct object_id *oid,
-                                        const char *path)
+static int fsck_loose(const struct object_id *oid, const char *path, void *data)
 {
        struct object *obj;
-       void *contents;
        enum object_type type;
        unsigned long size;
+       void *contents;
        int eaten;
 
-       if (read_loose_object(path, oid, &type, &size, &contents) < 0)
-               return NULL;
+       if (read_loose_object(path, oid, &type, &size, &contents) < 0) {
+               errors_found |= ERROR_OBJECT;
+               error("%s: object corrupt or missing: %s",
+                     oid_to_hex(oid), path);
+               return 0; /* keep checking other objects */
+       }
 
        if (!contents && type != OBJ_BLOB)
-               die("BUG: read_loose_object streamed a non-blob");
+               BUG("read_loose_object streamed a non-blob");
 
        obj = parse_object_buffer(oid, type, size, contents, &eaten);
-
-       if (!eaten)
-               free(contents);
-       return obj;
-}
-
-static int fsck_loose(const struct object_id *oid, const char *path, void *data)
-{
-       struct object *obj = parse_loose_object(oid, path);
-
        if (!obj) {
                errors_found |= ERROR_OBJECT;
-               error("%s: object corrupt or missing: %s",
+               error("%s: object could not be parsed: %s",
                      oid_to_hex(oid), path);
+               if (!eaten)
+                       free(contents);
                return 0; /* keep checking other objects */
        }
 
        obj->flags &= ~(REACHABLE | SEEN);
        obj->flags |= HAS_OBJ;
-       if (fsck_obj(obj))
+       if (fsck_obj(obj, contents, size))
                errors_found |= ERROR_OBJECT;
-       return 0;
+
+       if (!eaten)
+               free(contents);
+       return 0; /* keep checking other objects, even if we saw an error */
 }
 
 static int fsck_cruft(const char *basename, const char *path, void *data)
@@ -755,6 +754,9 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
                        }
                        stop_progress(&progress);
                }
+
+               if (fsck_finish(&fsck_obj_options))
+                       errors_found |= ERROR_OBJECT;
        }
 
        for (i = 0; i < argc; i++) {
index d604940bb64c8b21b02931e48e875580c454383e..ccfb1ceaeb3eb9c6a8cbe9297bceac94fa54bcac 100644 (file)
 #include "commit.h"
 #include "packfile.h"
 #include "object-store.h"
+#include "pack.h"
+#include "pack-objects.h"
+#include "blob.h"
+#include "tree.h"
 
 #define FAILED_RUN "failed to run %s"
 
@@ -41,6 +45,8 @@ static timestamp_t gc_log_expire_time;
 static const char *gc_log_expire = "1.day.ago";
 static const char *prune_expire = "2.weeks.ago";
 static const char *prune_worktrees_expire = "3.months.ago";
+static unsigned long big_pack_threshold;
+static unsigned long max_delta_cache_size = DEFAULT_DELTA_CACHE_SIZE;
 
 static struct argv_array pack_refs_cmd = ARGV_ARRAY_INIT;
 static struct argv_array reflog = ARGV_ARRAY_INIT;
@@ -128,6 +134,9 @@ static void gc_config(void)
        git_config_get_expiry("gc.worktreepruneexpire", &prune_worktrees_expire);
        git_config_get_expiry("gc.logexpiry", &gc_log_expire);
 
+       git_config_get_ulong("gc.bigpackthreshold", &big_pack_threshold);
+       git_config_get_ulong("pack.deltacachesize", &max_delta_cache_size);
+
        git_config(git_default_config, NULL);
 }
 
@@ -166,6 +175,28 @@ static int too_many_loose_objects(void)
        return needed;
 }
 
+static struct packed_git *find_base_packs(struct string_list *packs,
+                                         unsigned long limit)
+{
+       struct packed_git *p, *base = NULL;
+
+       for (p = get_packed_git(the_repository); p; p = p->next) {
+               if (!p->pack_local)
+                       continue;
+               if (limit) {
+                       if (p->pack_size >= limit)
+                               string_list_append(packs, p->pack_name);
+               } else if (!base || base->pack_size < p->pack_size) {
+                       base = p;
+               }
+       }
+
+       if (base)
+               string_list_append(packs, base->pack_name);
+
+       return base;
+}
+
 static int too_many_packs(void)
 {
        struct packed_git *p;
@@ -188,7 +219,86 @@ static int too_many_packs(void)
        return gc_auto_pack_limit < cnt;
 }
 
-static void add_repack_all_option(void)
+static uint64_t total_ram(void)
+{
+#if defined(HAVE_SYSINFO)
+       struct sysinfo si;
+
+       if (!sysinfo(&si))
+               return si.totalram;
+#elif defined(HAVE_BSD_SYSCTL) && (defined(HW_MEMSIZE) || defined(HW_PHYSMEM))
+       int64_t physical_memory;
+       int mib[2];
+       size_t length;
+
+       mib[0] = CTL_HW;
+# if defined(HW_MEMSIZE)
+       mib[1] = HW_MEMSIZE;
+# else
+       mib[1] = HW_PHYSMEM;
+# endif
+       length = sizeof(int64_t);
+       if (!sysctl(mib, 2, &physical_memory, &length, NULL, 0))
+               return physical_memory;
+#elif defined(GIT_WINDOWS_NATIVE)
+       MEMORYSTATUSEX memInfo;
+
+       memInfo.dwLength = sizeof(MEMORYSTATUSEX);
+       if (GlobalMemoryStatusEx(&memInfo))
+               return memInfo.ullTotalPhys;
+#endif
+       return 0;
+}
+
+static uint64_t estimate_repack_memory(struct packed_git *pack)
+{
+       unsigned long nr_objects = approximate_object_count();
+       size_t os_cache, heap;
+
+       if (!pack || !nr_objects)
+               return 0;
+
+       /*
+        * First we have to scan through at least one pack.
+        * Assume enough room in OS file cache to keep the entire pack
+        * or we may accidentally evict data of other processes from
+        * the cache.
+        */
+       os_cache = pack->pack_size + pack->index_size;
+       /* then pack-objects needs lots more for book keeping */
+       heap = sizeof(struct object_entry) * nr_objects;
+       /*
+        * internal rev-list --all --objects takes up some memory too,
+        * let's say half of it is for blobs
+        */
+       heap += sizeof(struct blob) * nr_objects / 2;
+       /*
+        * and the other half is for trees (commits and tags are
+        * usually insignificant)
+        */
+       heap += sizeof(struct tree) * nr_objects / 2;
+       /* and then obj_hash[], underestimated in fact */
+       heap += sizeof(struct object *) * nr_objects;
+       /* revindex is used also */
+       heap += sizeof(struct revindex_entry) * nr_objects;
+       /*
+        * read_sha1_file() (either at delta calculation phase, or
+        * writing phase) also fills up the delta base cache
+        */
+       heap += delta_base_cache_limit;
+       /* and of course pack-objects has its own delta cache */
+       heap += max_delta_cache_size;
+
+       return os_cache + heap;
+}
+
+static int keep_one_pack(struct string_list_item *item, void *data)
+{
+       argv_array_pushf(&repack, "--keep-pack=%s", basename(item->string));
+       return 0;
+}
+
+static void add_repack_all_option(struct string_list *keep_pack)
 {
        if (prune_expire && !strcmp(prune_expire, "now"))
                argv_array_push(&repack, "-a");
@@ -197,6 +307,9 @@ static void add_repack_all_option(void)
                if (prune_expire)
                        argv_array_pushf(&repack, "--unpack-unreachable=%s", prune_expire);
        }
+
+       if (keep_pack)
+               for_each_string_list(keep_pack, keep_one_pack, NULL);
 }
 
 static void add_repack_incremental_option(void)
@@ -219,9 +332,35 @@ static int need_to_gc(void)
         * we run "repack -A -d -l".  Otherwise we tell the caller
         * there is no need.
         */
-       if (too_many_packs())
-               add_repack_all_option();
-       else if (too_many_loose_objects())
+       if (too_many_packs()) {
+               struct string_list keep_pack = STRING_LIST_INIT_NODUP;
+
+               if (big_pack_threshold) {
+                       find_base_packs(&keep_pack, big_pack_threshold);
+                       if (keep_pack.nr >= gc_auto_pack_limit) {
+                               big_pack_threshold = 0;
+                               string_list_clear(&keep_pack, 0);
+                               find_base_packs(&keep_pack, 0);
+                       }
+               } else {
+                       struct packed_git *p = find_base_packs(&keep_pack, 0);
+                       uint64_t mem_have, mem_want;
+
+                       mem_have = total_ram();
+                       mem_want = estimate_repack_memory(p);
+
+                       /*
+                        * Only allow 1/2 of memory for pack-objects, leave
+                        * the rest for the OS and other processes in the
+                        * system.
+                        */
+                       if (!mem_have || mem_want < mem_have / 2)
+                               string_list_clear(&keep_pack, 0);
+               }
+
+               add_repack_all_option(&keep_pack);
+               string_list_clear(&keep_pack, 0);
+       } else if (too_many_loose_objects())
                add_repack_incremental_option();
        else
                return 0;
@@ -234,7 +373,7 @@ static int need_to_gc(void)
 /* return NULL on success, else hostname running the gc */
 static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
 {
-       static struct lock_file lock;
+       struct lock_file lock = LOCK_INIT;
        char my_host[HOST_NAME_MAX + 1];
        struct strbuf sb = STRBUF_INIT;
        struct stat st;
@@ -354,6 +493,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
        const char *name;
        pid_t pid;
        int daemonized = 0;
+       int keep_base_pack = -1;
        timestamp_t dummy;
 
        struct option builtin_gc_options[] = {
@@ -367,6 +507,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
                OPT_BOOL_F(0, "force", &force,
                           N_("force running gc even if there may be another gc running"),
                           PARSE_OPT_NOCOMPLETE),
+               OPT_BOOL(0, "keep-largest-pack", &keep_base_pack,
+                        N_("repack all other packs except the largest pack")),
                OPT_END()
        };
 
@@ -435,8 +577,19 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
                         */
                        daemonized = !daemonize();
                }
-       } else
-               add_repack_all_option();
+       } else {
+               struct string_list keep_pack = STRING_LIST_INIT_NODUP;
+
+               if (keep_base_pack != -1) {
+                       if (keep_base_pack)
+                               find_base_packs(&keep_pack, 0);
+               } else if (big_pack_threshold) {
+                       find_base_packs(&keep_pack, big_pack_threshold);
+               }
+
+               add_repack_all_option(&keep_pack);
+               string_list_clear(&keep_pack, 0);
+       }
 
        name = lock_repo_for_gc(force, &pid);
        if (name) {
index 6e7bc76785ace33f80251edfcc4feac8ad19d7c5..ee753a403eba0e9bc25b40a1efd200cafdf31e19 100644 (file)
@@ -488,7 +488,8 @@ static int grep_cache(struct grep_opt *opt, struct repository *repo,
                strbuf_addstr(&name, repo->submodule_prefix);
        }
 
-       repo_read_index(repo);
+       if (repo_read_index(repo) < 0)
+               die("index file corrupt");
 
        for (nr = 0; nr < repo->index->cache_nr; nr++) {
                const struct cache_entry *ce = repo->index->cache[nr];
@@ -885,9 +886,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                           N_("indicate hit with exit status without output")),
                OPT_BOOL(0, "all-match", &opt.all_match,
                        N_("show only matches from files that match all patterns")),
-               { OPTION_SET_INT, 0, "debug", &opt.debug, NULL,
-                 N_("show parse tree for grep expression"),
-                 PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1 },
+               OPT_SET_INT_F(0, "debug", &opt.debug,
+                             N_("show parse tree for grep expression"),
+                             1, PARSE_OPT_HIDDEN),
                OPT_GROUP(""),
                { OPTION_STRING, 'O', "open-files-in-pager", &show_in_pager,
                        N_("pager"), N_("show matching files in the pager"),
index 2d5107142926d46230d17715211338c1aa171e69..58e0a5507f10365b43eaa2698da2c8bba18b12ed 100644 (file)
@@ -9,6 +9,7 @@
 #include "run-command.h"
 #include "column.h"
 #include "help.h"
+#include "alias.h"
 
 #ifndef DEFAULT_HELP_FORMAT
 #define DEFAULT_HELP_FORMAT "man"
@@ -36,6 +37,7 @@ static const char *html_path;
 
 static int show_all = 0;
 static int show_guides = 0;
+static int verbose;
 static unsigned int colopts;
 static enum help_format help_format = HELP_FORMAT_NONE;
 static int exclude_guides;
@@ -48,6 +50,7 @@ static struct option builtin_help_options[] = {
                        HELP_FORMAT_WEB),
        OPT_SET_INT('i', "info", &help_format, N_("show info page"),
                        HELP_FORMAT_INFO),
+       OPT__VERBOSE(&verbose, N_("print command description")),
        OPT_END(),
 };
 
@@ -400,38 +403,6 @@ static void show_html_page(const char *git_cmd)
        open_html(page_path.buf);
 }
 
-static struct {
-       const char *name;
-       const char *help;
-} common_guides[] = {
-       { "attributes", N_("Defining attributes per path") },
-       { "everyday", N_("Everyday Git With 20 Commands Or So") },
-       { "glossary", N_("A Git glossary") },
-       { "ignore", N_("Specifies intentionally untracked files to ignore") },
-       { "modules", N_("Defining submodule properties") },
-       { "revisions", N_("Specifying revisions and ranges for Git") },
-       { "tutorial", N_("A tutorial introduction to Git (for version 1.5.1 or newer)") },
-       { "workflows", N_("An overview of recommended workflows with Git") },
-};
-
-static void list_common_guides_help(void)
-{
-       int i, longest = 0;
-
-       for (i = 0; i < ARRAY_SIZE(common_guides); i++) {
-               if (longest < strlen(common_guides[i].name))
-                       longest = strlen(common_guides[i].name);
-       }
-
-       puts(_("The common Git guides are:\n"));
-       for (i = 0; i < ARRAY_SIZE(common_guides); i++) {
-               printf("   %s   ", common_guides[i].name);
-               mput_char(' ', longest - strlen(common_guides[i].name));
-               puts(_(common_guides[i].help));
-       }
-       putchar('\n');
-}
-
 static const char *check_git_cmd(const char* cmd)
 {
        char *alias;
@@ -463,6 +434,11 @@ int cmd_help(int argc, const char **argv, const char *prefix)
 
        if (show_all) {
                git_config(git_help_config, NULL);
+               if (verbose) {
+                       setup_pager();
+                       list_all_cmds_help();
+                       return 0;
+               }
                printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
                load_command_list("git-", &main_cmds, &other_cmds);
                list_commands(colopts, &main_cmds, &other_cmds);
index a2cd29d8f49586d9683f6ebb83421b45d3fa824d..4ab31ed388bb860110f5bd33ddf6d707f0cfa41f 100644 (file)
@@ -223,7 +223,7 @@ static unsigned check_object(struct object *obj)
 
        if (!(obj->flags & FLAG_CHECKED)) {
                unsigned long size;
-               int type = oid_object_info(&obj->oid, &size);
+               int type = oid_object_info(the_repository, &obj->oid, &size);
                if (type <= 0)
                        die(_("did not receive expected object %s"),
                              oid_to_hex(&obj->oid));
@@ -812,7 +812,7 @@ static void sha1_object(const void *data, struct object_entry *obj_entry,
                enum object_type has_type;
                unsigned long has_size;
                read_lock();
-               has_type = oid_object_info(oid, &has_size);
+               has_type = oid_object_info(the_repository, oid, &has_size);
                if (has_type < 0)
                        die(_("cannot read existing object info %s"), oid_to_hex(oid));
                if (has_type != type || has_size != size)
@@ -837,6 +837,9 @@ static void sha1_object(const void *data, struct object_entry *obj_entry,
                                blob->object.flags |= FLAG_CHECKED;
                        else
                                die(_("invalid blob object %s"), oid_to_hex(oid));
+                       if (do_fsck_object &&
+                           fsck_object(&blob->object, (void *)data, size, &fsck_options))
+                               die(_("fsck error in packed object"));
                } else {
                        struct object *obj;
                        int eaten;
@@ -854,7 +857,7 @@ static void sha1_object(const void *data, struct object_entry *obj_entry,
                                die(_("invalid %s"), type_name(type));
                        if (do_fsck_object &&
                            fsck_object(obj, buf, size, &fsck_options))
-                               die(_("Error in object"));
+                               die(_("fsck error in packed object"));
                        if (strict && fsck_walk(obj, NULL, &fsck_options))
                                die(_("Not all child objects of %s are reachable"), oid_to_hex(&obj->oid));
 
@@ -866,7 +869,7 @@ static void sha1_object(const void *data, struct object_entry *obj_entry,
                        if (obj->type == OBJ_COMMIT) {
                                struct commit *commit = (struct commit *) obj;
                                if (detach_commit_buffer(commit, NULL) != data)
-                                       die("BUG: parse_object_buffer transmogrified our buffer");
+                                       BUG("parse_object_buffer transmogrified our buffer");
                        }
                        obj->flags |= FLAG_CHECKED;
                }
@@ -1015,7 +1018,7 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base,
 
                if (!compare_and_swap_type(&child->real_type, OBJ_REF_DELTA,
                                           base->obj->real_type))
-                       die("BUG: child->real_type != OBJ_REF_DELTA");
+                       BUG("child->real_type != OBJ_REF_DELTA");
 
                resolve_delta(child, base, result);
                if (base->ref_first == base->ref_last && base->ofs_last == -1)
@@ -1479,6 +1482,9 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
        } else
                chmod(final_index_name, 0444);
 
+       if (do_fsck_object)
+               add_packed_git(final_index_name, strlen(final_index_name), 0);
+
        if (!from_stdin) {
                printf("%s\n", sha1_to_hex(hash));
        } else {
@@ -1543,12 +1549,13 @@ static void read_v2_anomalous_offsets(struct packed_git *p,
 {
        const uint32_t *idx1, *idx2;
        uint32_t i;
+       const uint32_t hashwords = the_hash_algo->rawsz / sizeof(uint32_t);
 
        /* The address of the 4-byte offset table */
        idx1 = (((const uint32_t *)p->index_data)
                + 2 /* 8-byte header */
                + 256 /* fan out */
-               + 5 * p->num_objects /* 20-byte SHA-1 table */
+               + hashwords * p->num_objects /* object ID table */
                + p->num_objects /* CRC32 table */
                );
 
@@ -1820,6 +1827,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
                      pack_hash);
        else
                close(input_fd);
+
+       if (do_fsck_object && fsck_finish(&fsck_options))
+               die(_("fsck error in pack objects"));
+
        free(objects);
        strbuf_release(&index_name_buf);
        if (pack_name == NULL)
index 2542c5244e91b68692c7ed371fa9976997ee8ff5..4ecf90936822e64998651e4d51d1be9c921dee60 100644 (file)
@@ -117,7 +117,7 @@ static void copy_templates(const char *template_dir)
 
        dir = opendir(template_path.buf);
        if (!dir) {
-               warning(_("templates not found %s"), template_dir);
+               warning(_("templates not found in %s"), template_dir);
                goto free_return;
        }
 
@@ -391,7 +391,7 @@ int init_db(const char *git_dir, const char *real_git_dir,
                else if (get_shared_repository() == PERM_EVERYBODY)
                        xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_EVERYBODY);
                else
-                       die("BUG: invalid value for shared_repository");
+                       BUG("invalid value for shared_repository");
                git_config_set("core.sharedrepository", buf);
                git_config_set("receive.denyNonFastforwards", "true");
        }
index 71f68a3e4f59d987c653fb8f32a4d8770e9c6654..4686f68594829a85e9d73bcf0b6298c1e61b6852 100644 (file)
@@ -1019,7 +1019,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
            open_next_file(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet))
                return;
 
-       log_write_email_headers(rev, head, &pp.after_subject, &need_8bit_cte);
+       log_write_email_headers(rev, head, &pp.after_subject, &need_8bit_cte, 0);
 
        for (i = 0; !need_8bit_cte && i < nr; i++) {
                const char *buf = get_commit_buffer(list[i], NULL);
@@ -1067,8 +1067,8 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
 
        diff_setup_done(&opts);
 
-       diff_tree_oid(&origin->tree->object.oid,
-                     &head->tree->object.oid,
+       diff_tree_oid(get_commit_tree_oid(origin),
+                     get_commit_tree_oid(head),
                      "", &opts);
        diffcore_std(&opts);
        diff_flush(&opts);
@@ -1474,9 +1474,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                         N_("output all-zero hash in From header")),
                OPT_BOOL(0, "ignore-if-in-upstream", &ignore_if_in_upstream,
                         N_("don't include a patch matching a commit upstream")),
-               { OPTION_SET_INT, 'p', "no-stat", &use_patch_format, NULL,
-                 N_("show patch format instead of default (patch + stat)"),
-                 PARSE_OPT_NONEG | PARSE_OPT_NOARG, NULL, 1},
+               OPT_SET_INT_F('p', "no-stat", &use_patch_format,
+                             N_("show patch format instead of default (patch + stat)"),
+                             1, PARSE_OPT_NONEG),
                OPT_GROUP(N_("Messaging")),
                { OPTION_CALLBACK, 0, "add-header", NULL, N_("header"),
                            N_("add email header"), 0, header_callback },
index a71f6bd088a2666f0637463e1c168171dd319a96..88bb2019ad7a52a13373d4c88b127915b219e4a5 100644 (file)
@@ -166,7 +166,7 @@ static void show_killed_files(const struct index_state *istate,
                                 */
                                pos = index_name_pos(istate, ent->name, ent->len);
                                if (0 <= pos)
-                                       die("BUG: killed-file %.*s not found",
+                                       BUG("killed-file %.*s not found",
                                                ent->len, ent->name);
                                pos = -pos - 1;
                                while (pos < istate->cache_nr &&
@@ -556,9 +556,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
                { OPTION_CALLBACK, 0, "exclude-standard", &dir, NULL,
                        N_("add the standard git exclusions"),
                        PARSE_OPT_NOARG, option_parse_exclude_standard },
-               { OPTION_SET_INT, 0, "full-name", &prefix_len, NULL,
-                       N_("make the output relative to the project top directory"),
-                       PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL },
+               OPT_SET_INT_F(0, "full-name", &prefix_len,
+                             N_("make the output relative to the project top directory"),
+                             0, PARSE_OPT_NONEG),
                OPT_BOOL(0, "recurse-submodules", &recurse_submodules,
                        N_("recurse through submodules")),
                OPT_BOOL(0, "error-unmatch", &error_unmatch,
index ca3f04a839e79ecf361df5a1997da05c5d59a62d..1a25df7ee15b45df142679286afdb0e8c55647dc 100644 (file)
@@ -47,6 +47,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
        const char **pattern = NULL;
        struct argv_array ref_prefixes = ARGV_ARRAY_INIT;
        int i;
+       struct string_list server_options = STRING_LIST_INIT_DUP;
 
        struct remote *remote;
        struct transport *transport;
@@ -73,6 +74,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
                              2, PARSE_OPT_NOCOMPLETE),
                OPT_BOOL(0, "symref", &show_symref_target,
                         N_("show underlying ref in addition to the object pointed by it")),
+               OPT_STRING_LIST('o', "server-option", &server_options, N_("server-specific"), N_("option to transmit")),
                OPT_END()
        };
 
@@ -116,6 +118,8 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
        transport = transport_get(remote, NULL);
        if (uploadpack != NULL)
                transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack);
+       if (server_options.nr)
+               transport->server_options = &server_options;
 
        ref = transport_get_remote_refs(transport, &ref_prefixes);
        if (transport_disconnect(transport)) {
index d44b4f9c27d31cfa331ccef7ae538d8a6e6c38c6..409da4e8351f6d8719671d3176f0ea34fe9b756c 100644 (file)
@@ -94,7 +94,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
                        char size_text[24];
                        if (!strcmp(type, blob_type)) {
                                unsigned long size;
-                               if (oid_object_info(oid, &size) == OBJ_BAD)
+                               if (oid_object_info(the_repository, oid, &size) == OBJ_BAD)
                                        xsnprintf(size_text, sizeof(size_text),
                                                  "BAD");
                                else
index c362cfe90dbaf73d755aa54aa87237d8658ca853..b00d6f4821e7dc4067997bf515702edcfa413501 100644 (file)
@@ -35,6 +35,7 @@
 #include "string-list.h"
 #include "packfile.h"
 #include "tag.h"
+#include "alias.h"
 
 #define DEFAULT_TWOHEAD (1<<0)
 #define DEFAULT_OCTOPUS (1<<1)
@@ -214,9 +215,9 @@ static struct option builtin_merge_options[] = {
        OPT_BOOL('e', "edit", &option_edit,
                N_("edit message before committing")),
        OPT_SET_INT(0, "ff", &fast_forward, N_("allow fast-forward (default)"), FF_ALLOW),
-       { OPTION_SET_INT, 0, "ff-only", &fast_forward, NULL,
-               N_("abort if fast-forward is not possible"),
-               PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, FF_ONLY },
+       OPT_SET_INT_F(0, "ff-only", &fast_forward,
+                     N_("abort if fast-forward is not possible"),
+                     FF_ONLY, PARSE_OPT_NONEG),
        OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
        OPT_BOOL(0, "verify-signatures", &verify_signatures,
                N_("verify that the named commit has a valid GPG signature")),
@@ -281,7 +282,7 @@ static int save_state(struct object_id *stash)
        return rc;
 }
 
-static void read_empty(unsigned const char *sha1, int verbose)
+static void read_empty(const struct object_id *oid, int verbose)
 {
        int i = 0;
        const char *args[7];
@@ -291,15 +292,15 @@ static void read_empty(unsigned const char *sha1, int verbose)
                args[i++] = "-v";
        args[i++] = "-m";
        args[i++] = "-u";
-       args[i++] = EMPTY_TREE_SHA1_HEX;
-       args[i++] = sha1_to_hex(sha1);
+       args[i++] = empty_tree_oid_hex();
+       args[i++] = oid_to_hex(oid);
        args[i] = NULL;
 
        if (run_command_v_opt(args, RUN_GIT_CMD))
                die(_("read-tree failed"));
 }
 
-static void reset_hard(unsigned const char *sha1, int verbose)
+static void reset_hard(const struct object_id *oid, int verbose)
 {
        int i = 0;
        const char *args[6];
@@ -309,7 +310,7 @@ static void reset_hard(unsigned const char *sha1, int verbose)
                args[i++] = "-v";
        args[i++] = "--reset";
        args[i++] = "-u";
-       args[i++] = sha1_to_hex(sha1);
+       args[i++] = oid_to_hex(oid);
        args[i] = NULL;
 
        if (run_command_v_opt(args, RUN_GIT_CMD))
@@ -325,7 +326,7 @@ static void restore_state(const struct object_id *head,
        if (is_null_oid(stash))
                return;
 
-       reset_hard(head->hash, 1);
+       reset_hard(head, 1);
 
        args[2] = oid_to_hex(stash);
 
@@ -648,7 +649,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
                              struct commit_list *remoteheads,
                              struct commit *head)
 {
-       static struct lock_file lock;
+       struct lock_file lock = LOCK_INIT;
        const char *head_arg = "HEAD";
 
        hold_locked_index(&lock, LOCK_DIE_ON_ERROR);
@@ -806,7 +807,7 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads)
 {
        struct object_id result_tree, result_commit;
        struct commit_list *parents, **pptr = &parents;
-       static struct lock_file lock;
+       struct lock_file lock = LOCK_INIT;
 
        hold_locked_index(&lock, LOCK_DIE_ON_ERROR);
        refresh_cache(REFRESH_QUIET);
@@ -1298,7 +1299,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                if (remoteheads->next)
                        die(_("Can merge only exactly one commit into empty head"));
                remote_head_oid = &remoteheads->item->object.oid;
-               read_empty(remote_head_oid->hash, 0);
+               read_empty(remote_head_oid, 0);
                update_ref("initial pull", "HEAD", remote_head_oid, NULL, 0,
                           UPDATE_REFS_DIE_ON_ERR);
                goto done;
index 263c530315a4fe435a88f2042c768350f72e837b..bb76b469fd1f57319f3d202c1e2658d77962d698 100644 (file)
@@ -116,7 +116,7 @@ static void mktree_line(char *buf, size_t len, int nul_term_line, int allow_miss
        }
 
        /* Check the type of object identified by sha1 */
-       obj_type = oid_object_info(&oid, NULL);
+       obj_type = oid_object_info(the_repository, &oid, NULL);
        if (obj_type < 0) {
                if (allow_missing) {
                        ; /* no problem - missing objects are presumed to be of the right type */
index 7a63667d64810c1164cf3acad3cfcc6cedf4010d..80bb967a63f154eb44ebc1a6dd7f833d01dce83e 100644 (file)
@@ -72,7 +72,6 @@ static const char *add_slash(const char *path)
        return path;
 }
 
-static struct lock_file lock_file;
 #define SUBMODULE_WITH_GITDIR ((const char *)1)
 
 static void prepare_move_submodule(const char *src, int first,
@@ -131,6 +130,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
        enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes;
        struct stat st;
        struct string_list src_for_dst = STRING_LIST_INIT_NODUP;
+       struct lock_file lock_file = LOCK_INIT;
 
        git_config(git_default_config, NULL);
 
index e5bf80eef1d4113ac9c5361f200bb7b162de18a6..6981e2d906f43b06cac5bd11cb0d627571be09d5 100644 (file)
@@ -461,7 +461,7 @@ static int add(int argc, const char **argv, const char *prefix)
        if (d.buf.len || allow_empty) {
                write_note_data(&d, &new_note);
                if (add_note(t, &object, &new_note, combine_notes_overwrite))
-                       die("BUG: combine_notes_overwrite failed");
+                       BUG("combine_notes_overwrite failed");
                commit_notes(t, "Notes added by 'git notes add'");
        } else {
                fprintf(stderr, _("Removing note for object %s\n"),
@@ -544,7 +544,7 @@ static int copy(int argc, const char **argv, const char *prefix)
        }
 
        if (add_note(t, &object, from_note, combine_notes_overwrite))
-               die("BUG: combine_notes_overwrite failed");
+               BUG("combine_notes_overwrite failed");
        commit_notes(t, "Notes added by 'git notes copy'");
 out:
        free_notes(t);
@@ -621,7 +621,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
        if (d.buf.len || allow_empty) {
                write_note_data(&d, &new_note);
                if (add_note(t, &object, &new_note, combine_notes_overwrite))
-                       die("BUG: combine_notes_overwrite failed");
+                       BUG("combine_notes_overwrite failed");
                logmsg = xstrfmt("Notes added by 'git notes %s'", argv[0]);
        } else {
                fprintf(stderr, _("Removing note for object %s\n"),
@@ -778,13 +778,13 @@ static int merge(int argc, const char **argv, const char *prefix)
                           N_("resolve notes conflicts using the given strategy "
                              "(manual/ours/theirs/union/cat_sort_uniq)")),
                OPT_GROUP(N_("Committing unmerged notes")),
-               { OPTION_SET_INT, 0, "commit", &do_commit, NULL,
-                       N_("finalize notes merge by committing unmerged notes"),
-                       PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1},
+               OPT_SET_INT_F(0, "commit", &do_commit,
+                             N_("finalize notes merge by committing unmerged notes"),
+                             1, PARSE_OPT_NONEG),
                OPT_GROUP(N_("Aborting notes merge resolution")),
-               { OPTION_SET_INT, 0, "abort", &do_abort, NULL,
-                       N_("abort notes merge"),
-                       PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1},
+               OPT_SET_INT_F(0, "abort", &do_abort,
+                             N_("abort notes merge"),
+                             1, PARSE_OPT_NONEG),
                OPT_END()
        };
 
@@ -831,7 +831,7 @@ static int merge(int argc, const char **argv, const char *prefix)
                const char *short_ref = NULL;
 
                if (!skip_prefix(o.local_ref, "refs/notes/", &short_ref))
-                       die("BUG: local ref %s is outside of refs/notes/",
+                       BUG("local ref %s is outside of refs/notes/",
                            o.local_ref);
 
                strbuf_addf(&merge_key, "notes.%s.mergeStrategy", short_ref);
index 24b1c6c5dd73e74815753e534a8476b9c42d7f0b..71056d8294d7f4bcfdcbc9bb292a5d17faf49252 100644 (file)
 #include "list.h"
 #include "packfile.h"
 #include "object-store.h"
+#include "dir.h"
+
+#define IN_PACK(obj) oe_in_pack(&to_pack, obj)
+#define SIZE(obj) oe_size(&to_pack, obj)
+#define SET_SIZE(obj,size) oe_set_size(&to_pack, obj, size)
+#define DELTA_SIZE(obj) oe_delta_size(&to_pack, obj)
+#define DELTA(obj) oe_delta(&to_pack, obj)
+#define DELTA_CHILD(obj) oe_delta_child(&to_pack, obj)
+#define DELTA_SIBLING(obj) oe_delta_sibling(&to_pack, obj)
+#define SET_DELTA(obj, val) oe_set_delta(&to_pack, obj, val)
+#define SET_DELTA_SIZE(obj, val) oe_set_delta_size(&to_pack, obj, val)
+#define SET_DELTA_CHILD(obj, val) oe_set_delta_child(&to_pack, obj, val)
+#define SET_DELTA_SIBLING(obj, val) oe_set_delta_sibling(&to_pack, obj, val)
 
 static const char *pack_usage[] = {
        N_("git pack-objects --stdout [<options>...] [< <ref-list> | < <object-list>]"),
@@ -45,7 +58,7 @@ static const char *pack_usage[] = {
 static struct packing_data to_pack;
 
 static struct pack_idx_entry **written_list;
-static uint32_t nr_result, nr_written;
+static uint32_t nr_result, nr_written, nr_seen;
 
 static int non_empty;
 static int reuse_delta = 1, reuse_object = 1;
@@ -55,7 +68,8 @@ static int pack_loose_unreachable;
 static int local;
 static int have_non_local_packs;
 static int incremental;
-static int ignore_packed_keep;
+static int ignore_packed_keep_on_disk;
+static int ignore_packed_keep_in_core;
 static int allow_ofs_delta;
 static struct pack_idx_option pack_idx_opts;
 static const char *base_name;
@@ -80,7 +94,7 @@ static uint16_t write_bitmap_options;
 static int exclude_promisor_objects;
 
 static unsigned long delta_cache_size = 0;
-static unsigned long max_delta_cache_size = 256 * 1024 * 1024;
+static unsigned long max_delta_cache_size = DEFAULT_DELTA_CACHE_SIZE;
 static unsigned long cache_max_small_delta_size = 1000;
 
 static unsigned long window_memory_limit = 0;
@@ -127,13 +141,14 @@ static void *get_delta(struct object_entry *entry)
        buf = read_object_file(&entry->idx.oid, &type, &size);
        if (!buf)
                die("unable to read %s", oid_to_hex(&entry->idx.oid));
-       base_buf = read_object_file(&entry->delta->idx.oid, &type, &base_size);
+       base_buf = read_object_file(&DELTA(entry)->idx.oid, &type,
+                                   &base_size);
        if (!base_buf)
                die("unable to read %s",
-                   oid_to_hex(&entry->delta->idx.oid));
+                   oid_to_hex(&DELTA(entry)->idx.oid));
        delta_buf = diff_delta(base_buf, base_size,
                               buf, size, &delta_size, 0);
-       if (!delta_buf || delta_size != entry->delta_size)
+       if (!delta_buf || delta_size != DELTA_SIZE(entry))
                die("delta size changed");
        free(buf);
        free(base_buf);
@@ -264,10 +279,11 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent
        enum object_type type;
        void *buf;
        struct git_istream *st = NULL;
+       const unsigned hashsz = the_hash_algo->rawsz;
 
        if (!usable_delta) {
-               if (entry->type == OBJ_BLOB &&
-                   entry->size > big_file_threshold &&
+               if (oe_type(entry) == OBJ_BLOB &&
+                   oe_size_greater_than(&to_pack, entry, big_file_threshold) &&
                    (st = open_istream(&entry->idx.oid, &type, &size, NULL)) != NULL)
                        buf = NULL;
                else {
@@ -283,15 +299,15 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent
                FREE_AND_NULL(entry->delta_data);
                entry->z_delta_size = 0;
        } else if (entry->delta_data) {
-               size = entry->delta_size;
+               size = DELTA_SIZE(entry);
                buf = entry->delta_data;
                entry->delta_data = NULL;
-               type = (allow_ofs_delta && entry->delta->idx.offset) ?
+               type = (allow_ofs_delta && DELTA(entry)->idx.offset) ?
                        OBJ_OFS_DELTA : OBJ_REF_DELTA;
        } else {
                buf = get_delta(entry);
-               size = entry->delta_size;
-               type = (allow_ofs_delta && entry->delta->idx.offset) ?
+               size = DELTA_SIZE(entry);
+               type = (allow_ofs_delta && DELTA(entry)->idx.offset) ?
                        OBJ_OFS_DELTA : OBJ_REF_DELTA;
        }
 
@@ -315,12 +331,12 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent
                 * encoding of the relative offset for the delta
                 * base from this object's position in the pack.
                 */
-               off_t ofs = entry->idx.offset - entry->delta->idx.offset;
+               off_t ofs = entry->idx.offset - DELTA(entry)->idx.offset;
                unsigned pos = sizeof(dheader) - 1;
                dheader[pos] = ofs & 127;
                while (ofs >>= 7)
                        dheader[--pos] = 128 | (--ofs & 127);
-               if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) {
+               if (limit && hdrlen + sizeof(dheader) - pos + datalen + hashsz >= limit) {
                        if (st)
                                close_istream(st);
                        free(buf);
@@ -332,19 +348,19 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent
        } else if (type == OBJ_REF_DELTA) {
                /*
                 * Deltas with a base reference contain
-                * an additional 20 bytes for the base sha1.
+                * additional bytes for the base object ID.
                 */
-               if (limit && hdrlen + 20 + datalen + 20 >= limit) {
+               if (limit && hdrlen + hashsz + datalen + hashsz >= limit) {
                        if (st)
                                close_istream(st);
                        free(buf);
                        return 0;
                }
                hashwrite(f, header, hdrlen);
-               hashwrite(f, entry->delta->idx.oid.hash, 20);
-               hdrlen += 20;
+               hashwrite(f, DELTA(entry)->idx.oid.hash, hashsz);
+               hdrlen += hashsz;
        } else {
-               if (limit && hdrlen + datalen + 20 >= limit) {
+               if (limit && hdrlen + datalen + hashsz >= limit) {
                        if (st)
                                close_istream(st);
                        free(buf);
@@ -367,21 +383,23 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent
 static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,
                                unsigned long limit, int usable_delta)
 {
-       struct packed_git *p = entry->in_pack;
+       struct packed_git *p = IN_PACK(entry);
        struct pack_window *w_curs = NULL;
        struct revindex_entry *revidx;
        off_t offset;
-       enum object_type type = entry->type;
+       enum object_type type = oe_type(entry);
        off_t datalen;
        unsigned char header[MAX_PACK_OBJECT_HEADER],
                      dheader[MAX_PACK_OBJECT_HEADER];
        unsigned hdrlen;
+       const unsigned hashsz = the_hash_algo->rawsz;
+       unsigned long entry_size = SIZE(entry);
 
-       if (entry->delta)
-               type = (allow_ofs_delta && entry->delta->idx.offset) ?
+       if (DELTA(entry))
+               type = (allow_ofs_delta && DELTA(entry)->idx.offset) ?
                        OBJ_OFS_DELTA : OBJ_REF_DELTA;
        hdrlen = encode_in_pack_object_header(header, sizeof(header),
-                                             type, entry->size);
+                                             type, entry_size);
 
        offset = entry->in_pack_offset;
        revidx = find_pack_revindex(p, offset);
@@ -398,7 +416,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,
        datalen -= entry->in_pack_header_size;
 
        if (!pack_to_stdout && p->index_version == 1 &&
-           check_pack_inflate(p, &w_curs, offset, datalen, entry->size)) {
+           check_pack_inflate(p, &w_curs, offset, datalen, entry_size)) {
                error("corrupt packed object for %s",
                      oid_to_hex(&entry->idx.oid));
                unuse_pack(&w_curs);
@@ -406,12 +424,12 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,
        }
 
        if (type == OBJ_OFS_DELTA) {
-               off_t ofs = entry->idx.offset - entry->delta->idx.offset;
+               off_t ofs = entry->idx.offset - DELTA(entry)->idx.offset;
                unsigned pos = sizeof(dheader) - 1;
                dheader[pos] = ofs & 127;
                while (ofs >>= 7)
                        dheader[--pos] = 128 | (--ofs & 127);
-               if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) {
+               if (limit && hdrlen + sizeof(dheader) - pos + datalen + hashsz >= limit) {
                        unuse_pack(&w_curs);
                        return 0;
                }
@@ -420,16 +438,16 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,
                hdrlen += sizeof(dheader) - pos;
                reused_delta++;
        } else if (type == OBJ_REF_DELTA) {
-               if (limit && hdrlen + 20 + datalen + 20 >= limit) {
+               if (limit && hdrlen + hashsz + datalen + hashsz >= limit) {
                        unuse_pack(&w_curs);
                        return 0;
                }
                hashwrite(f, header, hdrlen);
-               hashwrite(f, entry->delta->idx.oid.hash, 20);
-               hdrlen += 20;
+               hashwrite(f, DELTA(entry)->idx.oid.hash, hashsz);
+               hdrlen += hashsz;
                reused_delta++;
        } else {
-               if (limit && hdrlen + datalen + 20 >= limit) {
+               if (limit && hdrlen + datalen + hashsz >= limit) {
                        unuse_pack(&w_curs);
                        return 0;
                }
@@ -465,28 +483,29 @@ static off_t write_object(struct hashfile *f,
        else
                limit = pack_size_limit - write_offset;
 
-       if (!entry->delta)
+       if (!DELTA(entry))
                usable_delta = 0;       /* no delta */
        else if (!pack_size_limit)
               usable_delta = 1;        /* unlimited packfile */
-       else if (entry->delta->idx.offset == (off_t)-1)
+       else if (DELTA(entry)->idx.offset == (off_t)-1)
                usable_delta = 0;       /* base was written to another pack */
-       else if (entry->delta->idx.offset)
+       else if (DELTA(entry)->idx.offset)
                usable_delta = 1;       /* base already exists in this pack */
        else
                usable_delta = 0;       /* base could end up in another pack */
 
        if (!reuse_object)
                to_reuse = 0;   /* explicit */
-       else if (!entry->in_pack)
+       else if (!IN_PACK(entry))
                to_reuse = 0;   /* can't reuse what we don't have */
-       else if (entry->type == OBJ_REF_DELTA || entry->type == OBJ_OFS_DELTA)
+       else if (oe_type(entry) == OBJ_REF_DELTA ||
+                oe_type(entry) == OBJ_OFS_DELTA)
                                /* check_object() decided it for us ... */
                to_reuse = usable_delta;
                                /* ... but pack split may override that */
-       else if (entry->type != entry->in_pack_type)
+       else if (oe_type(entry) != entry->in_pack_type)
                to_reuse = 0;   /* pack has delta which is unusable */
-       else if (entry->delta)
+       else if (DELTA(entry))
                to_reuse = 0;   /* we want to pack afresh */
        else
                to_reuse = 1;   /* we have it in-pack undeltified,
@@ -538,12 +557,12 @@ static enum write_one_status write_one(struct hashfile *f,
        }
 
        /* if we are deltified, write out base object first. */
-       if (e->delta) {
+       if (DELTA(e)) {
                e->idx.offset = 1; /* now recurse */
-               switch (write_one(f, e->delta, offset)) {
+               switch (write_one(f, DELTA(e), offset)) {
                case WRITE_ONE_RECURSIVE:
                        /* we cannot depend on this one */
-                       e->delta = NULL;
+                       SET_DELTA(e, NULL);
                        break;
                default:
                        break;
@@ -605,34 +624,34 @@ static void add_descendants_to_write_order(struct object_entry **wo,
                        /* add this node... */
                        add_to_write_order(wo, endp, e);
                        /* all its siblings... */
-                       for (s = e->delta_sibling; s; s = s->delta_sibling) {
+                       for (s = DELTA_SIBLING(e); s; s = DELTA_SIBLING(s)) {
                                add_to_write_order(wo, endp, s);
                        }
                }
                /* drop down a level to add left subtree nodes if possible */
-               if (e->delta_child) {
+               if (DELTA_CHILD(e)) {
                        add_to_order = 1;
-                       e = e->delta_child;
+                       e = DELTA_CHILD(e);
                } else {
                        add_to_order = 0;
                        /* our sibling might have some children, it is next */
-                       if (e->delta_sibling) {
-                               e = e->delta_sibling;
+                       if (DELTA_SIBLING(e)) {
+                               e = DELTA_SIBLING(e);
                                continue;
                        }
                        /* go back to our parent node */
-                       e = e->delta;
-                       while (e && !e->delta_sibling) {
+                       e = DELTA(e);
+                       while (e && !DELTA_SIBLING(e)) {
                                /* we're on the right side of a subtree, keep
                                 * going up until we can go right again */
-                               e = e->delta;
+                               e = DELTA(e);
                        }
                        if (!e) {
                                /* done- we hit our original root node */
                                return;
                        }
                        /* pass it off to sibling at this level */
-                       e = e->delta_sibling;
+                       e = DELTA_SIBLING(e);
                }
        };
 }
@@ -643,7 +662,7 @@ static void add_family_to_write_order(struct object_entry **wo,
 {
        struct object_entry *root;
 
-       for (root = e; root->delta; root = root->delta)
+       for (root = e; DELTA(root); root = DELTA(root))
                ; /* nothing */
        add_descendants_to_write_order(wo, endp, root);
 }
@@ -658,8 +677,8 @@ static struct object_entry **compute_write_order(void)
        for (i = 0; i < to_pack.nr_objects; i++) {
                objects[i].tagged = 0;
                objects[i].filled = 0;
-               objects[i].delta_child = NULL;
-               objects[i].delta_sibling = NULL;
+               SET_DELTA_CHILD(&objects[i], NULL);
+               SET_DELTA_SIBLING(&objects[i], NULL);
        }
 
        /*
@@ -669,11 +688,11 @@ static struct object_entry **compute_write_order(void)
         */
        for (i = to_pack.nr_objects; i > 0;) {
                struct object_entry *e = &objects[--i];
-               if (!e->delta)
+               if (!DELTA(e))
                        continue;
                /* Mark me as the first child */
-               e->delta_sibling = e->delta->delta_child;
-               e->delta->delta_child = e;
+               e->delta_sibling_idx = DELTA(e)->delta_child_idx;
+               SET_DELTA_CHILD(DELTA(e), e);
        }
 
        /*
@@ -705,8 +724,8 @@ static struct object_entry **compute_write_order(void)
         * And then all remaining commits and tags.
         */
        for (i = last_untagged; i < to_pack.nr_objects; i++) {
-               if (objects[i].type != OBJ_COMMIT &&
-                   objects[i].type != OBJ_TAG)
+               if (oe_type(&objects[i]) != OBJ_COMMIT &&
+                   oe_type(&objects[i]) != OBJ_TAG)
                        continue;
                add_to_write_order(wo, &wo_end, &objects[i]);
        }
@@ -715,7 +734,7 @@ static struct object_entry **compute_write_order(void)
         * And then all the trees.
         */
        for (i = last_untagged; i < to_pack.nr_objects; i++) {
-               if (objects[i].type != OBJ_TREE)
+               if (oe_type(&objects[i]) != OBJ_TREE)
                        continue;
                add_to_write_order(wo, &wo_end, &objects[i]);
        }
@@ -752,7 +771,7 @@ static off_t write_reused_pack(struct hashfile *f)
                die_errno("unable to seek in reused packfile");
 
        if (reuse_packfile_offset < 0)
-               reuse_packfile_offset = reuse_packfile->pack_size - 20;
+               reuse_packfile_offset = reuse_packfile->pack_size - the_hash_algo->rawsz;
 
        total = to_write = reuse_packfile_offset - sizeof(struct pack_header);
 
@@ -878,7 +897,8 @@ static void write_pack_file(void)
 
                        if (write_bitmap_index) {
                                bitmap_writer_set_checksum(oid.hash);
-                               bitmap_writer_build_type_index(written_list, nr_written);
+                               bitmap_writer_build_type_index(
+                                       &to_pack, written_list, nr_written);
                        }
 
                        finish_tmp_packfile(&tmpname, pack_tmp_name,
@@ -982,13 +1002,16 @@ static int want_found_object(int exclude, struct packed_git *p)
         * Otherwise, we signal "-1" at the end to tell the caller that we do
         * not know either way, and it needs to check more packs.
         */
-       if (!ignore_packed_keep &&
+       if (!ignore_packed_keep_on_disk &&
+           !ignore_packed_keep_in_core &&
            (!local || !have_non_local_packs))
                return 1;
 
        if (local && !p->pack_local)
                return 0;
-       if (ignore_packed_keep && p->pack_local && p->pack_keep)
+       if (p->pack_local &&
+           ((ignore_packed_keep_on_disk && p->pack_keep) ||
+            (ignore_packed_keep_in_core && p->pack_keep_in_core)))
                return 0;
 
        /* we don't know yet; keep looking for more packs */
@@ -1012,7 +1035,7 @@ static int want_object_in_pack(const struct object_id *oid,
        int want;
        struct list_head *pos;
 
-       if (!exclude && local && has_loose_object_nonlocal(oid->hash))
+       if (!exclude && local && has_loose_object_nonlocal(oid))
                return 0;
 
        /*
@@ -1066,14 +1089,13 @@ static void create_object_entry(const struct object_id *oid,
 
        entry = packlist_alloc(&to_pack, oid->hash, index_pos);
        entry->hash = hash;
-       if (type)
-               entry->type = type;
+       oe_set_type(entry, type);
        if (exclude)
                entry->preferred_base = 1;
        else
                nr_result++;
        if (found_pack) {
-               entry->in_pack = found_pack;
+               oe_set_in_pack(&to_pack, entry, found_pack);
                entry->in_pack_offset = found_offset;
        }
 
@@ -1091,6 +1113,8 @@ static int add_object_entry(const struct object_id *oid, enum object_type type,
        off_t found_offset = 0;
        uint32_t index_pos;
 
+       display_progress(progress_state, ++nr_seen);
+
        if (have_duplicate_entry(oid, exclude, &index_pos))
                return 0;
 
@@ -1106,8 +1130,6 @@ static int add_object_entry(const struct object_id *oid, enum object_type type,
        create_object_entry(oid, type, pack_name_hash(name),
                            exclude, name && no_try_delta(name),
                            index_pos, found_pack, found_offset);
-
-       display_progress(progress_state, nr_result);
        return 1;
 }
 
@@ -1118,6 +1140,8 @@ static int add_object_entry_from_bitmap(const struct object_id *oid,
 {
        uint32_t index_pos;
 
+       display_progress(progress_state, ++nr_seen);
+
        if (have_duplicate_entry(oid, 0, &index_pos))
                return 0;
 
@@ -1125,8 +1149,6 @@ static int add_object_entry_from_bitmap(const struct object_id *oid,
                return 0;
 
        create_object_entry(oid, type, name_hash, 0, 0, index_pos, pack, offset);
-
-       display_progress(progress_state, nr_result);
        return 1;
 }
 
@@ -1398,8 +1420,10 @@ static void cleanup_preferred_base(void)
 
 static void check_object(struct object_entry *entry)
 {
-       if (entry->in_pack) {
-               struct packed_git *p = entry->in_pack;
+       unsigned long canonical_size;
+
+       if (IN_PACK(entry)) {
+               struct packed_git *p = IN_PACK(entry);
                struct pack_window *w_curs = NULL;
                const unsigned char *base_ref = NULL;
                struct object_entry *base_entry;
@@ -1407,6 +1431,8 @@ static void check_object(struct object_entry *entry)
                unsigned long avail;
                off_t ofs;
                unsigned char *buf, c;
+               enum object_type type;
+               unsigned long in_pack_size;
 
                buf = use_pack(p, &w_curs, entry->in_pack_offset, &avail);
 
@@ -1415,11 +1441,15 @@ static void check_object(struct object_entry *entry)
                 * since non-delta representations could still be reused.
                 */
                used = unpack_object_header_buffer(buf, avail,
-                                                  &entry->in_pack_type,
-                                                  &entry->size);
+                                                  &type,
+                                                  &in_pack_size);
                if (used == 0)
                        goto give_up;
 
+               if (type < 0)
+                       BUG("invalid type %d", type);
+               entry->in_pack_type = type;
+
                /*
                 * Determine if this is a delta and if so whether we can
                 * reuse it or not.  Otherwise let's find out as cheaply as
@@ -1428,9 +1458,10 @@ static void check_object(struct object_entry *entry)
                switch (entry->in_pack_type) {
                default:
                        /* Not a delta hence we've already got all we need. */
-                       entry->type = entry->in_pack_type;
+                       oe_set_type(entry, entry->in_pack_type);
+                       SET_SIZE(entry, in_pack_size);
                        entry->in_pack_header_size = used;
-                       if (entry->type < OBJ_COMMIT || entry->type > OBJ_BLOB)
+                       if (oe_type(entry) < OBJ_COMMIT || oe_type(entry) > OBJ_BLOB)
                                goto give_up;
                        unuse_pack(&w_curs);
                        return;
@@ -1438,7 +1469,7 @@ static void check_object(struct object_entry *entry)
                        if (reuse_delta && !entry->preferred_base)
                                base_ref = use_pack(p, &w_curs,
                                                entry->in_pack_offset + used, NULL);
-                       entry->in_pack_header_size = used + 20;
+                       entry->in_pack_header_size = used + the_hash_algo->rawsz;
                        break;
                case OBJ_OFS_DELTA:
                        buf = use_pack(p, &w_curs,
@@ -1484,25 +1515,29 @@ static void check_object(struct object_entry *entry)
                         * deltify other objects against, in order to avoid
                         * circular deltas.
                         */
-                       entry->type = entry->in_pack_type;
-                       entry->delta = base_entry;
-                       entry->delta_size = entry->size;
-                       entry->delta_sibling = base_entry->delta_child;
-                       base_entry->delta_child = entry;
+                       oe_set_type(entry, entry->in_pack_type);
+                       SET_SIZE(entry, in_pack_size); /* delta size */
+                       SET_DELTA(entry, base_entry);
+                       SET_DELTA_SIZE(entry, in_pack_size);
+                       entry->delta_sibling_idx = base_entry->delta_child_idx;
+                       SET_DELTA_CHILD(base_entry, entry);
                        unuse_pack(&w_curs);
                        return;
                }
 
-               if (entry->type) {
+               if (oe_type(entry)) {
+                       off_t delta_pos;
+
                        /*
                         * This must be a delta and we already know what the
                         * final object type is.  Let's extract the actual
                         * object size from the delta header.
                         */
-                       entry->size = get_size_from_delta(p, &w_curs,
-                                       entry->in_pack_offset + entry->in_pack_header_size);
-                       if (entry->size == 0)
+                       delta_pos = entry->in_pack_offset + entry->in_pack_header_size;
+                       canonical_size = get_size_from_delta(p, &w_curs, delta_pos);
+                       if (canonical_size == 0)
                                goto give_up;
+                       SET_SIZE(entry, canonical_size);
                        unuse_pack(&w_curs);
                        return;
                }
@@ -1516,27 +1551,34 @@ static void check_object(struct object_entry *entry)
                unuse_pack(&w_curs);
        }
 
-       entry->type = oid_object_info(&entry->idx.oid, &entry->size);
-       /*
-        * The error condition is checked in prepare_pack().  This is
-        * to permit a missing preferred base object to be ignored
-        * as a preferred base.  Doing so can result in a larger
-        * pack file, but the transfer will still take place.
-        */
+       oe_set_type(entry,
+                   oid_object_info(the_repository, &entry->idx.oid, &canonical_size));
+       if (entry->type_valid) {
+               SET_SIZE(entry, canonical_size);
+       } else {
+               /*
+                * Bad object type is checked in prepare_pack().  This is
+                * to permit a missing preferred base object to be ignored
+                * as a preferred base.  Doing so can result in a larger
+                * pack file, but the transfer will still take place.
+                */
+       }
 }
 
 static int pack_offset_sort(const void *_a, const void *_b)
 {
        const struct object_entry *a = *(struct object_entry **)_a;
        const struct object_entry *b = *(struct object_entry **)_b;
+       const struct packed_git *a_in_pack = IN_PACK(a);
+       const struct packed_git *b_in_pack = IN_PACK(b);
 
        /* avoid filesystem trashing with loose objects */
-       if (!a->in_pack && !b->in_pack)
+       if (!a_in_pack && !b_in_pack)
                return oidcmp(&a->idx.oid, &b->idx.oid);
 
-       if (a->in_pack < b->in_pack)
+       if (a_in_pack < b_in_pack)
                return -1;
-       if (a->in_pack > b->in_pack)
+       if (a_in_pack > b_in_pack)
                return 1;
        return a->in_pack_offset < b->in_pack_offset ? -1 :
                        (a->in_pack_offset > b->in_pack_offset);
@@ -1557,29 +1599,37 @@ static int pack_offset_sort(const void *_a, const void *_b)
  */
 static void drop_reused_delta(struct object_entry *entry)
 {
-       struct object_entry **p = &entry->delta->delta_child;
+       unsigned *idx = &to_pack.objects[entry->delta_idx - 1].delta_child_idx;
        struct object_info oi = OBJECT_INFO_INIT;
+       enum object_type type;
+       unsigned long size;
+
+       while (*idx) {
+               struct object_entry *oe = &to_pack.objects[*idx - 1];
 
-       while (*p) {
-               if (*p == entry)
-                       *p = (*p)->delta_sibling;
+               if (oe == entry)
+                       *idx = oe->delta_sibling_idx;
                else
-                       p = &(*p)->delta_sibling;
+                       idx = &oe->delta_sibling_idx;
        }
-       entry->delta = NULL;
+       SET_DELTA(entry, NULL);
        entry->depth = 0;
 
-       oi.sizep = &entry->size;
-       oi.typep = &entry->type;
-       if (packed_object_info(entry->in_pack, entry->in_pack_offset, &oi) < 0) {
+       oi.sizep = &size;
+       oi.typep = &type;
+       if (packed_object_info(the_repository, IN_PACK(entry), entry->in_pack_offset, &oi) < 0) {
                /*
                 * We failed to get the info from this pack for some reason;
                 * fall back to sha1_object_info, which may find another copy.
-                * And if that fails, the error will be recorded in entry->type
+                * And if that fails, the error will be recorded in oe_type(entry)
                 * and dealt with in prepare_pack().
                 */
-               entry->type = oid_object_info(&entry->idx.oid, &entry->size);
+               oe_set_type(entry,
+                           oid_object_info(the_repository, &entry->idx.oid, &size));
+       } else {
+               oe_set_type(entry, type);
        }
+       SET_SIZE(entry, size);
 }
 
 /*
@@ -1603,7 +1653,7 @@ static void break_delta_chains(struct object_entry *entry)
 
        for (cur = entry, total_depth = 0;
             cur;
-            cur = cur->delta, total_depth++) {
+            cur = DELTA(cur), total_depth++) {
                if (cur->dfs_state == DFS_DONE) {
                        /*
                         * We've already seen this object and know it isn't
@@ -1620,7 +1670,7 @@ static void break_delta_chains(struct object_entry *entry)
                 * is a bug.
                 */
                if (cur->dfs_state != DFS_NONE)
-                       die("BUG: confusing delta dfs state in first pass: %d",
+                       BUG("confusing delta dfs state in first pass: %d",
                            cur->dfs_state);
 
                /*
@@ -1628,7 +1678,7 @@ static void break_delta_chains(struct object_entry *entry)
                 * it's not a delta, we're done traversing, but we'll mark it
                 * done to save time on future traversals.
                 */
-               if (!cur->delta) {
+               if (!DELTA(cur)) {
                        cur->dfs_state = DFS_DONE;
                        break;
                }
@@ -1651,7 +1701,7 @@ static void break_delta_chains(struct object_entry *entry)
                 * We keep all commits in the chain that we examined.
                 */
                cur->dfs_state = DFS_ACTIVE;
-               if (cur->delta->dfs_state == DFS_ACTIVE) {
+               if (DELTA(cur)->dfs_state == DFS_ACTIVE) {
                        drop_reused_delta(cur);
                        cur->dfs_state = DFS_DONE;
                        break;
@@ -1666,7 +1716,7 @@ static void break_delta_chains(struct object_entry *entry)
         * an extra "next" pointer to keep going after we reset cur->delta.
         */
        for (cur = entry; cur; cur = next) {
-               next = cur->delta;
+               next = DELTA(cur);
 
                /*
                 * We should have a chain of zero or more ACTIVE states down to
@@ -1677,7 +1727,7 @@ static void break_delta_chains(struct object_entry *entry)
                if (cur->dfs_state == DFS_DONE)
                        break;
                else if (cur->dfs_state != DFS_ACTIVE)
-                       die("BUG: confusing delta dfs state in second pass: %d",
+                       BUG("confusing delta dfs state in second pass: %d",
                            cur->dfs_state);
 
                /*
@@ -1711,6 +1761,10 @@ static void get_object_details(void)
        uint32_t i;
        struct object_entry **sorted_by_offset;
 
+       if (progress)
+               progress_state = start_progress(_("Counting objects"),
+                                               to_pack.nr_objects);
+
        sorted_by_offset = xcalloc(to_pack.nr_objects, sizeof(struct object_entry *));
        for (i = 0; i < to_pack.nr_objects; i++)
                sorted_by_offset[i] = to_pack.objects + i;
@@ -1719,9 +1773,12 @@ static void get_object_details(void)
        for (i = 0; i < to_pack.nr_objects; i++) {
                struct object_entry *entry = sorted_by_offset[i];
                check_object(entry);
-               if (big_file_threshold < entry->size)
+               if (entry->type_valid &&
+                   oe_size_greater_than(&to_pack, entry, big_file_threshold))
                        entry->no_try_delta = 1;
+               display_progress(progress_state, i + 1);
        }
+       stop_progress(&progress_state);
 
        /*
         * This must happen in a second pass, since we rely on the delta
@@ -1746,10 +1803,14 @@ static int type_size_sort(const void *_a, const void *_b)
 {
        const struct object_entry *a = *(struct object_entry **)_a;
        const struct object_entry *b = *(struct object_entry **)_b;
+       enum object_type a_type = oe_type(a);
+       enum object_type b_type = oe_type(b);
+       unsigned long a_size = SIZE(a);
+       unsigned long b_size = SIZE(b);
 
-       if (a->type > b->type)
+       if (a_type > b_type)
                return -1;
-       if (a->type < b->type)
+       if (a_type < b_type)
                return 1;
        if (a->hash > b->hash)
                return -1;
@@ -1759,9 +1820,9 @@ static int type_size_sort(const void *_a, const void *_b)
                return -1;
        if (a->preferred_base < b->preferred_base)
                return 1;
-       if (a->size > b->size)
+       if (a_size > b_size)
                return -1;
-       if (a->size < b->size)
+       if (a_size < b_size)
                return 1;
        return a < b ? -1 : (a > b);  /* newest first */
 }
@@ -1814,6 +1875,46 @@ static pthread_mutex_t progress_mutex;
 
 #endif
 
+/*
+ * Return the size of the object without doing any delta
+ * reconstruction (so non-deltas are true object sizes, but deltas
+ * return the size of the delta data).
+ */
+unsigned long oe_get_size_slow(struct packing_data *pack,
+                              const struct object_entry *e)
+{
+       struct packed_git *p;
+       struct pack_window *w_curs;
+       unsigned char *buf;
+       enum object_type type;
+       unsigned long used, avail, size;
+
+       if (e->type_ != OBJ_OFS_DELTA && e->type_ != OBJ_REF_DELTA) {
+               read_lock();
+               if (oid_object_info(the_repository, &e->idx.oid, &size) < 0)
+                       die(_("unable to get size of %s"),
+                           oid_to_hex(&e->idx.oid));
+               read_unlock();
+               return size;
+       }
+
+       p = oe_in_pack(pack, e);
+       if (!p)
+               BUG("when e->type is a delta, it must belong to a pack");
+
+       read_lock();
+       w_curs = NULL;
+       buf = use_pack(p, &w_curs, e->in_pack_offset, &avail);
+       used = unpack_object_header_buffer(buf, avail, &type, &size);
+       if (used == 0)
+               die(_("unable to parse object header of %s"),
+                   oid_to_hex(&e->idx.oid));
+
+       unuse_pack(&w_curs);
+       read_unlock();
+       return size;
+}
+
 static int try_delta(struct unpacked *trg, struct unpacked *src,
                     unsigned max_depth, unsigned long *mem_usage)
 {
@@ -1825,7 +1926,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
        void *delta_buf;
 
        /* Don't bother doing diffs between different types */
-       if (trg_entry->type != src_entry->type)
+       if (oe_type(trg_entry) != oe_type(src_entry))
                return -1;
 
        /*
@@ -1836,8 +1937,8 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
         * it, we will still save the transfer cost, as we already know
         * the other side has it and we won't send src_entry at all.
         */
-       if (reuse_delta && trg_entry->in_pack &&
-           trg_entry->in_pack == src_entry->in_pack &&
+       if (reuse_delta && IN_PACK(trg_entry) &&
+           IN_PACK(trg_entry) == IN_PACK(src_entry) &&
            !src_entry->preferred_base &&
            trg_entry->in_pack_type != OBJ_REF_DELTA &&
            trg_entry->in_pack_type != OBJ_OFS_DELTA)
@@ -1848,19 +1949,19 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
                return 0;
 
        /* Now some size filtering heuristics. */
-       trg_size = trg_entry->size;
-       if (!trg_entry->delta) {
-               max_size = trg_size/2 - 20;
+       trg_size = SIZE(trg_entry);
+       if (!DELTA(trg_entry)) {
+               max_size = trg_size/2 - the_hash_algo->rawsz;
                ref_depth = 1;
        } else {
-               max_size = trg_entry->delta_size;
+               max_size = DELTA_SIZE(trg_entry);
                ref_depth = trg->depth;
        }
        max_size = (uint64_t)max_size * (max_depth - src->depth) /
                                                (max_depth - ref_depth + 1);
        if (max_size == 0)
                return 0;
-       src_size = src_entry->size;
+       src_size = SIZE(src_entry);
        sizediff = src_size < trg_size ? trg_size - src_size : 0;
        if (sizediff >= max_size)
                return 0;
@@ -1922,10 +2023,14 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
        delta_buf = create_delta(src->index, trg->data, trg_size, &delta_size, max_size);
        if (!delta_buf)
                return 0;
+       if (delta_size >= (1U << OE_DELTA_SIZE_BITS)) {
+               free(delta_buf);
+               return 0;
+       }
 
-       if (trg_entry->delta) {
+       if (DELTA(trg_entry)) {
                /* Prefer only shallower same-sized deltas. */
-               if (delta_size == trg_entry->delta_size &&
+               if (delta_size == DELTA_SIZE(trg_entry) &&
                    src->depth + 1 >= trg->depth) {
                        free(delta_buf);
                        return 0;
@@ -1940,7 +2045,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
        free(trg_entry->delta_data);
        cache_lock();
        if (trg_entry->delta_data) {
-               delta_cache_size -= trg_entry->delta_size;
+               delta_cache_size -= DELTA_SIZE(trg_entry);
                trg_entry->delta_data = NULL;
        }
        if (delta_cacheable(src_size, trg_size, delta_size)) {
@@ -1952,8 +2057,8 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
                free(delta_buf);
        }
 
-       trg_entry->delta = src_entry;
-       trg_entry->delta_size = delta_size;
+       SET_DELTA(trg_entry, src_entry);
+       SET_DELTA_SIZE(trg_entry, delta_size);
        trg->depth = src->depth + 1;
 
        return 1;
@@ -1961,13 +2066,13 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
 
 static unsigned int check_delta_limit(struct object_entry *me, unsigned int n)
 {
-       struct object_entry *child = me->delta_child;
+       struct object_entry *child = DELTA_CHILD(me);
        unsigned int m = n;
        while (child) {
                unsigned int c = check_delta_limit(child, n + 1);
                if (m < c)
                        m = c;
-               child = child->delta_sibling;
+               child = DELTA_SIBLING(child);
        }
        return m;
 }
@@ -1978,7 +2083,7 @@ static unsigned long free_unpacked(struct unpacked *n)
        free_delta_index(n->index);
        n->index = NULL;
        if (n->data) {
-               freed_mem += n->entry->size;
+               freed_mem += SIZE(n->entry);
                FREE_AND_NULL(n->data);
        }
        n->entry = NULL;
@@ -2036,7 +2141,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,
                 * otherwise they would become too deep.
                 */
                max_depth = depth;
-               if (entry->delta_child) {
+               if (DELTA_CHILD(entry)) {
                        max_depth -= check_delta_limit(entry, 0);
                        if (max_depth <= 0)
                                goto next;
@@ -2074,19 +2179,26 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,
                 * between writes at that moment.
                 */
                if (entry->delta_data && !pack_to_stdout) {
-                       entry->z_delta_size = do_compress(&entry->delta_data,
-                                                         entry->delta_size);
-                       cache_lock();
-                       delta_cache_size -= entry->delta_size;
-                       delta_cache_size += entry->z_delta_size;
-                       cache_unlock();
+                       unsigned long size;
+
+                       size = do_compress(&entry->delta_data, DELTA_SIZE(entry));
+                       if (size < (1U << OE_Z_DELTA_BITS)) {
+                               entry->z_delta_size = size;
+                               cache_lock();
+                               delta_cache_size -= DELTA_SIZE(entry);
+                               delta_cache_size += entry->z_delta_size;
+                               cache_unlock();
+                       } else {
+                               FREE_AND_NULL(entry->delta_data);
+                               entry->z_delta_size = 0;
+                       }
                }
 
                /* if we made n a delta, and if n is already at max
                 * depth, leaving it in the window is pointless.  we
                 * should evict it first.
                 */
-               if (entry->delta && max_depth <= n->depth)
+               if (DELTA(entry) && max_depth <= n->depth)
                        continue;
 
                /*
@@ -2094,7 +2206,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,
                 * currently deltified object, to keep it longer.  It will
                 * be the first base object to be attempted next.
                 */
-               if (entry->delta) {
+               if (DELTA(entry)) {
                        struct unpacked swap = array[best_base];
                        int dist = (window + idx - best_base) % window;
                        int dst = best_base;
@@ -2415,13 +2527,14 @@ static void prepare_pack(int window, int depth)
        for (i = 0; i < to_pack.nr_objects; i++) {
                struct object_entry *entry = to_pack.objects + i;
 
-               if (entry->delta)
+               if (DELTA(entry))
                        /* This happens if we decided to reuse existing
                         * delta from a pack.  "reuse_delta &&" is implied.
                         */
                        continue;
 
-               if (entry->size < 50)
+               if (!entry->type_valid ||
+                   oe_size_less_than(&to_pack, entry, 50))
                        continue;
 
                if (entry->no_try_delta)
@@ -2429,11 +2542,11 @@ static void prepare_pack(int window, int depth)
 
                if (!entry->preferred_base) {
                        nr_deltas++;
-                       if (entry->type < 0)
+                       if (oe_type(entry) < 0)
                                die("unable to get type of object %s",
                                    oid_to_hex(&entry->idx.oid));
                } else {
-                       if (entry->type < 0) {
+                       if (oe_type(entry) < 0) {
                                /*
                                 * This object is not found, but we
                                 * don't have to include it anyway.
@@ -2542,7 +2655,7 @@ static void read_object_list_from_stdin(void)
                        die("expected object ID, got garbage:\n %s", line);
 
                add_preferred_base_object(p + 1);
-               add_object_entry(&oid, 0, p + 1, 0);
+               add_object_entry(&oid, OBJ_NONE, p + 1, 0);
        }
 }
 
@@ -2675,7 +2788,7 @@ static void add_objects_in_unpacked_packs(struct rev_info *revs)
                struct object_id oid;
                struct object *o;
 
-               if (!p->pack_local || p->pack_keep)
+               if (!p->pack_local || p->pack_keep || p->pack_keep_in_core)
                        continue;
                if (open_pack_index(p))
                        die("cannot open pack index");
@@ -2706,7 +2819,7 @@ static void add_objects_in_unpacked_packs(struct rev_info *revs)
 static int add_loose_object(const struct object_id *oid, const char *path,
                            void *data)
 {
-       enum object_type type = oid_object_info(oid, NULL);
+       enum object_type type = oid_object_info(the_repository, oid, NULL);
 
        if (type < 0) {
                warning("loose object at %s could not be examined", path);
@@ -2738,7 +2851,8 @@ static int has_sha1_pack_kept_or_nonlocal(const struct object_id *oid)
                                        get_packed_git(the_repository);
 
        while (p) {
-               if ((!p->pack_local || p->pack_keep) &&
+               if ((!p->pack_local || p->pack_keep ||
+                               p->pack_keep_in_core) &&
                        find_pack_entry_one(oid->hash, p)) {
                        last_found = p;
                        return 1;
@@ -2781,7 +2895,7 @@ static void loosen_unused_packed_objects(struct rev_info *revs)
        struct object_id oid;
 
        for (p = get_packed_git(the_repository); p; p = p->next) {
-               if (!p->pack_local || p->pack_keep)
+               if (!p->pack_local || p->pack_keep || p->pack_keep_in_core)
                        continue;
 
                if (open_pack_index(p))
@@ -2807,7 +2921,8 @@ static int pack_options_allow_reuse(void)
 {
        return pack_to_stdout &&
               allow_ofs_delta &&
-              !ignore_packed_keep &&
+              !ignore_packed_keep_on_disk &&
+              !ignore_packed_keep_in_core &&
               (!local || !have_non_local_packs) &&
               !incremental;
 }
@@ -2916,6 +3031,32 @@ static void get_object_list(int ac, const char **av)
        oid_array_clear(&recent_objects);
 }
 
+static void add_extra_kept_packs(const struct string_list *names)
+{
+       struct packed_git *p;
+
+       if (!names->nr)
+               return;
+
+       for (p = get_packed_git(the_repository); p; p = p->next) {
+               const char *name = basename(p->pack_name);
+               int i;
+
+               if (!p->pack_local)
+                       continue;
+
+               for (i = 0; i < names->nr; i++)
+                       if (!fspathcmp(name, names->items[i].string))
+                               break;
+
+               if (i < names->nr) {
+                       p->pack_keep_in_core = 1;
+                       ignore_packed_keep_in_core = 1;
+                       continue;
+               }
+       }
+}
+
 static int option_parse_index_version(const struct option *opt,
                                      const char *arg, int unset)
 {
@@ -2955,6 +3096,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
        struct argv_array rp = ARGV_ARRAY_INIT;
        int rev_list_unpacked = 0, rev_list_all = 0, rev_list_reflog = 0;
        int rev_list_index = 0;
+       struct string_list keep_pack_list = STRING_LIST_INIT_NODUP;
        struct option pack_objects_options[] = {
                OPT_SET_INT('q', "quiet", &progress,
                            N_("do not show progress meter"), 0),
@@ -2992,18 +3134,18 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                         N_("do not create an empty pack output")),
                OPT_BOOL(0, "revs", &use_internal_rev_list,
                         N_("read revision arguments from standard input")),
-               { OPTION_SET_INT, 0, "unpacked", &rev_list_unpacked, NULL,
-                 N_("limit the objects to those that are not yet packed"),
-                 PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 },
-               { OPTION_SET_INT, 0, "all", &rev_list_all, NULL,
-                 N_("include objects reachable from any reference"),
-                 PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 },
-               { OPTION_SET_INT, 0, "reflog", &rev_list_reflog, NULL,
-                 N_("include objects referred by reflog entries"),
-                 PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 },
-               { OPTION_SET_INT, 0, "indexed-objects", &rev_list_index, NULL,
-                 N_("include objects referred to by the index"),
-                 PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 },
+               OPT_SET_INT_F(0, "unpacked", &rev_list_unpacked,
+                             N_("limit the objects to those that are not yet packed"),
+                             1, PARSE_OPT_NONEG),
+               OPT_SET_INT_F(0, "all", &rev_list_all,
+                             N_("include objects reachable from any reference"),
+                             1, PARSE_OPT_NONEG),
+               OPT_SET_INT_F(0, "reflog", &rev_list_reflog,
+                             N_("include objects referred by reflog entries"),
+                             1, PARSE_OPT_NONEG),
+               OPT_SET_INT_F(0, "indexed-objects", &rev_list_index,
+                             N_("include objects referred to by the index"),
+                             1, PARSE_OPT_NONEG),
                OPT_BOOL(0, "stdout", &pack_to_stdout,
                         N_("output pack to stdout")),
                OPT_BOOL(0, "include-tag", &include_tag,
@@ -3019,8 +3161,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                         N_("create thin packs")),
                OPT_BOOL(0, "shallow", &shallow,
                         N_("create packs suitable for shallow fetches")),
-               OPT_BOOL(0, "honor-pack-keep", &ignore_packed_keep,
+               OPT_BOOL(0, "honor-pack-keep", &ignore_packed_keep_on_disk,
                         N_("ignore packs that have companion .keep file")),
+               OPT_STRING_LIST(0, "keep-pack", &keep_pack_list, N_("name"),
+                               N_("ignore this pack")),
                OPT_INTEGER(0, "compression", &pack_compression_level,
                            N_("pack compression level")),
                OPT_SET_INT(0, "keep-true-parents", &grafts_replace_parents,
@@ -3038,6 +3182,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                OPT_END(),
        };
 
+       if (DFS_NUM_STATES > (1 << OE_DFS_STATE_BITS))
+               BUG("too many dfs states, increase OE_DFS_STATE_BITS");
+
        check_replace_refs = 0;
 
        reset_pack_idx_option(&pack_idx_opts);
@@ -3054,6 +3201,17 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
        if (pack_to_stdout != !base_name || argc)
                usage_with_options(pack_usage, pack_objects_options);
 
+       if (depth >= (1 << OE_DEPTH_BITS)) {
+               warning(_("delta chain depth %d is too deep, forcing %d"),
+                       depth, (1 << OE_DEPTH_BITS) - 1);
+               depth = (1 << OE_DEPTH_BITS) - 1;
+       }
+       if (cache_max_small_delta_size >= (1U << OE_Z_DELTA_BITS)) {
+               warning(_("pack.deltaCacheLimit is too high, forcing %d"),
+                       (1U << OE_Z_DELTA_BITS) - 1);
+               cache_max_small_delta_size = (1U << OE_Z_DELTA_BITS) - 1;
+       }
+
        argv_array_push(&rp, "pack-objects");
        if (thin) {
                use_internal_rev_list = 1;
@@ -3085,6 +3243,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                fetch_if_missing = 0;
                argv_array_push(&rp, "--exclude-promisor-objects");
        }
+       if (unpack_unreachable || keep_unreachable || pack_loose_unreachable)
+               use_internal_rev_list = 1;
 
        if (!reuse_object)
                reuse_delta = 0;
@@ -3148,19 +3308,20 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
        if (progress && all_progress_implied)
                progress = 2;
 
-       if (ignore_packed_keep) {
+       add_extra_kept_packs(&keep_pack_list);
+       if (ignore_packed_keep_on_disk) {
                struct packed_git *p;
                for (p = get_packed_git(the_repository); p; p = p->next)
                        if (p->pack_local && p->pack_keep)
                                break;
                if (!p) /* no keep-able packs found */
-                       ignore_packed_keep = 0;
+                       ignore_packed_keep_on_disk = 0;
        }
        if (local) {
                /*
-                * unlike ignore_packed_keep above, we do not want to
-                * unset "local" based on looking at packs, as it
-                * also covers non-local objects
+                * unlike ignore_packed_keep_on_disk above, we do not
+                * want to unset "local" based on looking at packs, as
+                * it also covers non-local objects
                 */
                struct packed_git *p;
                for (p = get_packed_git(the_repository); p; p = p->next) {
@@ -3171,8 +3332,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                }
        }
 
+       prepare_packing_data(&to_pack);
+
        if (progress)
-               progress_state = start_progress(_("Counting objects"), 0);
+               progress_state = start_progress(_("Enumerating objects"), 0);
        if (!use_internal_rev_list)
                read_object_list_from_stdin();
        else {
index 354478a12762d0b500cf7f56baf90ffc8d2b7c93..0494dceff73d672ee430f1c5bd0ce9f8b70c83f6 100644 (file)
@@ -20,7 +20,7 @@ static int load_all_packs, verbose, alt_odb;
 
 struct llist_item {
        struct llist_item *next;
-       const unsigned char *sha1;
+       const struct object_id *oid;
 };
 static struct llist {
        struct llist_item *front;
@@ -90,14 +90,14 @@ static struct llist * llist_copy(struct llist *list)
                return ret;
 
        new_item = ret->front = llist_item_get();
-       new_item->sha1 = list->front->sha1;
+       new_item->oid = list->front->oid;
 
        old_item = list->front->next;
        while (old_item) {
                prev = new_item;
                new_item = llist_item_get();
                prev->next = new_item;
-               new_item->sha1 = old_item->sha1;
+               new_item->oid = old_item->oid;
                old_item = old_item->next;
        }
        new_item->next = NULL;
@@ -108,10 +108,10 @@ static struct llist * llist_copy(struct llist *list)
 
 static inline struct llist_item *llist_insert(struct llist *list,
                                              struct llist_item *after,
-                                              const unsigned char *sha1)
+                                             const struct object_id *oid)
 {
        struct llist_item *new_item = llist_item_get();
-       new_item->sha1 = sha1;
+       new_item->oid = oid;
        new_item->next = NULL;
 
        if (after != NULL) {
@@ -131,21 +131,21 @@ static inline struct llist_item *llist_insert(struct llist *list,
 }
 
 static inline struct llist_item *llist_insert_back(struct llist *list,
-                                                  const unsigned char *sha1)
+                                                  const struct object_id *oid)
 {
-       return llist_insert(list, list->back, sha1);
+       return llist_insert(list, list->back, oid);
 }
 
 static inline struct llist_item *llist_insert_sorted_unique(struct llist *list,
-                       const unsigned char *sha1, struct llist_item *hint)
+                       const struct object_id *oid, struct llist_item *hint)
 {
        struct llist_item *prev = NULL, *l;
 
        l = (hint == NULL) ? list->front : hint;
        while (l) {
-               int cmp = hashcmp(l->sha1, sha1);
+               int cmp = oidcmp(l->oid, oid);
                if (cmp > 0) { /* we insert before this entry */
-                       return llist_insert(list, prev, sha1);
+                       return llist_insert(list, prev, oid);
                }
                if (!cmp) { /* already exists */
                        return l;
@@ -154,11 +154,11 @@ static inline struct llist_item *llist_insert_sorted_unique(struct llist *list,
                l = l->next;
        }
        /* insert at the end */
-       return llist_insert_back(list, sha1);
+       return llist_insert_back(list, oid);
 }
 
 /* returns a pointer to an item in front of sha1 */
-static inline struct llist_item * llist_sorted_remove(struct llist *list, const unsigned char *sha1, struct llist_item *hint)
+static inline struct llist_item * llist_sorted_remove(struct llist *list, const struct object_id *oid, struct llist_item *hint)
 {
        struct llist_item *prev, *l;
 
@@ -166,7 +166,7 @@ static inline struct llist_item * llist_sorted_remove(struct llist *list, const
        l = (hint == NULL) ? list->front : hint;
        prev = NULL;
        while (l) {
-               int cmp = hashcmp(l->sha1, sha1);
+               int cmp = oidcmp(l->oid, oid);
                if (cmp > 0) /* not in list, since sorted */
                        return prev;
                if (!cmp) { /* found */
@@ -201,7 +201,7 @@ static void llist_sorted_difference_inplace(struct llist *A,
        b = B->front;
 
        while (b) {
-               hint = llist_sorted_remove(A, b->sha1, hint);
+               hint = llist_sorted_remove(A, b->oid, hint);
                b = b->next;
        }
 }
@@ -252,13 +252,14 @@ static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2)
        unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step;
        const unsigned char *p1_base, *p2_base;
        struct llist_item *p1_hint = NULL, *p2_hint = NULL;
+       const unsigned int hashsz = the_hash_algo->rawsz;
 
        p1_base = p1->pack->index_data;
        p2_base = p2->pack->index_data;
        p1_base += 256 * 4 + ((p1->pack->index_version < 2) ? 4 : 8);
        p2_base += 256 * 4 + ((p2->pack->index_version < 2) ? 4 : 8);
-       p1_step = (p1->pack->index_version < 2) ? 24 : 20;
-       p2_step = (p2->pack->index_version < 2) ? 24 : 20;
+       p1_step = hashsz + ((p1->pack->index_version < 2) ? 4 : 0);
+       p2_step = hashsz + ((p2->pack->index_version < 2) ? 4 : 0);
 
        while (p1_off < p1->pack->num_objects * p1_step &&
               p2_off < p2->pack->num_objects * p2_step)
@@ -267,9 +268,11 @@ static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2)
                /* cmp ~ p1 - p2 */
                if (cmp == 0) {
                        p1_hint = llist_sorted_remove(p1->unique_objects,
-                                       p1_base + p1_off, p1_hint);
+                                       (const struct object_id *)(p1_base + p1_off),
+                                       p1_hint);
                        p2_hint = llist_sorted_remove(p2->unique_objects,
-                                       p1_base + p1_off, p2_hint);
+                                       (const struct object_id *)(p1_base + p1_off),
+                                       p2_hint);
                        p1_off += p1_step;
                        p2_off += p2_step;
                        continue;
@@ -359,13 +362,14 @@ static size_t sizeof_union(struct packed_git *p1, struct packed_git *p2)
        size_t ret = 0;
        unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step;
        const unsigned char *p1_base, *p2_base;
+       const unsigned int hashsz = the_hash_algo->rawsz;
 
        p1_base = p1->index_data;
        p2_base = p2->index_data;
        p1_base += 256 * 4 + ((p1->index_version < 2) ? 4 : 8);
        p2_base += 256 * 4 + ((p2->index_version < 2) ? 4 : 8);
-       p1_step = (p1->index_version < 2) ? 24 : 20;
-       p2_step = (p2->index_version < 2) ? 24 : 20;
+       p1_step = hashsz + ((p1->index_version < 2) ? 4 : 0);
+       p2_step = hashsz + ((p2->index_version < 2) ? 4 : 0);
 
        while (p1_off < p1->num_objects * p1_step &&
               p2_off < p2->num_objects * p2_step)
@@ -499,7 +503,7 @@ static void load_all_objects(void)
                l = pl->all_objects->front;
                while (l) {
                        hint = llist_insert_sorted_unique(all_objects,
-                                                         l->sha1, hint);
+                                                         l->oid, hint);
                        l = l->next;
                }
                pl = pl->next;
@@ -558,9 +562,9 @@ static struct pack_list * add_pack(struct packed_git *p)
 
        base = p->index_data;
        base += 256 * 4 + ((p->index_version < 2) ? 4 : 8);
-       step = (p->index_version < 2) ? 24 : 20;
+       step = the_hash_algo->rawsz + ((p->index_version < 2) ? 4 : 0);
        while (off < p->num_objects * step) {
-               llist_insert_back(l.all_objects, base + off);
+               llist_insert_back(l.all_objects, (const struct object_id *)(base + off));
                off += step;
        }
        /* this list will be pruned in cmp_two_packs later */
@@ -601,8 +605,8 @@ int cmd_pack_redundant(int argc, const char **argv, const char *prefix)
        int i;
        struct pack_list *min, *red, *pl;
        struct llist *ignore;
-       unsigned char *sha1;
-       char buf[42]; /* 40 byte sha1 + \n + \0 */
+       struct object_id *oid;
+       char buf[GIT_MAX_HEXSZ + 2]; /* hex hash + \n + \0 */
 
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage(pack_redundant_usage);
@@ -650,10 +654,10 @@ int cmd_pack_redundant(int argc, const char **argv, const char *prefix)
        llist_init(&ignore);
        if (!isatty(0)) {
                while (fgets(buf, sizeof(buf), stdin)) {
-                       sha1 = xmalloc(20);
-                       if (get_sha1_hex(buf, sha1))
-                               die("Bad sha1 on stdin: %s", buf);
-                       llist_insert_sorted_unique(ignore, sha1, NULL);
+                       oid = xmalloc(sizeof(*oid));
+                       if (get_oid_hex(buf, oid))
+                               die("Bad object ID on stdin: %s", buf);
+                       llist_insert_sorted_unique(ignore, oid, NULL);
                }
        }
        llist_sorted_difference_inplace(all_objects, ignore);
index 419238171d7a2159f5e533bab84408aacb4d35e4..4ff525e50fc701392e7ed276ae4bba4a82ddf8a1 100644 (file)
@@ -25,7 +25,7 @@ static int prune_object(const struct object_id *oid, const char *path,
 {
        int *opts = data;
 
-       if (!has_sha1_pack(oid->hash))
+       if (!has_object_pack(oid))
                return 0;
 
        if (*opts & PRUNE_PACKED_DRY_RUN)
index 38ced18dadff03836c6bed3fcbdefe8bd528a7c0..518ffbea1397faa35102dc088a3830e40c9b13a6 100644 (file)
@@ -50,7 +50,8 @@ static int prune_object(const struct object_id *oid, const char *fullpath,
        if (st.st_mtime > expire)
                return 0;
        if (show_only || verbose) {
-               enum object_type type = oid_object_info(oid, NULL);
+               enum object_type type = oid_object_info(the_repository, oid,
+                                                       NULL);
                printf("%s %s\n", oid_to_hex(oid),
                       (type > 0) ? type_name(type) : "unknown");
        }
index 09575fd23ce75b4bbf4e6a541649adc2f2107cba..1f2ecf3a88d95de5f819285aa1fc1558b63b6c4e 100644 (file)
@@ -28,14 +28,16 @@ enum rebase_type {
        REBASE_FALSE = 0,
        REBASE_TRUE,
        REBASE_PRESERVE,
+       REBASE_MERGES,
        REBASE_INTERACTIVE
 };
 
 /**
  * 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.
+ * "merges", returns REBASE_MERGES. 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)
@@ -48,6 +50,8 @@ static enum rebase_type parse_config_rebase(const char *key, const char *value,
                return REBASE_TRUE;
        else if (!strcmp(value, "preserve"))
                return REBASE_PRESERVE;
+       else if (!strcmp(value, "merges"))
+               return REBASE_MERGES;
        else if (!strcmp(value, "interactive"))
                return REBASE_INTERACTIVE;
 
@@ -131,7 +135,7 @@ static struct option pull_options[] = {
        /* Options passed to git-merge or git-rebase */
        OPT_GROUP(N_("Options related to merging")),
        { OPTION_CALLBACK, 'r', "rebase", &opt_rebase,
-         "false|true|preserve|interactive",
+         "false|true|merges|preserve|interactive",
          N_("incorporate changes by rebasing rather than merging"),
          PARSE_OPT_OPTARG, parse_opt_rebase },
        OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,
@@ -540,7 +544,7 @@ static int run_fetch(const char *repo, const char **refspecs)
                argv_array_push(&args, repo);
                argv_array_pushv(&args, refspecs);
        } else if (*refspecs)
-               die("BUG: refspecs without repo?");
+               BUG("refspecs without repo?");
        ret = run_command_v_opt(args.argv, RUN_GIT_CMD);
        argv_array_clear(&args);
        return ret;
@@ -801,7 +805,9 @@ static int run_rebase(const struct object_id *curr_head,
        argv_push_verbosity(&args);
 
        /* Options passed to git-rebase */
-       if (opt_rebase == REBASE_PRESERVE)
+       if (opt_rebase == REBASE_MERGES)
+               argv_array_push(&args, "--rebase-merges");
+       else if (opt_rebase == REBASE_PRESERVE)
                argv_array_push(&args, "--preserve-merges");
        else if (opt_rebase == REBASE_INTERACTIVE)
                argv_array_push(&args, "--interactive");
index bf87a2710b31fa49b1802ed297c8ae52eede6354..ebc43eb8057dc9c85aad4c608250ed9a6c4d6cd1 100644 (file)
@@ -107,8 +107,6 @@ static int git_read_tree_config(const char *var, const char *value, void *cb)
        return git_default_config(var, value, cb);
 }
 
-static struct lock_file lock_file;
-
 int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
 {
        int i, stage = 0;
@@ -116,6 +114,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
        struct tree_desc t[MAX_UNPACK_TREES];
        struct unpack_trees_options opts;
        int prefix_set = 0;
+       struct lock_file lock_file = LOCK_INIT;
        const struct option read_tree_options[] = {
                { OPTION_CALLBACK, 0, "index-output", NULL, N_("file"),
                  N_("write resulting index to <file>"),
index ad074705bb51d1de4221b3c5dfaa7229903c0ef0..f7c2a5fdc815a892b99d6561d73d953740bf789a 100644 (file)
@@ -12,8 +12,8 @@ static const char * const builtin_rebase_helper_usage[] = {
 int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 {
        struct replay_opts opts = REPLAY_OPTS_INIT;
-       unsigned flags = 0, keep_empty = 0;
-       int abbreviate_commands = 0;
+       unsigned flags = 0, keep_empty = 0, rebase_merges = 0;
+       int abbreviate_commands = 0, rebase_cousins = -1;
        enum {
                CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,
                CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,
@@ -24,6 +24,9 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
                OPT_BOOL(0, "keep-empty", &keep_empty, N_("keep empty commits")),
                OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message,
                        N_("allow commits with empty messages")),
+               OPT_BOOL(0, "rebase-merges", &rebase_merges, N_("rebase merge commits")),
+               OPT_BOOL(0, "rebase-cousins", &rebase_cousins,
+                        N_("keep original branch points of cousins")),
                OPT_CMDMODE(0, "continue", &command, N_("continue rebase"),
                                CONTINUE),
                OPT_CMDMODE(0, "abort", &command, N_("abort rebase"),
@@ -57,8 +60,14 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 
        flags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
        flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
+       flags |= rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
+       flags |= rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
        flags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
 
+       if (rebase_cousins >= 0 && !rebase_merges)
+               warning(_("--[no-]rebase-cousins has no effect without "
+                         "--rebase-merges"));
+
        if (command == CONTINUE && argc == 1)
                return !!sequencer_continue(&opts);
        if (command == ABORT && argc == 1)
index 0dd163280d43c9d23e87fecc049ed03332aada0b..68d36e0a56c3c72ad76c07af293d20e8254377d1 100644 (file)
@@ -454,21 +454,21 @@ static void hmac_sha1(unsigned char *out,
        /* RFC 2104 2. (6) & (7) */
        git_SHA1_Init(&ctx);
        git_SHA1_Update(&ctx, k_opad, sizeof(k_opad));
-       git_SHA1_Update(&ctx, out, 20);
+       git_SHA1_Update(&ctx, out, GIT_SHA1_RAWSZ);
        git_SHA1_Final(out, &ctx);
 }
 
 static char *prepare_push_cert_nonce(const char *path, timestamp_t stamp)
 {
        struct strbuf buf = STRBUF_INIT;
-       unsigned char sha1[20];
+       unsigned char sha1[GIT_SHA1_RAWSZ];
 
        strbuf_addf(&buf, "%s:%"PRItime, path, stamp);
        hmac_sha1(sha1, buf.buf, buf.len, cert_nonce_seed, strlen(cert_nonce_seed));;
        strbuf_release(&buf);
 
        /* RFC 2104 5. HMAC-SHA1-80 */
-       strbuf_addf(&buf, "%"PRItime"-%.*s", stamp, 20, sha1_to_hex(sha1));
+       strbuf_addf(&buf, "%"PRItime"-%.*s", stamp, GIT_SHA1_HEXSZ, sha1_to_hex(sha1));
        return strbuf_detach(&buf, NULL);
 }
 
@@ -876,7 +876,7 @@ static void refuse_unconfigured_deny_delete_current(void)
 static int command_singleton_iterator(void *cb_data, struct object_id *oid);
 static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
 {
-       static struct lock_file shallow_lock;
+       struct lock_file shallow_lock = LOCK_INIT;
        struct oid_array extra = OID_ARRAY_INIT;
        struct check_connected_options opt = CHECK_CONNECTED_INIT;
        uint32_t mask = 1 << (cmd->index % 32);
@@ -968,7 +968,7 @@ static const char *push_to_deploy(unsigned char *sha1,
                return "Working directory has unstaged changes";
 
        /* diff-index with either HEAD or an empty tree */
-       diff_index[4] = head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX;
+       diff_index[4] = head_has_history() ? "HEAD" : empty_tree_oid_hex();
 
        child_process_init(&child);
        child.argv = diff_index;
@@ -1378,7 +1378,7 @@ static void warn_if_skipped_connectivity_check(struct command *commands,
                }
        }
        if (!checked_connectivity)
-               die("BUG: connectivity check skipped???");
+               BUG("connectivity check skipped???");
 }
 
 static void execute_commands_non_atomic(struct command *commands,
index a89bd1dd25252ddfe5ad6b32ee89950d3a4b258f..a48984d37e4f5f9e1f07a840582f82e202bd09f0 100644 (file)
@@ -154,7 +154,7 @@ static int commit_is_complete(struct commit *commit)
                for (i = 0; i < found.nr; i++) {
                        struct commit *c =
                                (struct commit *)found.objects[i].item;
-                       if (!tree_is_complete(&c->tree->object.oid)) {
+                       if (!tree_is_complete(get_commit_tree_oid(c))) {
                                is_incomplete = 1;
                                c->object.flags |= INCOMPLETE;
                        }
index b84175cc6ce98c85f5596d1029a09398912bd097..1a82d850a22c3bfc3176eeda3f8b061b6feb7dc4 100644 (file)
@@ -246,7 +246,9 @@ static int add(int argc, const char **argv)
 struct branch_info {
        char *remote_name;
        struct string_list merge;
-       enum { NO_REBASE, NORMAL_REBASE, INTERACTIVE_REBASE } rebase;
+       enum {
+               NO_REBASE, NORMAL_REBASE, INTERACTIVE_REBASE, REBASE_MERGES
+       } rebase;
 };
 
 static struct string_list branch_list = STRING_LIST_INIT_NODUP;
@@ -307,6 +309,8 @@ static int config_read_branches(const char *key, const char *value, void *cb)
                                info->rebase = v;
                        else if (!strcmp(value, "preserve"))
                                info->rebase = NORMAL_REBASE;
+                       else if (!strcmp(value, "merges"))
+                               info->rebase = REBASE_MERGES;
                        else if (!strcmp(value, "interactive"))
                                info->rebase = INTERACTIVE_REBASE;
                }
@@ -962,9 +966,15 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data)
 
        printf("    %-*s ", show_info->width, item->string);
        if (branch_info->rebase) {
-               printf_ln(branch_info->rebase == INTERACTIVE_REBASE
-                         ? _("rebases interactively onto remote %s")
-                         : _("rebases onto remote %s"), merge->items[0].string);
+               const char *msg;
+               if (branch_info->rebase == INTERACTIVE_REBASE)
+                       msg = _("rebases interactively onto remote %s");
+               else if (branch_info->rebase == REBASE_MERGES)
+                       msg = _("rebases interactively (with merges) onto "
+                               "remote %s");
+               else
+                       msg = _("rebases onto remote %s");
+               printf_ln(msg, merge->items[0].string);
                return 0;
        } else if (show_info->any_rebase) {
                printf_ln(_(" merges with remote %s"), merge->items[0].string);
index 7bdb40142f9261dac6514d98ee01dc44e595ec71..6c636e159eaf2d67d617c459aceddd7423e326ab 100644 (file)
@@ -86,7 +86,8 @@ static void remove_pack_on_signal(int signo)
  * have a corresponding .keep or .promisor file. These packs are not to
  * be kept if we are going to pack everything into one file.
  */
-static void get_non_kept_pack_filenames(struct string_list *fname_list)
+static void get_non_kept_pack_filenames(struct string_list *fname_list,
+                                       const struct string_list *extra_keep)
 {
        DIR *dir;
        struct dirent *e;
@@ -97,6 +98,14 @@ static void get_non_kept_pack_filenames(struct string_list *fname_list)
 
        while ((e = readdir(dir)) != NULL) {
                size_t len;
+               int i;
+
+               for (i = 0; i < extra_keep->nr; i++)
+                       if (!fspathcmp(e->d_name, extra_keep->items[i].string))
+                               break;
+               if (extra_keep->nr > 0 && i < extra_keep->nr)
+                       continue;
+
                if (!strip_suffix(e->d_name, ".pack", &len))
                        continue;
 
@@ -148,7 +157,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
        struct string_list rollback = STRING_LIST_INIT_NODUP;
        struct string_list existing_packs = STRING_LIST_INIT_DUP;
        struct strbuf line = STRBUF_INIT;
-       int ext, ret, failed;
+       int i, ext, ret, failed;
        FILE *out;
 
        /* variables to be filled by option parsing */
@@ -160,6 +169,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
        const char *depth = NULL;
        const char *threads = NULL;
        const char *max_pack_size = NULL;
+       struct string_list keep_pack_list = STRING_LIST_INIT_NODUP;
        int no_reuse_delta = 0, no_reuse_object = 0;
        int no_update_server_info = 0;
        int quiet = 0;
@@ -200,6 +210,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
                                N_("maximum size of each packfile")),
                OPT_BOOL(0, "pack-kept-objects", &pack_kept_objects,
                                N_("repack objects in packs marked with .keep")),
+               OPT_STRING_LIST(0, "keep-pack", &keep_pack_list, N_("name"),
+                               N_("do not repack this pack")),
                OPT_END()
        };
 
@@ -230,6 +242,9 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
        argv_array_push(&cmd.args, "--keep-true-parents");
        if (!pack_kept_objects)
                argv_array_push(&cmd.args, "--honor-pack-keep");
+       for (i = 0; i < keep_pack_list.nr; i++)
+               argv_array_pushf(&cmd.args, "--keep-pack=%s",
+                                keep_pack_list.items[i].string);
        argv_array_push(&cmd.args, "--non-empty");
        argv_array_push(&cmd.args, "--all");
        argv_array_push(&cmd.args, "--reflog");
@@ -254,7 +269,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
                argv_array_push(&cmd.args, "--write-bitmap-index");
 
        if (pack_everything & ALL_INTO_ONE) {
-               get_non_kept_pack_filenames(&existing_packs);
+               get_non_kept_pack_filenames(&existing_packs, &keep_pack_list);
 
                if (existing_packs.nr && delete_redundant) {
                        if (unpack_unreachable) {
index 237ea656cfb6297da175c3d4681edb748883ed55..6da2411e14b9f94f309671e14124ef32d89ef528 100644 (file)
@@ -22,6 +22,7 @@ static const char * const git_replace_usage[] = {
        N_("git replace [-f] <object> <replacement>"),
        N_("git replace [-f] --edit <object>"),
        N_("git replace [-f] --graft <commit> [<parent>...]"),
+       N_("git replace [-f] --convert-graft-file"),
        N_("git replace -d <object>..."),
        N_("git replace [--format=<format>] [-l [<pattern>]]"),
        NULL
@@ -55,8 +56,9 @@ static int show_reference(const char *refname, const struct object_id *oid,
                        if (get_oid(refname, &object))
                                return error("Failed to resolve '%s' as a valid ref.", refname);
 
-                       obj_type = oid_object_info(&object, NULL);
-                       repl_type = oid_object_info(oid, NULL);
+                       obj_type = oid_object_info(the_repository, &object,
+                                                  NULL);
+                       repl_type = oid_object_info(the_repository, oid, NULL);
 
                        printf("%s (%s) -> %s (%s)\n", refname, type_name(obj_type),
                               oid_to_hex(oid), type_name(repl_type));
@@ -81,9 +83,9 @@ static int list_replace_refs(const char *pattern, const char *format)
        else if (!strcmp(format, "long"))
                data.format = REPLACE_FORMAT_LONG;
        else
-               die("invalid replace format '%s'\n"
-                   "valid formats are 'short', 'medium' and 'long'\n",
-                   format);
+               return error("invalid replace format '%s'\n"
+                            "valid formats are 'short', 'medium' and 'long'\n",
+                            format);
 
        for_each_replace_ref(the_repository, show_reference, (void *)&data);
 
@@ -136,7 +138,7 @@ static int delete_replace_ref(const char *name, const char *ref,
        return 0;
 }
 
-static void check_ref_valid(struct object_id *object,
+static int check_ref_valid(struct object_id *object,
                            struct object_id *prev,
                            struct strbuf *ref,
                            int force)
@@ -144,12 +146,13 @@ static void check_ref_valid(struct object_id *object,
        strbuf_reset(ref);
        strbuf_addf(ref, "%s%s", git_replace_ref_base, oid_to_hex(object));
        if (check_refname_format(ref->buf, 0))
-               die("'%s' is not a valid ref name.", ref->buf);
+               return error("'%s' is not a valid ref name.", ref->buf);
 
        if (read_ref(ref->buf, prev))
                oidclr(prev);
        else if (!force)
-               die("replace ref '%s' already exists", ref->buf);
+               return error("replace ref '%s' already exists", ref->buf);
+       return 0;
 }
 
 static int replace_object_oid(const char *object_ref,
@@ -163,28 +166,33 @@ static int replace_object_oid(const char *object_ref,
        struct strbuf ref = STRBUF_INIT;
        struct ref_transaction *transaction;
        struct strbuf err = STRBUF_INIT;
+       int res = 0;
 
-       obj_type = oid_object_info(object, NULL);
-       repl_type = oid_object_info(repl, NULL);
+       obj_type = oid_object_info(the_repository, object, NULL);
+       repl_type = oid_object_info(the_repository, repl, NULL);
        if (!force && obj_type != repl_type)
-               die("Objects must be of the same type.\n"
-                   "'%s' points to a replaced object of type '%s'\n"
-                   "while '%s' points to a replacement object of type '%s'.",
-                   object_ref, type_name(obj_type),
-                   replace_ref, type_name(repl_type));
-
-       check_ref_valid(object, &prev, &ref, force);
+               return error("Objects must be of the same type.\n"
+                            "'%s' points to a replaced object of type '%s'\n"
+                            "while '%s' points to a replacement object of "
+                            "type '%s'.",
+                            object_ref, type_name(obj_type),
+                            replace_ref, type_name(repl_type));
+
+       if (check_ref_valid(object, &prev, &ref, force)) {
+               strbuf_release(&ref);
+               return -1;
+       }
 
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
            ref_transaction_update(transaction, ref.buf, repl, &prev,
                                   0, NULL, &err) ||
            ref_transaction_commit(transaction, &err))
-               die("%s", err.buf);
+               res = error("%s", err.buf);
 
        ref_transaction_free(transaction);
        strbuf_release(&ref);
-       return 0;
+       return res;
 }
 
 static int replace_object(const char *object_ref, const char *replace_ref, int force)
@@ -192,9 +200,11 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f
        struct object_id object, repl;
 
        if (get_oid(object_ref, &object))
-               die("Failed to resolve '%s' as a valid ref.", object_ref);
+               return error("Failed to resolve '%s' as a valid ref.",
+                            object_ref);
        if (get_oid(replace_ref, &repl))
-               die("Failed to resolve '%s' as a valid ref.", replace_ref);
+               return error("Failed to resolve '%s' as a valid ref.",
+                            replace_ref);
 
        return replace_object_oid(object_ref, &object, replace_ref, &repl, force);
 }
@@ -204,7 +214,7 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f
  * If "raw" is true, then the object's raw contents are printed according to
  * "type". Otherwise, we pretty-print the contents for human editing.
  */
-static void export_object(const struct object_id *oid, enum object_type type,
+static int export_object(const struct object_id *oid, enum object_type type,
                          int raw, const char *filename)
 {
        struct child_process cmd = CHILD_PROCESS_INIT;
@@ -212,7 +222,7 @@ static void export_object(const struct object_id *oid, enum object_type type,
 
        fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
        if (fd < 0)
-               die_errno("unable to open %s for writing", filename);
+               return error_errno("unable to open %s for writing", filename);
 
        argv_array_push(&cmd.args, "--no-replace-objects");
        argv_array_push(&cmd.args, "cat-file");
@@ -225,7 +235,8 @@ static void export_object(const struct object_id *oid, enum object_type type,
        cmd.out = fd;
 
        if (run_command(&cmd))
-               die("cat-file reported failure");
+               return error("cat-file reported failure");
+       return 0;
 }
 
 /*
@@ -233,14 +244,14 @@ static void export_object(const struct object_id *oid, enum object_type type,
  * interpreting it as "type", and writing the result to the object database.
  * The sha1 of the written object is returned via sha1.
  */
-static void import_object(struct object_id *oid, enum object_type type,
+static int import_object(struct object_id *oid, enum object_type type,
                          int raw, const char *filename)
 {
        int fd;
 
        fd = open(filename, O_RDONLY);
        if (fd < 0)
-               die_errno("unable to open %s for reading", filename);
+               return error_errno("unable to open %s for reading", filename);
 
        if (!raw && type == OBJ_TREE) {
                const char *argv[] = { "mktree", NULL };
@@ -252,27 +263,40 @@ static void import_object(struct object_id *oid, enum object_type type,
                cmd.in = fd;
                cmd.out = -1;
 
-               if (start_command(&cmd))
-                       die("unable to spawn mktree");
+               if (start_command(&cmd)) {
+                       close(fd);
+                       return error("unable to spawn mktree");
+               }
 
-               if (strbuf_read(&result, cmd.out, 41) < 0)
-                       die_errno("unable to read from mktree");
+               if (strbuf_read(&result, cmd.out, 41) < 0) {
+                       error_errno("unable to read from mktree");
+                       close(fd);
+                       close(cmd.out);
+                       return -1;
+               }
                close(cmd.out);
 
-               if (finish_command(&cmd))
-                       die("mktree reported failure");
-               if (get_oid_hex(result.buf, oid) < 0)
-                       die("mktree did not return an object name");
+               if (finish_command(&cmd)) {
+                       strbuf_release(&result);
+                       return error("mktree reported failure");
+               }
+               if (get_oid_hex(result.buf, oid) < 0) {
+                       strbuf_release(&result);
+                       return error("mktree did not return an object name");
+               }
 
                strbuf_release(&result);
        } else {
                struct stat st;
                int flags = HASH_FORMAT_CHECK | HASH_WRITE_OBJECT;
 
-               if (fstat(fd, &st) < 0)
-                       die_errno("unable to fstat %s", filename);
+               if (fstat(fd, &st) < 0) {
+                       error_errno("unable to fstat %s", filename);
+                       close(fd);
+                       return -1;
+               }
                if (index_fd(oid, fd, &st, type, NULL, flags) < 0)
-                       die("unable to write object to database");
+                       return error("unable to write object to database");
                /* index_fd close()s fd for us */
        }
 
@@ -280,30 +304,43 @@ static void import_object(struct object_id *oid, enum object_type type,
         * No need to close(fd) here; both run-command and index-fd
         * will have done it for us.
         */
+       return 0;
 }
 
 static int edit_and_replace(const char *object_ref, int force, int raw)
 {
-       char *tmpfile = git_pathdup("REPLACE_EDITOBJ");
+       char *tmpfile;
        enum object_type type;
        struct object_id old_oid, new_oid, prev;
        struct strbuf ref = STRBUF_INIT;
 
        if (get_oid(object_ref, &old_oid) < 0)
-               die("Not a valid object name: '%s'", object_ref);
+               return error("Not a valid object name: '%s'", object_ref);
 
-       type = oid_object_info(&old_oid, NULL);
+       type = oid_object_info(the_repository, &old_oid, NULL);
        if (type < 0)
-               die("unable to get object type for %s", oid_to_hex(&old_oid));
+               return error("unable to get object type for %s",
+                            oid_to_hex(&old_oid));
 
-       check_ref_valid(&old_oid, &prev, &ref, force);
+       if (check_ref_valid(&old_oid, &prev, &ref, force)) {
+               strbuf_release(&ref);
+               return -1;
+       }
        strbuf_release(&ref);
 
-       export_object(&old_oid, type, raw, tmpfile);
-       if (launch_editor(tmpfile, NULL, NULL) < 0)
-               die("editing object file failed");
-       import_object(&new_oid, type, raw, tmpfile);
-
+       tmpfile = git_pathdup("REPLACE_EDITOBJ");
+       if (export_object(&old_oid, type, raw, tmpfile)) {
+               free(tmpfile);
+               return -1;
+       }
+       if (launch_editor(tmpfile, NULL, NULL) < 0) {
+               free(tmpfile);
+               return error("editing object file failed");
+       }
+       if (import_object(&new_oid, type, raw, tmpfile)) {
+               free(tmpfile);
+               return -1;
+       }
        free(tmpfile);
 
        if (!oidcmp(&old_oid, &new_oid))
@@ -312,7 +349,7 @@ static int edit_and_replace(const char *object_ref, int force, int raw)
        return replace_object_oid(object_ref, &old_oid, "replacement", &new_oid, force);
 }
 
-static void replace_parents(struct strbuf *buf, int argc, const char **argv)
+static int replace_parents(struct strbuf *buf, int argc, const char **argv)
 {
        struct strbuf new_parents = STRBUF_INIT;
        const char *parent_start, *parent_end;
@@ -329,9 +366,15 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv)
        /* prepare new parents */
        for (i = 0; i < argc; i++) {
                struct object_id oid;
-               if (get_oid(argv[i], &oid) < 0)
-                       die(_("Not a valid object name: '%s'"), argv[i]);
-               lookup_commit_or_die(&oid, argv[i]);
+               if (get_oid(argv[i], &oid) < 0) {
+                       strbuf_release(&new_parents);
+                       return error(_("Not a valid object name: '%s'"),
+                                    argv[i]);
+               }
+               if (!lookup_commit_reference(&oid)) {
+                       strbuf_release(&new_parents);
+                       return error(_("could not parse %s"), argv[i]);
+               }
                strbuf_addf(&new_parents, "parent %s\n", oid_to_hex(&oid));
        }
 
@@ -340,6 +383,7 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv)
                      new_parents.buf, new_parents.len);
 
        strbuf_release(&new_parents);
+       return 0;
 }
 
 struct check_mergetag_data {
@@ -347,7 +391,7 @@ struct check_mergetag_data {
        const char **argv;
 };
 
-static void check_one_mergetag(struct commit *commit,
+static int check_one_mergetag(struct commit *commit,
                               struct commit_extra_header *extra,
                               void *data)
 {
@@ -360,33 +404,35 @@ static void check_one_mergetag(struct commit *commit,
        hash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &tag_oid);
        tag = lookup_tag(&tag_oid);
        if (!tag)
-               die(_("bad mergetag in commit '%s'"), ref);
+               return error(_("bad mergetag in commit '%s'"), ref);
        if (parse_tag_buffer(tag, extra->value, extra->len))
-               die(_("malformed mergetag in commit '%s'"), ref);
+               return error(_("malformed mergetag in commit '%s'"), ref);
 
        /* iterate over new parents */
        for (i = 1; i < mergetag_data->argc; i++) {
                struct object_id oid;
                if (get_oid(mergetag_data->argv[i], &oid) < 0)
-                       die(_("Not a valid object name: '%s'"), mergetag_data->argv[i]);
+                       return error(_("Not a valid object name: '%s'"),
+                                    mergetag_data->argv[i]);
                if (!oidcmp(&tag->tagged->oid, &oid))
-                       return; /* found */
+                       return 0; /* found */
        }
 
-       die(_("original commit '%s' contains mergetag '%s' that is discarded; "
-             "use --edit instead of --graft"), ref, oid_to_hex(&tag_oid));
+       return error(_("original commit '%s' contains mergetag '%s' that is "
+                      "discarded; use --edit instead of --graft"), ref,
+                    oid_to_hex(&tag_oid));
 }
 
-static void check_mergetags(struct commit *commit, int argc, const char **argv)
+static int check_mergetags(struct commit *commit, int argc, const char **argv)
 {
        struct check_mergetag_data mergetag_data;
 
        mergetag_data.argc = argc;
        mergetag_data.argv = argv;
-       for_each_mergetag(check_one_mergetag, commit, &mergetag_data);
+       return for_each_mergetag(check_one_mergetag, commit, &mergetag_data);
 }
 
-static int create_graft(int argc, const char **argv, int force)
+static int create_graft(int argc, const char **argv, int force, int gentle)
 {
        struct object_id old_oid, new_oid;
        const char *old_ref = argv[0];
@@ -396,33 +442,81 @@ static int create_graft(int argc, const char **argv, int force)
        unsigned long size;
 
        if (get_oid(old_ref, &old_oid) < 0)
-               die(_("Not a valid object name: '%s'"), old_ref);
-       commit = lookup_commit_or_die(&old_oid, old_ref);
+               return error(_("Not a valid object name: '%s'"), old_ref);
+       commit = lookup_commit_reference(&old_oid);
+       if (!commit)
+               return error(_("could not parse %s"), old_ref);
 
        buffer = get_commit_buffer(commit, &size);
        strbuf_add(&buf, buffer, size);
        unuse_commit_buffer(commit, buffer);
 
-       replace_parents(&buf, argc - 1, &argv[1]);
+       if (replace_parents(&buf, argc - 1, &argv[1]) < 0) {
+               strbuf_release(&buf);
+               return -1;
+       }
 
        if (remove_signature(&buf)) {
                warning(_("the original commit '%s' has a gpg signature."), old_ref);
                warning(_("the signature will be removed in the replacement commit!"));
        }
 
-       check_mergetags(commit, argc, argv);
+       if (check_mergetags(commit, argc, argv)) {
+               strbuf_release(&buf);
+               return -1;
+       }
 
-       if (write_object_file(buf.buf, buf.len, commit_type, &new_oid))
-               die(_("could not write replacement commit for: '%s'"), old_ref);
+       if (write_object_file(buf.buf, buf.len, commit_type, &new_oid)) {
+               strbuf_release(&buf);
+               return error(_("could not write replacement commit for: '%s'"),
+                            old_ref);
+       }
 
        strbuf_release(&buf);
 
-       if (!oidcmp(&old_oid, &new_oid))
+       if (!oidcmp(&old_oid, &new_oid)) {
+               if (gentle) {
+                       warning("graft for '%s' unnecessary", oid_to_hex(&old_oid));
+                       return 0;
+               }
                return error("new commit is the same as the old one: '%s'", oid_to_hex(&old_oid));
+       }
 
        return replace_object_oid(old_ref, &old_oid, "replacement", &new_oid, force);
 }
 
+static int convert_graft_file(int force)
+{
+       const char *graft_file = get_graft_file();
+       FILE *fp = fopen_or_warn(graft_file, "r");
+       struct strbuf buf = STRBUF_INIT, err = STRBUF_INIT;
+       struct argv_array args = ARGV_ARRAY_INIT;
+
+       if (!fp)
+               return -1;
+
+       while (strbuf_getline(&buf, fp) != EOF) {
+               if (*buf.buf == '#')
+                       continue;
+
+               argv_array_split(&args, buf.buf);
+               if (args.argc && create_graft(args.argc, args.argv, force, 1))
+                       strbuf_addf(&err, "\n\t%s", buf.buf);
+               argv_array_clear(&args);
+       }
+       fclose(fp);
+
+       strbuf_release(&buf);
+
+       if (!err.len)
+               return unlink_or_warn(graft_file);
+
+       warning(_("could not convert the following graft(s):\n%s"), err.buf);
+       strbuf_release(&err);
+
+       return -1;
+}
+
 int cmd_replace(int argc, const char **argv, const char *prefix)
 {
        int force = 0;
@@ -434,6 +528,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
                MODE_DELETE,
                MODE_EDIT,
                MODE_GRAFT,
+               MODE_CONVERT_GRAFT_FILE,
                MODE_REPLACE
        } cmdmode = MODE_UNSPECIFIED;
        struct option options[] = {
@@ -441,6 +536,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
                OPT_CMDMODE('d', "delete", &cmdmode, N_("delete replace refs"), MODE_DELETE),
                OPT_CMDMODE('e', "edit", &cmdmode, N_("edit existing object"), MODE_EDIT),
                OPT_CMDMODE('g', "graft", &cmdmode, N_("change a commit's parents"), MODE_GRAFT),
+               OPT_CMDMODE(0, "convert-graft-file", &cmdmode, N_("convert existing graft file"), MODE_CONVERT_GRAFT_FILE),
                OPT_BOOL_F('f', "force", &force, N_("replace the ref if it exists"),
                           PARSE_OPT_NOCOMPLETE),
                OPT_BOOL(0, "raw", &raw, N_("do not pretty-print contents for --edit")),
@@ -463,7 +559,8 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
        if (force &&
            cmdmode != MODE_REPLACE &&
            cmdmode != MODE_EDIT &&
-           cmdmode != MODE_GRAFT)
+           cmdmode != MODE_GRAFT &&
+           cmdmode != MODE_CONVERT_GRAFT_FILE)
                usage_msg_opt("-f only makes sense when writing a replacement",
                              git_replace_usage, options);
 
@@ -494,7 +591,13 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
                if (argc < 1)
                        usage_msg_opt("-g needs at least one argument",
                                      git_replace_usage, options);
-               return create_graft(argc, argv, force);
+               return create_graft(argc, argv, force, 0);
+
+       case MODE_CONVERT_GRAFT_FILE:
+               if (argc != 0)
+                       usage_msg_opt("--convert-graft-file takes no argument",
+                                     git_replace_usage, options);
+               return !!convert_graft_file(force);
 
        case MODE_LIST:
                if (argc > 1)
@@ -503,6 +606,6 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
                return list_replace_refs(argv[0], format);
 
        default:
-               die("BUG: invalid cmdmode %d", (int)cmdmode);
+               BUG("invalid cmdmode %d", (int)cmdmode);
        }
 }
index 7f1c3f02a302128d6c00c35b8783c1a62353b37a..a862c70fab99290b5faf3e700e88194589583329 100644 (file)
@@ -314,7 +314,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
        unborn = !strcmp(rev, "HEAD") && get_oid("HEAD", &oid);
        if (unborn) {
                /* reset on unborn branch: treat as reset to empty tree */
-               hashcpy(oid.hash, EMPTY_TREE_SHA1_BIN);
+               oidcpy(&oid, the_hash_algo->empty_tree);
        } else if (!pathspec.nr) {
                struct commit *commit;
                if (get_oid_committish(rev, &oid))
index 36b208778280e6019d9bc4fb4063dff3d44f08e6..4f49e96bfd0166aeb6f9875a688ff0dd83d631b5 100644 (file)
@@ -282,6 +282,10 @@ static int try_difference(const char *arg)
                        struct commit *a, *b;
                        a = lookup_commit_reference(&start_oid);
                        b = lookup_commit_reference(&end_oid);
+                       if (!a || !b) {
+                               *dotdot = '.';
+                               return 0;
+                       }
                        exclude = get_merge_bases(a, b);
                        while (exclude) {
                                struct commit *commit = pop_commit(&exclude);
@@ -328,12 +332,12 @@ static int try_parent_shorthands(const char *arg)
                return 0;
 
        *dotdot = 0;
-       if (get_oid_committish(arg, &oid)) {
+       if (get_oid_committish(arg, &oid) ||
+           !(commit = lookup_commit_reference(&oid))) {
                *dotdot = '^';
                return 0;
        }
 
-       commit = lookup_commit_reference(&oid);
        if (exclude_parent &&
            exclude_parent > commit_list_count(commit->parents)) {
                *dotdot = '^';
@@ -887,8 +891,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                                if (read_cache() < 0)
                                        die(_("Could not read the index"));
                                if (the_index.split_index) {
-                                       const unsigned char *sha1 = the_index.split_index->base_sha1;
-                                       const char *path = git_path("sharedindex.%s", sha1_to_hex(sha1));
+                                       const struct object_id *oid = &the_index.split_index->base_oid;
+                                       const char *path = git_path("sharedindex.%s", oid_to_hex(oid));
                                        strbuf_reset(&buf);
                                        puts(relative_path(path, prefix, &buf));
                                }
index 5b6fc7ee818be4a4f060dc06f12fb45a25a2ea9b..65b448ef8ee91079194841762d4b7a2193eec9a9 100644 (file)
@@ -233,8 +233,6 @@ static int check_local_mod(struct object_id *head, int index_only)
        return errs;
 }
 
-static struct lock_file lock_file;
-
 static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0;
 static int ignore_unmatch = 0;
 
@@ -251,6 +249,7 @@ static struct option builtin_rm_options[] = {
 
 int cmd_rm(int argc, const char **argv, const char *prefix)
 {
+       struct lock_file lock_file = LOCK_INIT;
        int i;
        struct pathspec pathspec;
        char *seen;
index 88a149a2c3846cb0dc103de62a72d256e282e566..bd250ca2164b31356fb42406351761cd2381d115 100644 (file)
@@ -1066,7 +1066,7 @@ static int module_deinit(int argc, const char **argv, const char *prefix)
 }
 
 static int clone_submodule(const char *path, const char *gitdir, const char *url,
-                          const char *depth, struct string_list *reference,
+                          const char *depth, struct string_list *reference, int dissociate,
                           int quiet, int progress)
 {
        struct child_process cp = CHILD_PROCESS_INIT;
@@ -1085,6 +1085,8 @@ static int clone_submodule(const char *path, const char *gitdir, const char *url
                        argv_array_pushl(&cp.args, "--reference",
                                         item->string, NULL);
        }
+       if (dissociate)
+               argv_array_push(&cp.args, "--dissociate");
        if (gitdir && *gitdir)
                argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL);
 
@@ -1200,6 +1202,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)
        char *p, *path = NULL, *sm_gitdir;
        struct strbuf sb = STRBUF_INIT;
        struct string_list reference = STRING_LIST_INIT_NODUP;
+       int dissociate = 0;
        char *sm_alternate = NULL, *error_strategy = NULL;
 
        struct option module_clone_options[] = {
@@ -1218,6 +1221,8 @@ static int module_clone(int argc, const char **argv, const char *prefix)
                OPT_STRING_LIST(0, "reference", &reference,
                           N_("repo"),
                           N_("reference repository")),
+               OPT_BOOL(0, "dissociate", &dissociate,
+                          N_("use --reference only while cloning")),
                OPT_STRING(0, "depth", &depth,
                           N_("string"),
                           N_("depth for shallow clones")),
@@ -1257,7 +1262,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)
 
                prepare_possible_alternates(name, &reference);
 
-               if (clone_submodule(path, sm_gitdir, url, depth, &reference,
+               if (clone_submodule(path, sm_gitdir, url, depth, &reference, dissociate,
                                    quiet, progress))
                        die(_("clone of '%s' into submodule path '%s' failed"),
                            url, path);
@@ -1309,6 +1314,7 @@ struct submodule_update_clone {
        int quiet;
        int recommend_shallow;
        struct string_list references;
+       int dissociate;
        const char *depth;
        const char *recursive_prefix;
        const char *prefix;
@@ -1324,7 +1330,7 @@ struct submodule_update_clone {
        int failed_clones_nr, failed_clones_alloc;
 };
 #define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \
-       SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, \
+       SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, 0, \
        NULL, NULL, NULL, \
        STRING_LIST_INIT_DUP, 0, NULL, 0, 0}
 
@@ -1451,6 +1457,8 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
                for_each_string_list_item(item, &suc->references)
                        argv_array_pushl(&child->args, "--reference", item->string, NULL);
        }
+       if (suc->dissociate)
+               argv_array_push(&child->args, "--dissociate");
        if (suc->depth)
                argv_array_push(&child->args, suc->depth);
 
@@ -1584,6 +1592,8 @@ static int update_clone(int argc, const char **argv, const char *prefix)
                           N_("rebase, merge, checkout or none")),
                OPT_STRING_LIST(0, "reference", &suc.references, N_("repo"),
                           N_("reference repository")),
+               OPT_BOOL(0, "dissociate", &suc.dissociate,
+                          N_("use --reference only while cloning")),
                OPT_STRING(0, "depth", &suc.depth, "<depth>",
                           N_("Create a shallow clone truncated to the "
                              "specified number of revisions")),
@@ -1827,6 +1837,29 @@ static int is_active(int argc, const char **argv, const char *prefix)
        return !is_submodule_active(the_repository, argv[1]);
 }
 
+/*
+ * Exit non-zero if any of the submodule names given on the command line is
+ * invalid. If no names are given, filter stdin to print only valid names
+ * (which is primarily intended for testing).
+ */
+static int check_name(int argc, const char **argv, const char *prefix)
+{
+       if (argc > 1) {
+               while (*++argv) {
+                       if (check_submodule_name(*argv) < 0)
+                               return 1;
+               }
+       } else {
+               struct strbuf buf = STRBUF_INIT;
+               while (strbuf_getline(&buf, stdin) != EOF) {
+                       if (!check_submodule_name(buf.buf))
+                               printf("%s\n", buf.buf);
+               }
+               strbuf_release(&buf);
+       }
+       return 0;
+}
+
 #define SUPPORT_SUPER_PREFIX (1<<0)
 
 struct cmd_struct {
@@ -1852,6 +1885,7 @@ static struct cmd_struct commands[] = {
        {"push-check", push_check, 0},
        {"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},
        {"is-active", is_active, 0},
+       {"check-name", check_name, 0},
 };
 
 int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
index 46a5c6a1da17fa12ff65f33889527ec76869c3cd..5d0dd112408419a0090cbe179d69c0ea709f22a7 100644 (file)
@@ -212,7 +212,7 @@ static void create_tag(const struct object_id *object, const char *tag,
        struct strbuf header = STRBUF_INIT;
        char *path = NULL;
 
-       type = oid_object_info(object, NULL);
+       type = oid_object_info(the_repository, object, NULL);
        if (type <= OBJ_NONE)
            die(_("bad object type."));
 
@@ -298,7 +298,7 @@ static void create_reflog_msg(const struct object_id *oid, struct strbuf *sb)
        }
 
        strbuf_addstr(sb, " (");
-       type = oid_object_info(oid, NULL);
+       type = oid_object_info(the_repository, oid, NULL);
        switch (type) {
        default:
                strbuf_addstr(sb, "object of unknown type");
index b7755c6cc5a05a536666d0ae2fd9d0c3171a6cfd..6e81ca8ca28a67ce3d7bc0d62556fd41eda2ba52 100644 (file)
@@ -199,7 +199,7 @@ static int check_object(struct object *obj, int type, void *data, struct fsck_op
 
        if (!(obj->flags & FLAG_OPEN)) {
                unsigned long size;
-               int type = oid_object_info(&obj->oid, &size);
+               int type = oid_object_info(the_repository, &obj->oid, &size);
                if (type != obj->type || type <= 0)
                        die("object of unexpected type");
                obj->flags |= FLAG_WRITTEN;
@@ -210,7 +210,7 @@ static int check_object(struct object *obj, int type, void *data, struct fsck_op
        if (!obj_buf)
                die("Whoops! Cannot find object '%s'", oid_to_hex(&obj->oid));
        if (fsck_object(obj, obj_buf->buffer, obj_buf->size, &fsck_options))
-               die("Error in object");
+               die("fsck error in packed object");
        fsck_options.walk = check_object;
        if (fsck_walk(obj, NULL, &fsck_options))
                die("Error on reachable objects of %s", oid_to_hex(&obj->oid));
@@ -572,8 +572,11 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
        unpack_all();
        the_hash_algo->update_fn(&ctx, buffer, offset);
        the_hash_algo->final_fn(oid.hash, &ctx);
-       if (strict)
+       if (strict) {
                write_rest();
+               if (fsck_finish(&fsck_options))
+                       die(_("fsck error in pack objects"));
+       }
        if (hashcmp(fill(the_hash_algo->rawsz), oid.hash))
                die("final sha1 did not match");
        use(the_hash_algo->rawsz);
index 10d070a76fb1b0b94c058f60934bb05db37a4164..a8709a26ec4b8bb79cbb20bfa4a7892e063c804d 100644 (file)
@@ -364,10 +364,9 @@ static int process_directory(const char *path, int len, struct stat *st)
        return error("%s: is a directory - add files inside instead", path);
 }
 
-static int process_path(const char *path)
+static int process_path(const char *path, struct stat *st, int stat_errno)
 {
        int pos, len;
-       struct stat st;
        const struct cache_entry *ce;
 
        len = strlen(path);
@@ -391,13 +390,13 @@ static int process_path(const char *path)
         * First things first: get the stat information, to decide
         * what to do about the pathname!
         */
-       if (lstat(path, &st) < 0)
-               return process_lstat_error(path, errno);
+       if (stat_errno)
+               return process_lstat_error(path, stat_errno);
 
-       if (S_ISDIR(st.st_mode))
-               return process_directory(path, len, &st);
+       if (S_ISDIR(st->st_mode))
+               return process_directory(path, len, st);
 
-       return add_one_path(ce, path, len, &st);
+       return add_one_path(ce, path, len, st);
 }
 
 static int add_cacheinfo(unsigned int mode, const struct object_id *oid,
@@ -406,7 +405,7 @@ static int add_cacheinfo(unsigned int mode, const struct object_id *oid,
        int size, len, option;
        struct cache_entry *ce;
 
-       if (!verify_path(path))
+       if (!verify_path(path, mode))
                return error("Invalid path '%s'", path);
 
        len = strlen(path);
@@ -449,7 +448,18 @@ static void chmod_path(char flip, const char *path)
 
 static void update_one(const char *path)
 {
-       if (!verify_path(path)) {
+       int stat_errno = 0;
+       struct stat st;
+
+       if (mark_valid_only || mark_skip_worktree_only || force_remove ||
+           mark_fsmonitor_only)
+               st.st_mode = 0;
+       else if (lstat(path, &st) < 0) {
+               st.st_mode = 0;
+               stat_errno = errno;
+       } /* else stat is valid */
+
+       if (!verify_path(path, st.st_mode)) {
                fprintf(stderr, "Ignoring path %s\n", path);
                return;
        }
@@ -475,7 +485,7 @@ static void update_one(const char *path)
                report("remove '%s'", path);
                return;
        }
-       if (process_path(path))
+       if (process_path(path, &st, stat_errno))
                die("Unable to process path %s", path);
        report("add '%s'", path);
 }
@@ -545,7 +555,7 @@ static void read_index_info(int nul_term_line)
                        path_name = uq.buf;
                }
 
-               if (!verify_path(path_name)) {
+               if (!verify_path(path_name, mode)) {
                        fprintf(stderr, "Ignoring path %s\n", path_name);
                        continue;
                }
@@ -1164,7 +1174,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
                report(_("Untracked cache enabled for '%s'"), get_git_work_tree());
                break;
        default:
-               die("BUG: bad untracked_cache value: %d", untracked_cache);
+               BUG("bad untracked_cache value: %d", untracked_cache);
        }
 
        if (fsmonitor > 0) {
index 30647b30c5337e23c70720558a5ab96e9a8d68fd..5c7d2bb1807f942139b3ec41b426320e4b0cfc2a 100644 (file)
@@ -29,8 +29,6 @@ struct add_opts {
        int detach;
        int checkout;
        int keep_locked;
-       const char *new_branch;
-       int force_new_branch;
 };
 
 static int show_only;
@@ -298,8 +296,6 @@ static int add_worktree(const char *path, const char *refname,
        strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
        write_file(sb.buf, "../..");
 
-       fprintf_ln(stderr, _("Preparing %s (identifier %s)"), path, name);
-
        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);
        cp.git_cmd = 1;
@@ -366,18 +362,75 @@ static int add_worktree(const char *path, const char *refname,
        return ret;
 }
 
+static void print_preparing_worktree_line(int detach,
+                                         const char *branch,
+                                         const char *new_branch,
+                                         int force_new_branch)
+{
+       if (force_new_branch) {
+               struct commit *commit = lookup_commit_reference_by_name(new_branch);
+               if (!commit)
+                       printf_ln(_("Preparing worktree (new branch '%s')"), new_branch);
+               else
+                       printf_ln(_("Preparing worktree (resetting branch '%s'; was at %s)"),
+                                 new_branch,
+                                 find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV));
+       } else if (new_branch) {
+               printf_ln(_("Preparing worktree (new branch '%s')"), new_branch);
+       } else {
+               struct strbuf s = STRBUF_INIT;
+               if (!detach && !strbuf_check_branch_ref(&s, branch) &&
+                   ref_exists(s.buf))
+                       printf_ln(_("Preparing worktree (checking out '%s')"),
+                                 branch);
+               else {
+                       struct commit *commit = lookup_commit_reference_by_name(branch);
+                       if (!commit)
+                               die(_("invalid reference: %s"), branch);
+                       printf_ln(_("Preparing worktree (detached HEAD %s)"),
+                                 find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV));
+               }
+               strbuf_release(&s);
+       }
+}
+
+static const char *dwim_branch(const char *path, const char **new_branch)
+{
+       int n;
+       const char *s = worktree_basename(path, &n);
+       const char *branchname = xstrndup(s, n);
+       struct strbuf ref = STRBUF_INIT;
+
+       UNLEAK(branchname);
+       if (!strbuf_check_branch_ref(&ref, branchname) &&
+           ref_exists(ref.buf)) {
+               strbuf_release(&ref);
+               return branchname;
+       }
+
+       *new_branch = branchname;
+       if (guess_remote) {
+               struct object_id oid;
+               const char *remote =
+                       unique_tracking_name(*new_branch, &oid);
+               return remote;
+       }
+       return NULL;
+}
+
 static int add(int ac, const char **av, const char *prefix)
 {
        struct add_opts opts;
        const char *new_branch_force = NULL;
        char *path;
        const char *branch;
+       const char *new_branch = NULL;
        const char *opt_track = NULL;
        struct option options[] = {
                OPT__FORCE(&opts.force,
                           N_("checkout <branch> even if already checked out in other worktree"),
                           PARSE_OPT_NOCOMPLETE),
-               OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
+               OPT_STRING('b', NULL, &new_branch, N_("branch"),
                           N_("create a new branch")),
                OPT_STRING('B', NULL, &new_branch_force, N_("branch"),
                           N_("create or reset a branch")),
@@ -395,7 +448,7 @@ static int add(int ac, const char **av, const char *prefix)
        memset(&opts, 0, sizeof(opts));
        opts.checkout = 1;
        ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
-       if (!!opts.detach + !!opts.new_branch + !!new_branch_force > 1)
+       if (!!opts.detach + !!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);
@@ -406,33 +459,25 @@ static int add(int ac, const char **av, const char *prefix)
        if (!strcmp(branch, "-"))
                branch = "@{-1}";
 
-       opts.force_new_branch = !!new_branch_force;
-       if (opts.force_new_branch) {
+       if (new_branch_force) {
                struct strbuf symref = STRBUF_INIT;
 
-               opts.new_branch = new_branch_force;
+               new_branch = new_branch_force;
 
                if (!opts.force &&
-                   !strbuf_check_branch_ref(&symref, opts.new_branch) &&
+                   !strbuf_check_branch_ref(&symref, new_branch) &&
                    ref_exists(symref.buf))
                        die_if_checked_out(symref.buf, 0);
                strbuf_release(&symref);
        }
 
-       if (ac < 2 && !opts.new_branch && !opts.detach) {
-               int n;
-               const char *s = worktree_basename(path, &n);
-               opts.new_branch = xstrndup(s, n);
-               if (guess_remote) {
-                       struct object_id oid;
-                       const char *remote =
-                               unique_tracking_name(opts.new_branch, &oid);
-                       if (remote)
-                               branch = remote;
-               }
+       if (ac < 2 && !new_branch && !opts.detach) {
+               const char *s = dwim_branch(path, &new_branch);
+               if (s)
+                       branch = s;
        }
 
-       if (ac == 2 && !opts.new_branch && !opts.detach) {
+       if (ac == 2 && !new_branch && !opts.detach) {
                struct object_id oid;
                struct commit *commit;
                const char *remote;
@@ -441,25 +486,27 @@ static int add(int ac, const char **av, const char *prefix)
                if (!commit) {
                        remote = unique_tracking_name(branch, &oid);
                        if (remote) {
-                               opts.new_branch = branch;
+                               new_branch = branch;
                                branch = remote;
                        }
                }
        }
 
-       if (opts.new_branch) {
+       print_preparing_worktree_line(opts.detach, branch, new_branch, !!new_branch_force);
+
+       if (new_branch) {
                struct child_process cp = CHILD_PROCESS_INIT;
                cp.git_cmd = 1;
                argv_array_push(&cp.args, "branch");
-               if (opts.force_new_branch)
+               if (new_branch_force)
                        argv_array_push(&cp.args, "--force");
-               argv_array_push(&cp.args, opts.new_branch);
+               argv_array_push(&cp.args, new_branch);
                argv_array_push(&cp.args, branch);
                if (opt_track)
                        argv_array_push(&cp.args, opt_track);
                if (run_command(&cp))
                        return -1;
-               branch = opts.new_branch;
+               branch = new_branch;
        } else if (opt_track) {
                die(_("--[no-]track can only be used if a new branch is created"));
        }
index c0bc8de107a425c10ba750812247c481de463b02..b7e131c47a62603069185d67e5d2758175bc543f 100644 (file)
@@ -230,7 +230,7 @@ static int deflate_to_pack(struct bulk_checkin_state *state,
                 * pack, and write into it.
                 */
                if (!idx)
-                       die("BUG: should not happen");
+                       BUG("should not happen");
                hashfile_truncate(state->f, &checkpoint);
                state->offset = checkpoint.offset;
                finish_bulk_checkin(state);
index 902c9b54485be2000696a697472fa10d97b36153..160bbfdc64ec2876bdc685d253bf537d1c739c1f 100644 (file)
--- a/bundle.c
+++ b/bundle.c
@@ -409,7 +409,7 @@ static int write_bundle_refs(int bundle_fd, struct rev_info *revs)
 int create_bundle(struct bundle_header *header, const char *path,
                  int argc, const char **argv)
 {
-       static struct lock_file lock;
+       struct lock_file lock = LOCK_INIT;
        int bundle_fd = -1;
        int bundle_to_stdout;
        int ref_count = 0;
index 6a555f4d431f9f6dbf8dad06d75a4ec81a4254fd..25663825b553b4e4590e3b4aaad54b4f5950aef8 100644 (file)
@@ -385,7 +385,7 @@ static int update_one(struct cache_tree *it,
                /*
                 * "sub" can be an empty tree if all subentries are i-t-a.
                 */
-               if (contains_ita && !oidcmp(oid, &empty_tree_oid))
+               if (contains_ita && is_empty_tree_oid(oid))
                        continue;
 
                strbuf_grow(&buffer, entlen + 100);
@@ -523,7 +523,7 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p)
        if (0 <= it->entry_count) {
                if (size < rawsz)
                        goto free_return;
-               memcpy(it->oid.hash, (const unsigned char*)buf, rawsz);
+               oidread(&it->oid, (const unsigned char *)buf);
                buf += rawsz;
                size -= rawsz;
        }
diff --git a/cache.h b/cache.h
index 0c1fb9fbccaccb9e01c800f28fb11dcd7e0ff2b6..89a107a7f79175c600eb2a9689982912541ae4e7 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -324,7 +324,7 @@ struct index_state {
                 drop_cache_tree : 1;
        struct hashmap name_hash;
        struct hashmap dir_hash;
-       unsigned char sha1[20];
+       struct object_id oid;
        struct untracked_cache *untracked;
        uint64_t fsmonitor_last_update;
        struct ewah_bitmap *fsmonitor_dirty;
@@ -373,6 +373,13 @@ extern void free_name_hash(struct index_state *istate);
 #define read_blob_data_from_cache(path, sz) read_blob_data_from_index(&the_index, (path), (sz))
 #endif
 
+#define TYPE_BITS 3
+
+/*
+ * Values in this enum (except those outside the 3 bit range) are part
+ * of pack file format. See Documentation/technical/pack-format.txt
+ * for more information.
+ */
 enum object_type {
        OBJ_BAD = -1,
        OBJ_NONE = 0,
@@ -635,7 +642,7 @@ extern int unmerged_index(const struct index_state *);
  */
 extern int index_has_changes(struct strbuf *sb);
 
-extern int verify_path(const char *path);
+extern int verify_path(const char *path, unsigned mode);
 extern int strcmp_offset(const char *s1, const char *s2, size_t *first_change);
 extern int index_dir_exists(struct index_state *istate, const char *name, int namelen);
 extern void adjust_dirname_case(struct index_state *istate, char *name);
@@ -1010,21 +1017,10 @@ static inline void oidclr(struct object_id *oid)
        memset(oid->hash, 0, GIT_MAX_RAWSZ);
 }
 
-
-#define EMPTY_TREE_SHA1_HEX \
-       "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
-#define EMPTY_TREE_SHA1_BIN_LITERAL \
-        "\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60" \
-        "\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04"
-extern const struct object_id empty_tree_oid;
-#define EMPTY_TREE_SHA1_BIN (empty_tree_oid.hash)
-
-#define EMPTY_BLOB_SHA1_HEX \
-       "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
-#define EMPTY_BLOB_SHA1_BIN_LITERAL \
-       "\xe6\x9d\xe2\x9b\xb2\xd1\xd6\x43\x4b\x8b" \
-       "\x29\xae\x77\x5a\xd8\xc2\xe4\x8c\x53\x91"
-extern const struct object_id empty_blob_oid;
+static inline void oidread(struct object_id *oid, const unsigned char *hash)
+{
+       memcpy(oid->hash, hash, the_hash_algo->rawsz);
+}
 
 static inline int is_empty_blob_sha1(const unsigned char *sha1)
 {
@@ -1046,6 +1042,9 @@ static inline int is_empty_tree_oid(const struct object_id *oid)
        return !oidcmp(oid, the_hash_algo->empty_tree);
 }
 
+const char *empty_tree_oid_hex(void);
+const char *empty_blob_oid_hex(void);
+
 /* set default permissions by passing mode arguments to open(2) */
 int git_mkstemps_mode(char *pattern, int suffix_len, int mode);
 int git_mkstemp_mode(char *pattern, int mode);
@@ -1161,7 +1160,15 @@ int normalize_path_copy(char *dst, const char *src);
 int longest_ancestor_length(const char *path, struct string_list *prefixes);
 char *strip_path_suffix(const char *path, const char *suffix);
 int daemon_avoid_alias(const char *path);
-extern int is_ntfs_dotgit(const char *name);
+
+/*
+ * These functions match their is_hfs_dotgit() counterparts; see utf8.h for
+ * details.
+ */
+int is_ntfs_dotgit(const char *name);
+int is_ntfs_dotgitmodules(const char *name);
+int is_ntfs_dotgitignore(const char *name);
+int is_ntfs_dotgitattributes(const char *name);
 
 /*
  * Returns true iff "str" could be confused as a command-line option when
@@ -1194,7 +1201,7 @@ static inline void *read_object_file(const struct object_id *oid, enum object_ty
 }
 
 /* Read and unpack an object file into memory, write memory to an object file */
-extern int oid_object_info(const struct object_id *, unsigned long *);
+int oid_object_info(struct repository *r, const struct object_id *, unsigned long *);
 
 extern int hash_object_file(const void *buf, unsigned long len,
                            const char *type, struct object_id *oid);
@@ -1253,7 +1260,7 @@ extern int has_object_file_with_flags(const struct object_id *oid, int flags);
  * with the specified name.  This function does not respect replace
  * references.
  */
-extern int has_loose_object_nonlocal(const unsigned char *sha1);
+extern int has_loose_object_nonlocal(const struct object_id *oid);
 
 extern void assert_oid_type(const struct object_id *oid, enum object_type expect);
 
@@ -1284,7 +1291,6 @@ static inline int hex2chr(const char *s)
 #define FALLBACK_DEFAULT_ABBREV 7
 
 struct object_context {
-       unsigned char tree[20];
        unsigned mode;
        /*
         * symlink_path is only used by get_tree_entry_follow_symlinks,
@@ -1551,7 +1557,6 @@ struct pack_window {
 
 struct pack_entry {
        off_t offset;
-       unsigned char sha1[20];
        struct packed_git *p;
 };
 
@@ -1675,7 +1680,10 @@ struct object_info {
 #define OBJECT_INFO_QUICK 8
 /* Do not check loose object */
 #define OBJECT_INFO_IGNORE_LOOSE 16
-extern int oid_object_info_extended(const struct object_id *, struct object_info *, unsigned flags);
+
+int oid_object_info_extended(struct repository *r,
+                            const struct object_id *,
+                            struct object_info *, unsigned flags);
 
 /*
  * Set this to 0 to prevent sha1_object_info_extended() from fetching missing
@@ -1818,11 +1826,6 @@ extern int ws_blank_line(const char *line, int len, unsigned ws_rule);
 void overlay_tree_on_index(struct index_state *istate,
                           const char *tree_name, const char *prefix);
 
-char *alias_lookup(const char *alias);
-int split_cmdline(char *cmdline, const char ***argv);
-/* Takes a negative value returned by split_cmdline */
-const char *split_cmdline_strerror(int cmdline_errno);
-
 /* setup.c */
 struct startup_info {
        int have_repository;
index 109ef280da9884027f17fe7f233b6b818bd71393..ceecc889caf9fc3af038ffa4179056291e90b704 100755 (executable)
@@ -99,6 +99,9 @@ export DEFAULT_TEST_TARGET=prove
 export GIT_PROVE_OPTS="--timer --jobs 3 --state=failed,slow,save"
 export GIT_TEST_OPTS="--verbose-log -x"
 export GIT_TEST_CLONE_2GB=YesPlease
+if [ "$jobname" = linux-gcc ]; then
+       export CC=gcc-8
+fi
 
 case "$jobname" in
 linux-clang|linux-gcc)
index 3735ce413f1835b3222fab05ba9ff5ab5205c2c0..4b04c75b7f81a749c0d48674b8e2042abe5769eb 100755 (executable)
@@ -11,7 +11,10 @@ make --jobs=2
 make --quiet test
 if test "$jobname" = "linux-gcc"
 then
-       GIT_TEST_SPLIT_INDEX=YesPlease make --quiet test
+       export GIT_TEST_SPLIT_INDEX=yes
+       export GIT_TEST_FULL_IN_PACK_ARRAY=true
+       export GIT_TEST_OE_SIZE=10
+       make --quiet test
 fi
 
 check_unignored_build_artifacts
diff --git a/color.c b/color.c
index c6c6c4f580fe9bde55bd5f433b1bffd3932053f8..b1c24c69de652b0b8900e76ebd5b4e6cd24e5203 100644 (file)
--- a/color.c
+++ b/color.c
@@ -174,7 +174,7 @@ static char *color_output(char *out, int len, const struct color *c, char type)
                break;
        case COLOR_ANSI:
                if (len < 2)
-                       die("BUG: color parsing ran out of space");
+                       BUG("color parsing ran out of space");
                *out++ = type;
                *out++ = '0' + c->value;
                break;
@@ -256,7 +256,7 @@ int color_parse_mem(const char *value, int value_len, char *dst)
 #undef OUT
 #define OUT(x) do { \
        if (dst == end) \
-               die("BUG: color parsing ran out of space"); \
+               BUG("color parsing ran out of space"); \
        *dst++ = (x); \
 } while(0)
 
index 49ab85b76916784d5d5e6d6ef15a9638ef97b59f..2165297608ec92c3f5a9cc9abfba072f9d43be1f 100644 (file)
--- a/column.c
+++ b/column.c
@@ -214,7 +214,7 @@ void print_columns(const struct string_list *list, unsigned int colopts,
                display_table(list, colopts, &nopts);
                break;
        default:
-               die("BUG: invalid layout mode %d", COL_LAYOUT(colopts));
+               BUG("invalid layout mode %d", COL_LAYOUT(colopts));
        }
 }
 
index 835c5890be93abc1852dd0e1e19dbb627fee6041..e1c26c1bb7e618f6f372d9a568e7cab75612d2db 100644 (file)
@@ -1,23 +1,58 @@
-# common commands are grouped by themes
-# these groups are output by 'git help' in the order declared here.
-# map each common command in the command list to one of these groups.
-### common groups (do not change this line)
-init         start a working area (see also: git help tutorial)
-worktree     work on the current change (see also: git help everyday)
-info         examine the history and state (see also: git help revisions)
-history      grow, mark and tweak your common history
-remote       collaborate (see also: git help workflows)
-
-### command list (do not change this line)
-# command name                          category [deprecated] [common]
+# Command classification list
+# ---------------------------
+# All supported commands, builtin or external, must be described in
+# here. This info is used to list commands in various places. Each
+# command is on one line followed by one or more attributes.
+#
+# The first attribute group is mandatory and indicates the command
+# type. This group includes:
+#
+#   mainporcelain
+#   ancillarymanipulators
+#   ancillaryinterrogators
+#   foreignscminterface
+#   plumbingmanipulators
+#   plumbinginterrogators
+#   synchingrepositories
+#   synchelpers
+#   purehelpers
+#
+# The type names are self explanatory. But if you want to see what
+# command belongs to what group to get a better picture, have a look
+# at "git" man page, "GIT COMMANDS" section.
+#
+# Commands of type mainporcelain can also optionally have one of these
+# attributes:
+#
+#   init
+#   worktree
+#   info
+#   history
+#   remote
+#
+# These commands are considered "common" and will show up in "git
+# help" output in groups. Uncommon porcelain commands must not
+# specify any of these attributes.
+#
+# "complete" attribute is used to mark that the command should be
+# completable by git-completion.bash. Note that by default,
+# mainporcelain commands are completable so you don't need this
+# attribute.
+#
+# As part of the Git man page list, the man(5/7) guides are also
+# specified here, which can only have "guide" attribute and nothing
+# else.
+#
+### command list (do not change this line, also do not change alignment)
+# command name                          category [category] [category]
 git-add                                 mainporcelain           worktree
 git-am                                  mainporcelain
 git-annotate                            ancillaryinterrogators
-git-apply                               plumbingmanipulators
+git-apply                               plumbingmanipulators            complete
 git-archimport                          foreignscminterface
 git-archive                             mainporcelain
 git-bisect                              mainporcelain           info
-git-blame                               ancillaryinterrogators
+git-blame                               ancillaryinterrogators          complete
 git-branch                              mainporcelain           history
 git-bundle                              mainporcelain
 git-cat-file                            plumbinginterrogators
@@ -27,7 +62,7 @@ git-check-mailmap                       purehelpers
 git-checkout                            mainporcelain           history
 git-checkout-index                      plumbingmanipulators
 git-check-ref-format                    purehelpers
-git-cherry                              ancillaryinterrogators
+git-cherry                              ancillaryinterrogators          complete
 git-cherry-pick                         mainporcelain
 git-citool                              mainporcelain
 git-clean                               mainporcelain
@@ -36,7 +71,7 @@ git-column                              purehelpers
 git-commit                              mainporcelain           history
 git-commit-graph                        plumbingmanipulators
 git-commit-tree                         plumbingmanipulators
-git-config                              ancillarymanipulators
+git-config                              ancillarymanipulators           complete
 git-count-objects                       ancillaryinterrogators
 git-credential                          purehelpers
 git-credential-cache                    purehelpers
@@ -50,7 +85,7 @@ git-diff                                mainporcelain           history
 git-diff-files                          plumbinginterrogators
 git-diff-index                          plumbinginterrogators
 git-diff-tree                           plumbinginterrogators
-git-difftool                            ancillaryinterrogators
+git-difftool                            ancillaryinterrogators          complete
 git-fast-export                         ancillarymanipulators
 git-fast-import                         ancillarymanipulators
 git-fetch                               mainporcelain           remote
@@ -59,20 +94,20 @@ git-filter-branch                       ancillarymanipulators
 git-fmt-merge-msg                       purehelpers
 git-for-each-ref                        plumbinginterrogators
 git-format-patch                        mainporcelain
-git-fsck                                ancillaryinterrogators
+git-fsck                                ancillaryinterrogators          complete
 git-gc                                  mainporcelain
 git-get-tar-commit-id                   ancillaryinterrogators
 git-grep                                mainporcelain           info
 git-gui                                 mainporcelain
 git-hash-object                         plumbingmanipulators
-git-help                                ancillaryinterrogators
+git-help                                ancillaryinterrogators          complete
 git-http-backend                        synchingrepositories
 git-http-fetch                          synchelpers
 git-http-push                           synchelpers
 git-imap-send                           foreignscminterface
 git-index-pack                          plumbingmanipulators
 git-init                                mainporcelain           init
-git-instaweb                            ancillaryinterrogators
+git-instaweb                            ancillaryinterrogators          complete
 git-interpret-trailers                  purehelpers
 gitk                                    mainporcelain
 git-log                                 mainporcelain           info
@@ -86,7 +121,7 @@ git-merge-base                          plumbinginterrogators
 git-merge-file                          plumbingmanipulators
 git-merge-index                         plumbingmanipulators
 git-merge-one-file                      purehelpers
-git-mergetool                           ancillarymanipulators
+git-mergetool                           ancillarymanipulators           complete
 git-merge-tree                          ancillaryinterrogators
 git-mktag                               plumbingmanipulators
 git-mktree                              plumbingmanipulators
@@ -107,28 +142,29 @@ git-quiltimport                         foreignscminterface
 git-read-tree                           plumbingmanipulators
 git-rebase                              mainporcelain           history
 git-receive-pack                        synchelpers
-git-reflog                              ancillarymanipulators
-git-remote                              ancillarymanipulators
-git-repack                              ancillarymanipulators
-git-replace                             ancillarymanipulators
-git-request-pull                        foreignscminterface
+git-reflog                              ancillarymanipulators           complete
+git-remote                              ancillarymanipulators           complete
+git-repack                              ancillarymanipulators           complete
+git-replace                             ancillarymanipulators           complete
+git-request-pull                        foreignscminterface             complete
 git-rerere                              ancillaryinterrogators
 git-reset                               mainporcelain           worktree
 git-revert                              mainporcelain
 git-rev-list                            plumbinginterrogators
 git-rev-parse                           ancillaryinterrogators
 git-rm                                  mainporcelain           worktree
-git-send-email                          foreignscminterface
+git-send-email                          foreignscminterface             complete
 git-send-pack                           synchingrepositories
 git-shell                               synchelpers
 git-shortlog                            mainporcelain
 git-show                                mainporcelain           info
-git-show-branch                         ancillaryinterrogators
+git-show-branch                         ancillaryinterrogators          complete
 git-show-index                          plumbinginterrogators
 git-show-ref                            plumbinginterrogators
 git-sh-i18n                             purehelpers
 git-sh-setup                            purehelpers
 git-stash                               mainporcelain
+git-stage                                                               complete
 git-status                              mainporcelain           info
 git-stripspace                          purehelpers
 git-submodule                           mainporcelain
@@ -147,6 +183,22 @@ git-verify-commit                       ancillaryinterrogators
 git-verify-pack                         plumbinginterrogators
 git-verify-tag                          ancillaryinterrogators
 gitweb                                  ancillaryinterrogators
-git-whatchanged                         ancillaryinterrogators
+git-whatchanged                         ancillaryinterrogators          complete
 git-worktree                            mainporcelain
 git-write-tree                          plumbingmanipulators
+gitattributes                           guide
+gitcli                                  guide
+gitcore-tutorial                        guide
+gitcvs-migration                        guide
+gitdiffcore                             guide
+giteveryday                             guide
+gitglossary                             guide
+githooks                                guide
+gitignore                               guide
+gitmodules                              guide
+gitnamespaces                           guide
+gitrepository-layout                    guide
+gitrevisions                            guide
+gittutorial-2                           guide
+gittutorial                             guide
+gitworkflows                            guide
index 3fc1e0da2768d3f209da08b6b4c71df133a3ec88..4c6127088ff96282fbcdc98ef5767281e52846e9 100644 (file)
@@ -250,7 +250,6 @@ static struct commit_list **insert_parent_or_die(struct commit_graph *g,
 
 static int fill_commit_in_graph(struct commit *item, struct commit_graph *g, uint32_t pos)
 {
-       struct object_id oid;
        uint32_t edge_value;
        uint32_t *parent_data_ptr;
        uint64_t date_low, date_high;
@@ -260,8 +259,7 @@ static int fill_commit_in_graph(struct commit *item, struct commit_graph *g, uin
        item->object.parsed = 1;
        item->graph_pos = pos;
 
-       hashcpy(oid.hash, commit_data);
-       item->tree = lookup_tree(&oid);
+       item->maybe_tree = NULL;
 
        date_high = get_be32(commit_data + g->hash_len + 8) & 0x3;
        date_low = get_be32(commit_data + g->hash_len + 12);
@@ -320,6 +318,28 @@ int parse_commit_in_graph(struct commit *item)
        return 0;
 }
 
+static struct tree *load_tree_for_commit(struct commit_graph *g, struct commit *c)
+{
+       struct object_id oid;
+       const unsigned char *commit_data = g->chunk_commit_data +
+                                          GRAPH_DATA_WIDTH * (c->graph_pos);
+
+       hashcpy(oid.hash, commit_data);
+       c->maybe_tree = lookup_tree(&oid);
+
+       return c->maybe_tree;
+}
+
+struct tree *get_commit_tree_in_graph(const struct commit *c)
+{
+       if (c->maybe_tree)
+               return c->maybe_tree;
+       if (c->graph_pos == COMMIT_NOT_FROM_GRAPH)
+               BUG("get_commit_tree_in_graph called from non-commit-graph commit");
+
+       return load_tree_for_commit(commit_graph, (struct commit *)c);
+}
+
 static void write_graph_chunk_fanout(struct hashfile *f,
                                     struct commit **commits,
                                     int nr_commits)
@@ -372,7 +392,7 @@ static void write_graph_chunk_data(struct hashfile *f, int hash_len,
                uint32_t packedDate[2];
 
                parse_commit(*list);
-               hashwrite(f, (*list)->tree->object.oid.hash, hash_len);
+               hashwrite(f, get_commit_tree_oid(*list)->hash, hash_len);
 
                parent = (*list)->parents;
 
@@ -495,7 +515,7 @@ static int add_packed_commits(const struct object_id *oid,
        struct object_info oi = OBJECT_INFO_INIT;
 
        oi.typep = &type;
-       if (packed_object_info(pack, offset, &oi) < 0)
+       if (packed_object_info(the_repository, pack, offset, &oi) < 0)
                die("unable to get type of object %s", oid_to_hex(oid));
 
        if (type != OBJ_COMMIT)
index e1d8580c98a24a51e64c4d0f1d9abda10c5e92f2..260a468e73acd89b2a7706c859e8c25b264f3365 100644 (file)
@@ -17,6 +17,8 @@ char *get_commit_graph_filename(const char *obj_dir);
  */
 int parse_commit_in_graph(struct commit *item);
 
+struct tree *get_commit_tree_in_graph(const struct commit *c);
+
 struct commit_graph {
        int graph_fd;
 
index 57049118a5321ddeff53d7be94017ed17c5f336e..b0e57cc4400964ad7dca8d37c5d4c10bf3979b51 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -13,6 +13,7 @@
 #include "prio-queue.h"
 #include "sha1-lookup.h"
 #include "wt-status.h"
+#include "advice.h"
 
 static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
 
@@ -177,6 +178,15 @@ static int read_graft_file(const char *graft_file)
        struct strbuf buf = STRBUF_INIT;
        if (!fp)
                return -1;
+       if (advice_graft_file_deprecated)
+               advise(_("Support for <GIT_DIR>/info/grafts is deprecated\n"
+                        "and will be removed in a future Git version.\n"
+                        "\n"
+                        "Please use \"git replace --convert-graft-file\"\n"
+                        "to convert the grafts into replace refs.\n"
+                        "\n"
+                        "Turn this message off by running\n"
+                        "\"git config advice.graftFileDeprecated false\""));
        while (!strbuf_getwholeline(&buf, fp, '\n')) {
                /* The format is just "Commit Parent1 Parent2 ...\n" */
                struct commit_graft *graft = read_graft_line(&buf);
@@ -296,6 +306,22 @@ void free_commit_buffer(struct commit *commit)
        }
 }
 
+struct tree *get_commit_tree(const struct commit *commit)
+{
+       if (commit->maybe_tree || !commit->object.parsed)
+               return commit->maybe_tree;
+
+       if (commit->graph_pos == COMMIT_NOT_FROM_GRAPH)
+               BUG("commit has NULL tree, but was not loaded from commit-graph");
+
+       return get_commit_tree_in_graph(commit);
+}
+
+struct object_id *get_commit_tree_oid(const struct commit *commit)
+{
+       return &get_commit_tree(commit)->object.oid;
+}
+
 const void *detach_commit_buffer(struct commit *commit, unsigned long *sizep)
 {
        struct commit_buffer *v = buffer_slab_peek(&buffer_slab, commit);
@@ -332,10 +358,10 @@ int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long s
        if (tail <= bufptr + tree_entry_len + 1 || memcmp(bufptr, "tree ", 5) ||
                        bufptr[tree_entry_len] != '\n')
                return error("bogus commit object %s", oid_to_hex(&item->object.oid));
-       if (get_sha1_hex(bufptr + 5, parent.hash) < 0)
+       if (get_oid_hex(bufptr + 5, &parent) < 0)
                return error("bad tree pointer in commit %s",
                             oid_to_hex(&item->object.oid));
-       item->tree = lookup_tree(&parent);
+       item->maybe_tree = lookup_tree(&parent);
        bufptr += tree_entry_len + 1; /* "tree " + "hex sha1" + "\n" */
        pptr = &item->parents;
 
@@ -344,7 +370,7 @@ int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long s
                struct commit *new_parent;
 
                if (tail <= bufptr + parent_entry_len + 1 ||
-                   get_sha1_hex(bufptr + 7, parent.hash) ||
+                   get_oid_hex(bufptr + 7, &parent) ||
                    bufptr[parent_entry_len] != '\n')
                        return error("bad parents in commit %s", oid_to_hex(&item->object.oid));
                bufptr += parent_entry_len + 1;
@@ -1291,17 +1317,19 @@ struct commit_extra_header *read_commit_extra_headers(struct commit *commit,
        return extra;
 }
 
-void for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data)
+int for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data)
 {
        struct commit_extra_header *extra, *to_free;
+       int res = 0;
 
        to_free = read_commit_extra_headers(commit, NULL);
-       for (extra = to_free; extra; extra = extra->next) {
+       for (extra = to_free; !res && extra; extra = extra->next) {
                if (strcmp(extra->key, "mergetag"))
                        continue; /* not a merge tag */
-               fn(commit, extra, data);
+               res = fn(commit, extra, data);
        }
        free_commit_extra_headers(to_free);
+       return res;
 }
 
 static inline int standard_header_field(const char *field, size_t len)
index e57ae4b58380cce6192cc6489682838677f048ce..c3af512f8b1b90b3963d0921f7ec0d0a43a2fcbe 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -19,11 +19,17 @@ struct commit_list {
 struct commit {
        struct object object;
        void *util;
-       unsigned int index;
        timestamp_t date;
        struct commit_list *parents;
-       struct tree *tree;
+
+       /*
+        * If the commit is loaded from the commit-graph file, then this
+        * member may be NULL. Only access it through get_commit_tree()
+        * or get_commit_tree_oid().
+        */
+       struct tree *maybe_tree;
        uint32_t graph_pos;
+       unsigned int index;
 };
 
 extern int save_commit_buffer;
@@ -102,6 +108,9 @@ void unuse_commit_buffer(const struct commit *, const void *buffer);
  */
 void free_commit_buffer(struct commit *);
 
+struct tree *get_commit_tree(const struct commit *);
+struct object_id *get_commit_tree_oid(const struct commit *);
+
 /*
  * Disassociate any cached object buffer from the commit, but do not free it.
  * The buffer (or NULL, if none) is returned.
@@ -294,10 +303,10 @@ extern const char *find_commit_header(const char *msg, const char *key,
 /* Find the end of the log message, the right place for a new trailer. */
 extern int ignore_non_trailer(const char *buf, size_t len);
 
-typedef void (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra,
+typedef int (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra,
                                 void *cb_data);
 
-extern void for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data);
+extern int for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data);
 
 struct merge_remote_desc {
        struct object *obj; /* the named object, could be a tag */
index 6f8f1d8c1130f89ccf913cc7f7cafac3dd39e123..fbbf0f8e9f2b2ae0a96769108d8a2773f071aec1 100644 (file)
--- a/config.c
+++ b/config.c
@@ -103,7 +103,7 @@ static int config_buf_ungetc(int c, struct config_source *conf)
        if (conf->u.buf.pos > 0) {
                conf->u.buf.pos--;
                if (conf->u.buf.buf[conf->u.buf.pos] != c)
-                       die("BUG: config_buf can only ungetc the same character");
+                       BUG("config_buf can only ungetc the same character");
                return c;
        }
 
@@ -190,7 +190,7 @@ static int prepare_include_condition_pattern(struct strbuf *pat)
                strbuf_realpath(&path, cf->path, 1);
                slash = find_last_dir_sep(path.buf);
                if (!slash)
-                       die("BUG: how is this possible?");
+                       BUG("how is this possible?");
                strbuf_splice(pat, 0, 1, path.buf, slash - path.buf);
                prefix = slash - path.buf + 1 /* slash */;
        } else if (!is_absolute_path(pat->buf))
@@ -1814,7 +1814,7 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha
        l_item->value_index = e->value_list.nr - 1;
 
        if (!cf)
-               die("BUG: configset_add_value has no source");
+               BUG("configset_add_value has no source");
        if (cf->name) {
                kv_info->filename = strintern(cf->name);
                kv_info->linenr = cf->linenr;
@@ -2333,6 +2333,19 @@ struct config_store_data {
        unsigned int key_seen:1, section_seen:1, is_keys_section:1;
 };
 
+static void config_store_data_clear(struct config_store_data *store)
+{
+       free(store->key);
+       if (store->value_regex != NULL &&
+           store->value_regex != CONFIG_REGEX_NONE) {
+               regfree(store->value_regex);
+               free(store->value_regex);
+       }
+       free(store->parsed);
+       free(store->seen);
+       memset(store, 0, sizeof(*store));
+}
+
 static int matches(const char *key, const char *value,
                   const struct config_store_data *store)
 {
@@ -2359,7 +2372,7 @@ static int store_aux_event(enum config_event_t type,
 
        if (type == CONFIG_EVENT_SECTION) {
                if (cf->var.len < 2 || cf->var.buf[cf->var.len - 1] != '.')
-                       BUG("Invalid section name '%s'", cf->var.buf);
+                       return error("invalid section name '%s'", cf->var.buf);
 
                /* Is this the section we were looking for? */
                store->is_keys_section =
@@ -2667,7 +2680,6 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
        fd = hold_lock_file_for_update(&lock, config_filename, 0);
        if (fd < 0) {
                error_errno("could not lock config file %s", config_filename);
-               free(store.key);
                ret = CONFIG_NO_LOCK;
                goto out_free;
        }
@@ -2677,8 +2689,6 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
         */
        in_fd = open(config_filename, O_RDONLY);
        if ( in_fd < 0 ) {
-               free(store.key);
-
                if ( ENOENT != errno ) {
                        error_errno("opening %s", config_filename);
                        ret = CONFIG_INVALID_FILE; /* same as "invalid config file" */
@@ -2690,7 +2700,8 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
                        goto out_free;
                }
 
-               store.key = (char *)key;
+               free(store.key);
+               store.key = xstrdup(key);
                if (write_section(fd, key, &store) < 0 ||
                    write_pair(fd, key, value, &store) < 0)
                        goto write_err_out;
@@ -2715,7 +2726,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
                        if (regcomp(store.value_regex, value_regex,
                                        REG_EXTENDED)) {
                                error("invalid pattern: %s", value_regex);
-                               free(store.value_regex);
+                               FREE_AND_NULL(store.value_regex);
                                ret = CONFIG_INVALID_PATTERN;
                                goto out_free;
                        }
@@ -2740,23 +2751,10 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
                                                      config_filename,
                                                      &store, &opts)) {
                        error("invalid config file %s", config_filename);
-                       free(store.key);
-                       if (store.value_regex != NULL &&
-                           store.value_regex != CONFIG_REGEX_NONE) {
-                               regfree(store.value_regex);
-                               free(store.value_regex);
-                       }
                        ret = CONFIG_INVALID_FILE;
                        goto out_free;
                }
 
-               free(store.key);
-               if (store.value_regex != NULL &&
-                   store.value_regex != CONFIG_REGEX_NONE) {
-                       regfree(store.value_regex);
-                       free(store.value_regex);
-               }
-
                /* if nothing to unset, or too many matches, error out */
                if ((store.seen_nr == 0 && value == NULL) ||
                    (store.seen_nr > 1 && multi_replace == 0)) {
@@ -2887,6 +2885,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
                munmap(contents, contents_sz);
        if (in_fd >= 0)
                close(in_fd);
+       config_store_data_clear(&store);
        return ret;
 
 write_err_out:
@@ -3127,6 +3126,7 @@ static int git_config_copy_or_rename_section_in_file(const char *config_filename
        rollback_lock_file(&lock);
 out_no_rollback:
        free(filename_buf);
+       config_store_data_clear(&store);
        return ret;
 }
 
@@ -3208,7 +3208,7 @@ const char *current_config_origin_type(void)
        else if(cf)
                type = cf->origin_type;
        else
-               die("BUG: current_config_origin_type called outside config callback");
+               BUG("current_config_origin_type called outside config callback");
 
        switch (type) {
        case CONFIG_ORIGIN_BLOB:
@@ -3222,7 +3222,7 @@ const char *current_config_origin_type(void)
        case CONFIG_ORIGIN_CMDLINE:
                return "command line";
        default:
-               die("BUG: unknown config origin type");
+               BUG("unknown config origin type");
        }
 }
 
@@ -3234,7 +3234,7 @@ const char *current_config_name(void)
        else if (cf)
                name = cf->name;
        else
-               die("BUG: current_config_name called outside config callback");
+               BUG("current_config_name called outside config callback");
        return name ? name : "";
 }
 
index a6e734c5d4ae7a3fc733e24bbfeae15ce6130ac4..684fc5bf02677bbaddd214f78b14fa55df7025c2 100644 (file)
@@ -37,6 +37,7 @@ ifeq ($(uname_S),Linux)
        HAVE_GETDELIM = YesPlease
        SANE_TEXT_GREP=-a
        FREAD_READS_DIRECTORIES = UnfortunatelyYes
+       BASIC_CFLAGS += -DHAVE_SYSINFO
        PROCFS_EXECUTABLE_PATH = /proc/self/exe
 endif
 ifeq ($(uname_S),GNU/kFreeBSD)
index b34dc80451fb375b112c1338a0aa3326a0a168ad..968e91b18c09e5ac814328e0f833e2d4aa91cf2c 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -14,6 +14,7 @@
 #include "strbuf.h"
 #include "version.h"
 #include "protocol.h"
+#include "alias.h"
 
 static char *server_capabilities_v1;
 static struct argv_array server_capabilities_v2 = ARGV_ARRAY_INIT;
@@ -408,7 +409,8 @@ static int process_ref_v2(const char *line, struct ref ***list)
 
 struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
                             struct ref **list, int for_push,
-                            const struct argv_array *ref_prefixes)
+                            const struct argv_array *ref_prefixes,
+                            const struct string_list *server_options)
 {
        int i;
        *list = NULL;
@@ -419,6 +421,12 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
        if (server_supports_v2("agent", 0))
                packet_write_fmt(fd_out, "agent=%s", git_user_agent_sanitized());
 
+       if (server_options && server_options->nr &&
+           server_supports_v2("server-option", 1))
+               for (i = 0; i < server_options->nr; i++)
+                       packet_write_fmt(fd_out, "server-option=%s",
+                                        server_options->items[i].string);
+
        packet_delim(fd_out);
        /* When pushing we don't want to request the peeled tags */
        if (!for_push)
diff --git a/contrib/coccinelle/commit.cocci b/contrib/coccinelle/commit.cocci
new file mode 100644 (file)
index 0000000..a7e9215
--- /dev/null
@@ -0,0 +1,28 @@
+@@
+expression c;
+@@
+- &c->maybe_tree->object.oid
++ get_commit_tree_oid(c)
+
+@@
+expression c;
+@@
+- c->maybe_tree->object.oid.hash
++ get_commit_tree_oid(c)->hash
+
+// These excluded functions must access c->maybe_tree direcly.
+@@
+identifier f !~ "^(get_commit_tree|get_commit_tree_in_graph|load_tree_for_commit)$";
+expression c;
+@@
+  f(...) {...
+- c->maybe_tree
++ get_commit_tree(c)
+  ...}
+
+@@
+expression c;
+expression s;
+@@
+- get_commit_tree(c) = s
++ c->maybe_tree = s
index 46047e17ece55f5fb1768e34d54b49c3841b3e58..12814e9bbf6be5ff0d1608c71554c2b4bb14c87d 100644 (file)
@@ -94,6 +94,70 @@ __git ()
                ${__git_dir:+--git-dir="$__git_dir"} "$@" 2>/dev/null
 }
 
+# Removes backslash escaping, single quotes and double quotes from a word,
+# stores the result in the variable $dequoted_word.
+# 1: The word to dequote.
+__git_dequote ()
+{
+       local rest="$1" len ch
+
+       dequoted_word=""
+
+       while test -n "$rest"; do
+               len=${#dequoted_word}
+               dequoted_word="$dequoted_word${rest%%[\\\'\"]*}"
+               rest="${rest:$((${#dequoted_word}-$len))}"
+
+               case "${rest:0:1}" in
+               \\)
+                       ch="${rest:1:1}"
+                       case "$ch" in
+                       $'\n')
+                               ;;
+                       *)
+                               dequoted_word="$dequoted_word$ch"
+                               ;;
+                       esac
+                       rest="${rest:2}"
+                       ;;
+               \')
+                       rest="${rest:1}"
+                       len=${#dequoted_word}
+                       dequoted_word="$dequoted_word${rest%%\'*}"
+                       rest="${rest:$((${#dequoted_word}-$len+1))}"
+                       ;;
+               \")
+                       rest="${rest:1}"
+                       while test -n "$rest" ; do
+                               len=${#dequoted_word}
+                               dequoted_word="$dequoted_word${rest%%[\\\"]*}"
+                               rest="${rest:$((${#dequoted_word}-$len))}"
+                               case "${rest:0:1}" in
+                               \\)
+                                       ch="${rest:1:1}"
+                                       case "$ch" in
+                                       \"|\\|\$|\`)
+                                               dequoted_word="$dequoted_word$ch"
+                                               ;;
+                                       $'\n')
+                                               ;;
+                                       *)
+                                               dequoted_word="$dequoted_word\\$ch"
+                                               ;;
+                                       esac
+                                       rest="${rest:2}"
+                                       ;;
+                               \")
+                                       rest="${rest:1}"
+                                       break
+                                       ;;
+                               esac
+                       done
+                       ;;
+               esac
+       done
+}
+
 # The following function is based on code from:
 #
 #   bash_completion - programmable completion functions for bash 3.2+
@@ -346,6 +410,24 @@ __gitcomp_nl ()
        __gitcomp_nl_append "$@"
 }
 
+# Fills the COMPREPLY array with prefiltered paths without any additional
+# processing.
+# Callers must take care of providing only paths that match the current path
+# to be completed and adding any prefix path components, if necessary.
+# 1: List of newline-separated matching paths, complete with all prefix
+#    path componens.
+__gitcomp_file_direct ()
+{
+       local IFS=$'\n'
+
+       COMPREPLY=($1)
+
+       # use a hack to enable file mode in bash < 4
+       compopt -o filenames +o nospace 2>/dev/null ||
+       compgen -f /non-existing-dir/ >/dev/null ||
+       true
+}
+
 # Generates completion reply with compgen from newline-separated possible
 # completion filenames.
 # It accepts 1 to 3 arguments:
@@ -365,7 +447,8 @@ __gitcomp_file ()
 
        # use a hack to enable file mode in bash < 4
        compopt -o filenames +o nospace 2>/dev/null ||
-       compgen -f /non-existing-dir/ > /dev/null
+       compgen -f /non-existing-dir/ >/dev/null ||
+       true
 }
 
 # Execute 'git ls-files', unless the --committable option is specified, in
@@ -375,10 +458,12 @@ __gitcomp_file ()
 __git_ls_files_helper ()
 {
        if [ "$2" == "--committable" ]; then
-               __git -C "$1" diff-index --name-only --relative HEAD
+               __git -C "$1" -c core.quotePath=false diff-index \
+                       --name-only --relative HEAD -- "${3//\\/\\\\}*"
        else
                # NOTE: $2 is not quoted in order to support multiple options
-               __git -C "$1" ls-files --exclude-standard $2
+               __git -C "$1" -c core.quotePath=false ls-files \
+                       --exclude-standard $2 -- "${3//\\/\\\\}*"
        fi
 }
 
@@ -389,12 +474,103 @@ __git_ls_files_helper ()
 #    If provided, only files within the specified directory are listed.
 #    Sub directories are never recursed.  Path must have a trailing
 #    slash.
+# 3: List only paths matching this path component (optional).
 __git_index_files ()
 {
-       local root="${2-.}" file
+       local root="$2" match="$3"
+
+       __git_ls_files_helper "$root" "$1" "$match" |
+       awk -F / -v pfx="${2//\\/\\\\}" '{
+               paths[$1] = 1
+       }
+       END {
+               for (p in paths) {
+                       if (substr(p, 1, 1) != "\"") {
+                               # No special characters, easy!
+                               print pfx p
+                               continue
+                       }
+
+                       # The path is quoted.
+                       p = dequote(p)
+                       if (p == "")
+                               continue
+
+                       # Even when a directory name itself does not contain
+                       # any special characters, it will still be quoted if
+                       # any of its (stripped) trailing path components do.
+                       # Because of this we may have seen the same direcory
+                       # both quoted and unquoted.
+                       if (p in paths)
+                               # We have seen the same directory unquoted,
+                               # skip it.
+                               continue
+                       else
+                               print pfx p
+               }
+       }
+       function dequote(p,    bs_idx, out, esc, esc_idx, dec) {
+               # Skip opening double quote.
+               p = substr(p, 2)
+
+               # Interpret backslash escape sequences.
+               while ((bs_idx = index(p, "\\")) != 0) {
+                       out = out substr(p, 1, bs_idx - 1)
+                       esc = substr(p, bs_idx + 1, 1)
+                       p = substr(p, bs_idx + 2)
+
+                       if ((esc_idx = index("abtvfr\"\\", esc)) != 0) {
+                               # C-style one-character escape sequence.
+                               out = out substr("\a\b\t\v\f\r\"\\",
+                                                esc_idx, 1)
+                       } else if (esc == "n") {
+                               # Uh-oh, a newline character.
+                               # We cant reliably put a pathname
+                               # containing a newline into COMPREPLY,
+                               # and the newline would create a mess.
+                               # Skip this path.
+                               return ""
+                       } else {
+                               # Must be a \nnn octal value, then.
+                               dec = esc             * 64 + \
+                                     substr(p, 1, 1) * 8  + \
+                                     substr(p, 2, 1)
+                               out = out sprintf("%c", dec)
+                               p = substr(p, 3)
+                       }
+               }
+               # Drop closing double quote, if there is one.
+               # (There isnt any if this is a directory, as it was
+               # already stripped with the trailing path components.)
+               if (substr(p, length(p), 1) == "\"")
+                       out = out substr(p, 1, length(p) - 1)
+               else
+                       out = out p
 
-       __git_ls_files_helper "$root" "$1" |
-       cut -f1 -d/ | sort | uniq
+               return out
+       }'
+}
+
+# __git_complete_index_file requires 1 argument:
+# 1: the options to pass to ls-file
+#
+# The exception is --committable, which finds the files appropriate commit.
+__git_complete_index_file ()
+{
+       local dequoted_word pfx="" cur_
+
+       __git_dequote "$cur"
+
+       case "$dequoted_word" in
+       ?*/*)
+               pfx="${dequoted_word%/*}/"
+               cur_="${dequoted_word##*/}"
+               ;;
+       *)
+               cur_="$dequoted_word"
+       esac
+
+       __gitcomp_file_direct "$(__git_index_files "$1" "$pfx" "$cur_")"
 }
 
 # Lists branches from the local repository.
@@ -713,26 +889,6 @@ __git_complete_revlist_file ()
        esac
 }
 
-
-# __git_complete_index_file requires 1 argument:
-# 1: the options to pass to ls-file
-#
-# The exception is --committable, which finds the files appropriate commit.
-__git_complete_index_file ()
-{
-       local pfx="" cur_="$cur"
-
-       case "$cur_" in
-       ?*/*)
-               pfx="${cur_%/*}"
-               cur_="${cur_##*/}"
-               pfx="${pfx}/"
-               ;;
-       esac
-
-       __gitcomp_file "$(__git_index_files "$1" ${pfx:+"$pfx"})" "$pfx" "$cur_"
-}
-
 __git_complete_file ()
 {
        __git_complete_revlist_file
@@ -833,127 +989,11 @@ __git_complete_strategy ()
        return 1
 }
 
-__git_commands () {
-       if test -n "${GIT_TESTING_COMMAND_COMPLETION:-}"
-       then
-               printf "%s" "${GIT_TESTING_COMMAND_COMPLETION}"
-       else
-               git help -a|egrep '^  [a-zA-Z0-9]'
-       fi
-}
-
-__git_list_all_commands ()
-{
-       local i IFS=" "$'\n'
-       for i in $(__git_commands)
-       do
-               case $i in
-               *--*)             : helper pattern;;
-               *) echo $i;;
-               esac
-       done
-}
-
 __git_all_commands=
 __git_compute_all_commands ()
 {
        test -n "$__git_all_commands" ||
-       __git_all_commands=$(__git_list_all_commands)
-}
-
-__git_list_porcelain_commands ()
-{
-       local i IFS=" "$'\n'
-       __git_compute_all_commands
-       for i in $__git_all_commands
-       do
-               case $i in
-               *--*)             : helper pattern;;
-               applymbox)        : ask gittus;;
-               applypatch)       : ask gittus;;
-               archimport)       : import;;
-               cat-file)         : plumbing;;
-               check-attr)       : plumbing;;
-               check-ignore)     : plumbing;;
-               check-mailmap)    : plumbing;;
-               check-ref-format) : plumbing;;
-               checkout-index)   : plumbing;;
-               column)           : internal helper;;
-               commit-graph)     : plumbing;;
-               commit-tree)      : plumbing;;
-               count-objects)    : infrequent;;
-               credential)       : credentials;;
-               credential-*)     : credentials helper;;
-               cvsexportcommit)  : export;;
-               cvsimport)        : import;;
-               cvsserver)        : daemon;;
-               daemon)           : daemon;;
-               diff-files)       : plumbing;;
-               diff-index)       : plumbing;;
-               diff-tree)        : plumbing;;
-               fast-import)      : import;;
-               fast-export)      : export;;
-               fsck-objects)     : plumbing;;
-               fetch-pack)       : plumbing;;
-               fmt-merge-msg)    : plumbing;;
-               for-each-ref)     : plumbing;;
-               hash-object)      : plumbing;;
-               http-*)           : transport;;
-               index-pack)       : plumbing;;
-               init-db)          : deprecated;;
-               local-fetch)      : plumbing;;
-               ls-files)         : plumbing;;
-               ls-remote)        : plumbing;;
-               ls-tree)          : plumbing;;
-               mailinfo)         : plumbing;;
-               mailsplit)        : plumbing;;
-               merge-*)          : plumbing;;
-               mktree)           : plumbing;;
-               mktag)            : plumbing;;
-               pack-objects)     : plumbing;;
-               pack-redundant)   : plumbing;;
-               pack-refs)        : plumbing;;
-               parse-remote)     : plumbing;;
-               patch-id)         : plumbing;;
-               prune)            : plumbing;;
-               prune-packed)     : plumbing;;
-               quiltimport)      : import;;
-               read-tree)        : plumbing;;
-               receive-pack)     : plumbing;;
-               remote-*)         : transport;;
-               rerere)           : plumbing;;
-               rev-list)         : plumbing;;
-               rev-parse)        : plumbing;;
-               runstatus)        : plumbing;;
-               sh-setup)         : internal;;
-               shell)            : daemon;;
-               show-ref)         : plumbing;;
-               send-pack)        : plumbing;;
-               show-index)       : plumbing;;
-               ssh-*)            : transport;;
-               stripspace)       : plumbing;;
-               symbolic-ref)     : plumbing;;
-               unpack-file)      : plumbing;;
-               unpack-objects)   : plumbing;;
-               update-index)     : plumbing;;
-               update-ref)       : plumbing;;
-               update-server-info) : daemon;;
-               upload-archive)   : plumbing;;
-               upload-pack)      : plumbing;;
-               write-tree)       : plumbing;;
-               var)              : infrequent;;
-               verify-pack)      : infrequent;;
-               verify-tag)       : plumbing;;
-               *) echo $i;;
-               esac
-       done
-}
-
-__git_porcelain_commands=
-__git_compute_porcelain_commands ()
-{
-       test -n "$__git_porcelain_commands" ||
-       __git_porcelain_commands=$(__git_list_porcelain_commands)
+       __git_all_commands=$(git --list-cmds=main,others,alias,nohelpers)
 }
 
 # Lists all set config variables starting with the given section prefix,
@@ -971,11 +1011,6 @@ __git_pretty_aliases ()
        __git_get_config_variables "pretty"
 }
 
-__git_aliases ()
-{
-       __git_get_config_variables "alias"
-}
-
 # __git_aliased_command requires 1 argument
 __git_aliased_command ()
 {
@@ -1583,13 +1618,12 @@ _git_help ()
                return
                ;;
        esac
-       __git_compute_all_commands
-       __gitcomp "$__git_all_commands $(__git_aliases)
-               attributes cli core-tutorial cvs-migration
-               diffcore everyday gitk glossary hooks ignore modules
-               namespaces repository-layout revisions tutorial tutorial-2
-               workflows
-               "
+       if test -n "$GIT_TESTING_ALL_COMMAND_LIST"
+       then
+               __gitcomp "$GIT_TESTING_ALL_COMMAND_LIST $(git --list-cmds=alias,list-guide) gitk"
+       else
+               __gitcomp "$(git --list-cmds=main,nohelpers,alias,list-guide) gitk"
+       fi
 }
 
 _git_init ()
@@ -1949,7 +1983,7 @@ _git_rebase ()
        --*)
                __gitcomp "
                        --onto --merge --strategy --interactive
-                       --preserve-merges --stat --no-stat
+                       --rebase-merges --preserve-merges --stat --no-stat
                        --committer-date-is-author-date --ignore-date
                        --ignore-whitespace --whitespace=
                        --autosquash --no-autosquash
@@ -2120,7 +2154,7 @@ _git_config ()
                return
                ;;
        branch.*.rebase)
-               __gitcomp "false true preserve interactive"
+               __gitcomp "false true merges preserve interactive"
                return
                ;;
        remote.pushdefault)
@@ -2177,7 +2211,7 @@ _git_config ()
                __gitcomp "$__git_log_date_formats"
                return
                ;;
-       sendemail.aliasesfiletype)
+       sendemail.aliasfiletype)
                __gitcomp "mutt mailrc pine elm gnus"
                return
                ;;
@@ -3058,7 +3092,7 @@ __git_complete_common () {
 __git_cmds_with_parseopt_helper=
 __git_support_parseopt_helper () {
        test -n "$__git_cmds_with_parseopt_helper" ||
-               __git_cmds_with_parseopt_helper="$(__git --list-parseopt-builtins)"
+               __git_cmds_with_parseopt_helper="$(__git --list-cmds=parseopt)"
 
        case " $__git_cmds_with_parseopt_helper " in
        *" $1 "*)
@@ -3073,10 +3107,17 @@ __git_support_parseopt_helper () {
 __git_complete_command () {
        local command="$1"
        local completion_func="_git_${command//-/_}"
-       if declare -f $completion_func >/dev/null 2>/dev/null; then
+       if ! declare -f $completion_func >/dev/null 2>/dev/null &&
+               declare -f _completion_loader >/dev/null 2>/dev/null
+       then
+               _completion_loader "git-$command"
+       fi
+       if declare -f $completion_func >/dev/null 2>/dev/null
+       then
                $completion_func
                return 0
-       elif __git_support_parseopt_helper "$command"; then
+       elif __git_support_parseopt_helper "$command"
+       then
                __git_complete_common "$command"
                return 0
        else
@@ -3137,8 +3178,14 @@ __git_main ()
                        --help
                        "
                        ;;
-               *)     __git_compute_porcelain_commands
-                      __gitcomp "$__git_porcelain_commands $(__git_aliases)" ;;
+               *)
+                       if test -n "$GIT_TESTING_PORCELAIN_COMMAND_LIST"
+                       then
+                               __gitcomp "$GIT_TESTING_PORCELAIN_COMMAND_LIST"
+                       else
+                               __gitcomp "$(git --list-cmds=list-mainporcelain,others,nohelpers,alias,list-complete,config)"
+                       fi
+                       ;;
                esac
                return
        fi
@@ -3225,6 +3272,15 @@ if [[ -n ${ZSH_VERSION-} ]]; then
                compadd -Q -S "${4- }" -p "${2-}" -- ${=1} && _ret=0
        }
 
+       __gitcomp_file_direct ()
+       {
+               emulate -L zsh
+
+               local IFS=$'\n'
+               compset -P '*[=:]'
+               compadd -Q -f -- ${=1} && _ret=0
+       }
+
        __gitcomp_file ()
        {
                emulate -L zsh
index c3521fbfc44fd8db18244bc10d346ee39b1c3c90..53cb0f934f8ba0b468145fe06480f9c99f860996 100644 (file)
@@ -93,6 +93,15 @@ __gitcomp_nl_append ()
        compadd -Q -S "${4- }" -p "${2-}" -- ${=1} && _ret=0
 }
 
+__gitcomp_file_direct ()
+{
+       emulate -L zsh
+
+       local IFS=$'\n'
+       compset -P '*[=:]'
+       compadd -Q -f -- ${=1} && _ret=0
+}
+
 __gitcomp_file ()
 {
        emulate -L zsh
diff --git a/contrib/convert-grafts-to-replace-refs.sh b/contrib/convert-grafts-to-replace-refs.sh
deleted file mode 100755 (executable)
index 0cbc917..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/sh
-
-# You should execute this script in the repository where you
-# want to convert grafts to replace refs.
-
-GRAFTS_FILE="${GIT_DIR:-.git}/info/grafts"
-
-. $(git --exec-path)/git-sh-setup
-
-test -f "$GRAFTS_FILE" || die "Could not find graft file: '$GRAFTS_FILE'"
-
-grep '^[^# ]' "$GRAFTS_FILE" |
-while read definition
-do
-       if test -n "$definition"
-       then
-               echo "Converting: $definition"
-               git replace --graft $definition ||
-                       die "Conversion failed for: $definition"
-       fi
-done
-
-mv "$GRAFTS_FILE" "$GRAFTS_FILE.bak" ||
-       die "Could not rename '$GRAFTS_FILE' to '$GRAFTS_FILE.bak'"
-
-echo "Success!"
-echo "All the grafts in '$GRAFTS_FILE' have been converted to replace refs!"
-echo "The grafts file '$GRAFTS_FILE' has been renamed: '$GRAFTS_FILE.bak'"
index 51b76138a5b14e05f530cd9ec0bb0b2fedff7493..0ffa40719126fe285722cfa8fc7b461d0584cd3a 100644 (file)
@@ -1,5 +1,5 @@
 test:
-       ./test.pl
+       ./t-git-credential-netrc.sh
 
 testverbose:
-       ./test.pl -d -v
+       ./t-git-credential-netrc.sh -d -v
index 1571a7b2693e50ac93828a76a01366da1e8a6983..0b9a94102ec90be0a51900113092a5177850f3dc 100755 (executable)
@@ -2,11 +2,13 @@
 
 use strict;
 use warnings;
+use autodie;
 
 use Getopt::Long;
 use File::Basename;
+use Git;
 
-my $VERSION = "0.1";
+my $VERSION = "0.2";
 
 my %options = (
               help => 0,
@@ -54,6 +56,7 @@ GetOptions(\%options,
            "insecure|k",
            "verbose|v",
            "file|f=s@",
+           'gpg|g:s',
           );
 
 if ($options{help}) {
@@ -62,27 +65,31 @@ if ($options{help}) {
 
        print <<EOHIPPUS;
 
-$0 [-f AUTHFILE1] [-f AUTHFILEN] [-d] [-v] [-k] get
+$0 [(-f <authfile>)...] [-g <program>] [-d] [-v] [-k] get
 
 Version $VERSION by tzz\@lifelogs.com.  License: BSD.
 
 Options:
 
-  -f|--file AUTHFILE : specify netrc-style files.  Files with the .gpg extension
-                       will be decrypted by GPG before parsing.  Multiple -f
-                       arguments are OK.  They are processed in order, and the
-                       first matching entry found is returned via the credential
-                       helper protocol (see below).
+  -f|--file <authfile>: specify netrc-style files.  Files with the .gpg
+                        extension will be decrypted by GPG before parsing.
+                        Multiple -f arguments are OK.  They are processed in
+                        order, and the first matching entry found is returned
+                        via the credential helper protocol (see below).
 
-                       When no -f option is given, .authinfo.gpg, .netrc.gpg,
-                      .authinfo, and .netrc files in your home directory are used
-                      in this order.
+                        When no -f option is given, .authinfo.gpg, .netrc.gpg,
+                        .authinfo, and .netrc files in your home directory are
+                        used in this order.
 
-  -k|--insecure      : ignore bad file ownership or permissions
+  -g|--gpg <program>  : specify the program for GPG. By default, this is the
+                        value of gpg.program in the git repository or global
+                        option or gpg.
 
-  -d|--debug         : turn on debugging (developer info)
+  -k|--insecure       : ignore bad file ownership or permissions
 
-  -v|--verbose       : be more verbose (show files and information found)
+  -d|--debug          : turn on debugging (developer info)
+
+  -v|--verbose        : be more verbose (show files and information found)
 
 To enable this credential helper:
 
@@ -99,8 +106,9 @@ in the path.)
 
   git config credential.helper '$shortname -f AUTHFILE -v'
 
-Only "get" mode is supported by this credential helper.  It opens every AUTHFILE
-and looks for the first entry that matches the requested search criteria:
+Only "get" mode is supported by this credential helper.  It opens every
+<authfile> and looks for the first entry that matches the requested search
+criteria:
 
  'port|protocol':
    The protocol that will be used (e.g., https). (protocol=X)
@@ -120,7 +128,7 @@ host=github.com
 protocol=https
 username=tzz
 
-this credential helper will look for the first entry in every AUTHFILE that
+this credential helper will look for the first entry in every <authfile> that
 matches
 
 machine github.com port https login tzz
@@ -137,8 +145,8 @@ Then, the helper will print out whatever tokens it got from the entry, including
 back to "protocol".  Any redundant entry tokens (part of the original query) are
 skipped.
 
-Again, note that only the first matching entry from all the AUTHFILEs, processed
-in the sequence given on the command line, is used.
+Again, note that only the first matching entry from all the <authfile>s,
+processed in the sequence given on the command line, is used.
 
 Netrc/authinfo tokens can be quoted as 'STRING' or "STRING".
 
@@ -152,7 +160,7 @@ EOHIPPUS
 my $mode = shift @ARGV;
 
 # Credentials must get a parameter, so die if it's missing.
-die "Syntax: $0 [-f AUTHFILE1] [-f AUTHFILEN] [-d] get" unless defined $mode;
+die "Syntax: $0 [(-f <authfile>)...] [-d] get" unless defined $mode;
 
 # Only support 'get' mode; with any other unsupported ones we just exit.
 exit 0 unless $mode eq 'get';
@@ -172,6 +180,8 @@ unless (scalar @$files) {
        $files = $options{file} = [ map { glob $_ } @candidates ];
 }
 
+load_config(\%options);
+
 my $query = read_credential_data_from_stdin();
 
 FILE:
@@ -233,7 +243,7 @@ sub load_netrc {
 
        my $io;
        if ($gpgmode) {
-               my @cmd = (qw(gpg --decrypt), $file);
+               my @cmd = ($options{'gpg'}, qw(--decrypt), $file);
                log_verbose("Using GPG to open $file: [@cmd]");
                open $io, "-|", @cmd;
        } else {
@@ -410,6 +420,14 @@ sub print_credential_data {
                printf "%s=%s\n", $git_token, $entry->{$git_token};
        }
 }
+sub load_config {
+       # load settings from git config
+       my $options = shift;
+       # set from command argument, gpg.program option, or default to gpg
+       $options->{'gpg'} //= Git->repository()->config('gpg.program')
+                         // 'gpg';
+       log_verbose("using $options{'gpg'} for GPG operations");
+}
 sub log_verbose {
        return unless $options{verbose};
        printf STDERR @_;
diff --git a/contrib/credential/netrc/t-git-credential-netrc.sh b/contrib/credential/netrc/t-git-credential-netrc.sh
new file mode 100755 (executable)
index 0000000..58191a6
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/sh
+(
+       cd ../../../t
+       test_description='git-credential-netrc'
+       . ./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
+       }
+
+       # set up test repository
+
+       test_expect_success \
+    'set up test repository' \
+    'git config --add gpg.program test.git-config-gpg'
+
+       # The external test will outputs its own plan
+       test_external_has_tap=1
+
+       test_external \
+    'git-credential-netrc' \
+    perl "$TEST_DIRECTORY"/../contrib/credential/netrc/test.pl
+
+       test_done
+)
diff --git a/contrib/credential/netrc/test.command-option-gpg b/contrib/credential/netrc/test.command-option-gpg
new file mode 100755 (executable)
index 0000000..d8f1285
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo machine command-option-gpg login username password password
diff --git a/contrib/credential/netrc/test.git-config-gpg b/contrib/credential/netrc/test.git-config-gpg
new file mode 100755 (executable)
index 0000000..65cf594
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo machine git-config-gpg login username password password
diff --git a/contrib/credential/netrc/test.netrc.gpg b/contrib/credential/netrc/test.netrc.gpg
new file mode 100644 (file)
index 0000000..e69de29
index 169b6463c3011fbef721d926396fa20b5d1616f7..1e1001030e7631dd49cbfeb002b429158019f191 100755 (executable)
 #!/usr/bin/perl
+use lib (split(/:/, $ENV{GITPERLLIB}));
 
 use warnings;
 use strict;
-use Test;
+use Test::More qw(no_plan);
+use File::Basename;
+use File::Spec::Functions qw(:DEFAULT rel2abs);
 use IPC::Open2;
 
-BEGIN { plan tests => 15 }
+BEGIN {
+       # t-git-credential-netrc.sh kicks off our testing, so we have to go
+       # from there.
+       Test::More->builder->current_test(1);
+       Test::More->builder->no_ending(1);
+}
 
 my @global_credential_args = @ARGV;
-my $netrc = './test.netrc';
-print "# Testing insecure file, nothing should be found\n";
+my $scriptDir = dirname rel2abs $0;
+my ($netrc, $netrcGpg, $gcNetrc) = map { catfile $scriptDir, $_; }
+                                       qw(test.netrc
+                                          test.netrc.gpg
+                                          git-credential-netrc);
+local $ENV{PATH} = join ':'
+                      , $scriptDir
+                      , $ENV{PATH}
+                      ? $ENV{PATH}
+                      : ();
+
+diag "Testing insecure file, nothing should be found\n";
 chmod 0644, $netrc;
 my $cred = run_credential(['-f', $netrc, 'get'],
                          { host => 'github.com' });
 
-ok(scalar keys %$cred, 0, "Got 0 keys from insecure file");
+ok(scalar keys %$cred == 0, "Got 0 keys from insecure file");
 
-print "# Testing missing file, nothing should be found\n";
+diag "Testing missing file, nothing should be found\n";
 chmod 0644, $netrc;
 $cred = run_credential(['-f', '///nosuchfile///', 'get'],
                       { host => 'github.com' });
 
-ok(scalar keys %$cred, 0, "Got 0 keys from missing file");
+ok(scalar keys %$cred == 0, "Got 0 keys from missing file");
 
 chmod 0600, $netrc;
 
-print "# Testing with invalid data\n";
+diag "Testing with invalid data\n";
 $cred = run_credential(['-f', $netrc, 'get'],
                       "bad data");
-ok(scalar keys %$cred, 4, "Got first found keys with bad data");
+ok(scalar keys %$cred == 4, "Got first found keys with bad data");
 
-print "# Testing netrc file for a missing corovamilkbar entry\n";
+diag "Testing netrc file for a missing corovamilkbar entry\n";
 $cred = run_credential(['-f', $netrc, 'get'],
                       { host => 'corovamilkbar' });
 
-ok(scalar keys %$cred, 0, "Got no corovamilkbar keys");
+ok(scalar keys %$cred == 0, "Got no corovamilkbar keys");
 
-print "# Testing netrc file for a github.com entry\n";
+diag "Testing netrc file for a github.com entry\n";
 $cred = run_credential(['-f', $netrc, 'get'],
                       { host => 'github.com' });
 
-ok(scalar keys %$cred, 2, "Got 2 Github keys");
+ok(scalar keys %$cred == 2, "Got 2 Github keys");
 
-ok($cred->{password}, 'carolknows', "Got correct Github password");
-ok($cred->{username}, 'carol', "Got correct Github username");
+is($cred->{password}, 'carolknows', "Got correct Github password");
+is($cred->{username}, 'carol', "Got correct Github username");
 
-print "# Testing netrc file for a username-specific entry\n";
+diag "Testing netrc file for a username-specific entry\n";
 $cred = run_credential(['-f', $netrc, 'get'],
                       { host => 'imap', username => 'bob' });
 
-ok(scalar keys %$cred, 2, "Got 2 username-specific keys");
+ok(scalar keys %$cred == 2, "Got 2 username-specific keys");
 
-ok($cred->{password}, 'bobwillknow', "Got correct user-specific password");
-ok($cred->{protocol}, 'imaps', "Got correct user-specific protocol");
+is($cred->{password}, 'bobwillknow', "Got correct user-specific password");
+is($cred->{protocol}, 'imaps', "Got correct user-specific protocol");
 
-print "# Testing netrc file for a host:port-specific entry\n";
+diag "Testing netrc file for a host:port-specific entry\n";
 $cred = run_credential(['-f', $netrc, 'get'],
                       { host => 'imap2:1099' });
 
-ok(scalar keys %$cred, 2, "Got 2 host:port-specific keys");
+ok(scalar keys %$cred == 2, "Got 2 host:port-specific keys");
 
-ok($cred->{password}, 'tzzknow', "Got correct host:port-specific password");
-ok($cred->{username}, 'tzz', "Got correct host:port-specific username");
+is($cred->{password}, 'tzzknow', "Got correct host:port-specific password");
+is($cred->{username}, 'tzz', "Got correct host:port-specific username");
 
-print "# Testing netrc file that 'host:port kills host' entry\n";
+diag "Testing netrc file that 'host:port kills host' entry\n";
 $cred = run_credential(['-f', $netrc, 'get'],
                       { host => 'imap2' });
 
-ok(scalar keys %$cred, 2, "Got 2 'host:port kills host' keys");
+ok(scalar keys %$cred == 2, "Got 2 'host:port kills host' keys");
+
+is($cred->{password}, 'bobwillknow', "Got correct 'host:port kills host' password");
+is($cred->{username}, 'bob', "Got correct 'host:port kills host' username");
+
+diag 'Testing netrc file decryption by git config gpg.program setting\n';
+$cred = run_credential( ['-f', $netrcGpg, 'get']
+                      , { host => 'git-config-gpg' }
+                      );
+
+ok(scalar keys %$cred == 2, 'Got keys decrypted by git config option');
+
+diag 'Testing netrc file decryption by gpg option\n';
+$cred = run_credential( ['-f', $netrcGpg, '-g', 'test.command-option-gpg', 'get']
+                      , { host => 'command-option-gpg' }
+                      );
 
-ok($cred->{password}, 'bobwillknow', "Got correct 'host:port kills host' password");
-ok($cred->{username}, 'bob', "Got correct 'host:port kills host' username");
+ok(scalar keys %$cred == 2, 'Got keys decrypted by command option');
 
 sub run_credential
 {
        my $args = shift @_;
        my $data = shift @_;
        my $pid = open2(my $chld_out, my $chld_in,
-                       './git-credential-netrc', @global_credential_args,
+                       $gcNetrc, @global_credential_args,
                        @$args);
 
        die "Couldn't open pipe to netrc credential helper: $!" unless $pid;
index d60b4315ed60ad10e849408c6986d1ea5b47b32b..e800d9f5c9cf25e1aae3a0b87ad35f46b2a973c3 100755 (executable)
@@ -63,6 +63,8 @@
        my $have_top_dir = 1;
        my ($top_dir, %files);
 
+       my $next_path = '';
+
        while (read(I, $_, 512) == 512) {
                my ($name, $mode, $uid, $gid, $size, $mtime,
                        $chksum, $typeflag, $linkname, $magic,
                        $prefix) = unpack 'Z100 Z8 Z8 Z8 Z12 Z12
                        Z8 Z1 Z100 Z6
                        Z2 Z32 Z32 Z8 Z8 Z*', $_;
+
+               unless ($next_path eq '') {
+                       # Recover name from previous extended header
+                       $name = $next_path;
+                       $next_path = '';
+               }
+
                last unless length($name);
                if ($name eq '././@LongLink') {
                        # GNU tar extension
                        Z8 Z1 Z100 Z6
                        Z2 Z32 Z32 Z8 Z8 Z*', $_;
                }
-               next if $name =~ m{/\z};
                $mode = oct $mode;
                $size = oct $size;
                $mtime = oct $mtime;
                next if $typeflag == 5; # directory
 
-               if ($typeflag != 1) { # handle hard links later
+               if ($typeflag eq 'x') { # extended header
+                       # If extended header, check for path
+                       my $pax_header = '';
+                       while ($size > 0 && read(I, $_, 512) == 512) {
+                               $pax_header = $pax_header . substr($_, 0, $size);
+                               $size -= 512;
+                       }
+
+                       my @lines = split /\n/, $pax_header;
+                       foreach my $line (@lines) {
+                               my ($len, $entry) = split / /, $line;
+                               my ($key, $value) = split /=/, $entry;
+                               if ($key eq 'path') {
+                                       $next_path = $value;
+                               }
+                       }
+                       next;
+               } elsif ($name =~ m{/\z}) { # directory
+                       next;
+               } elsif ($typeflag != 1) { # handle hard links later
                        print FI "blob\n", "mark :$next_mark\n";
                        if ($typeflag == 2) { # symbolic link
                                print FI "data ", length($linkname), "\n",
diff --git a/date.c b/date.c
index c3e673fd04221bb444e357ac01a1fe5984375daf..49f943e25b5dc4f288963077f5eabaf24f651aab 100644 (file)
--- a/date.c
+++ b/date.c
@@ -185,7 +185,7 @@ 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");
+               BUG("cannot create anonymous strftime date_mode struct");
        mode.type = type;
        mode.local = 0;
        return &mode;
diff --git a/diff.c b/diff.c
index 1289df4b1f9f395010e475073c2c5e5ce43976a7..136d44b45560d5c9db7e88a1ff22aa6b086108e8 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -177,7 +177,7 @@ static int parse_submodule_params(struct diff_options *options, const char *valu
        return 0;
 }
 
-static int git_config_rename(const char *var, const char *value)
+int git_config_rename(const char *var, const char *value)
 {
        if (!value)
                return DIFF_DETECT_RENAME;
@@ -1184,7 +1184,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
                fputs(o->stat_sep, o->file);
                break;
        default:
-               die("BUG: unknown diff symbol");
+               BUG("unknown diff symbol");
        }
        strbuf_release(&sb);
 }
@@ -1343,7 +1343,7 @@ static struct diff_tempfile *claim_diff_tempfile(void) {
        for (i = 0; i < ARRAY_SIZE(diff_temp); i++)
                if (!diff_temp[i].name)
                        return diff_temp + i;
-       die("BUG: diff is failing to clean up its tempfiles");
+       BUG("diff is failing to clean up its tempfiles");
 }
 
 static void remove_tempfile(void)
@@ -3472,7 +3472,7 @@ static int reuse_worktree_file(const char *name, const struct object_id *oid, in
         * objects however would tend to be slower as they need
         * to be individually opened and inflated.
         */
-       if (!FAST_WORKING_DIRECTORY && !want_file && has_sha1_pack(oid->hash))
+       if (!FAST_WORKING_DIRECTORY && !want_file && has_object_pack(oid))
                return 0;
 
        /*
@@ -3638,7 +3638,8 @@ int diff_populate_filespec(struct diff_filespec *s, unsigned int flags)
        else {
                enum object_type type;
                if (size_only || (flags & CHECK_BINARY)) {
-                       type = oid_object_info(&s->oid, &s->size);
+                       type = oid_object_info(the_repository, &s->oid,
+                                              &s->size);
                        if (type < 0)
                                die("unable to read %s",
                                    oid_to_hex(&s->oid));
@@ -3840,7 +3841,7 @@ static const char *diff_abbrev_oid(const struct object_id *oid, int abbrev)
                if (abbrev < 0)
                        abbrev = FALLBACK_DEFAULT_ABBREV;
                if (abbrev > GIT_SHA1_HEXSZ)
-                       die("BUG: oid abbreviation out of range: %d", abbrev);
+                       BUG("oid abbreviation out of range: %d", abbrev);
                if (abbrev)
                        hex[abbrev] = '\0';
                return hex;
@@ -3897,13 +3898,14 @@ static void fill_metainfo(struct strbuf *msg,
                *must_show_header = 0;
        }
        if (one && two && oidcmp(&one->oid, &two->oid)) {
-               int abbrev = o->flags.full_index ? 40 : DEFAULT_ABBREV;
+               const unsigned hexsz = the_hash_algo->hexsz;
+               int abbrev = o->flags.full_index ? hexsz : DEFAULT_ABBREV;
 
                if (o->flags.binary) {
                        mmfile_t mf;
                        if ((!fill_mmfile(&mf, one) && diff_filespec_is_binary(one)) ||
                            (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two)))
-                               abbrev = 40;
+                               abbrev = hexsz;
                }
                strbuf_addf(msg, "%s%sindex %s..%s", line_prefix, set,
                            diff_abbrev_oid(&one->oid, abbrev),
@@ -4138,6 +4140,11 @@ void diff_setup_done(struct diff_options *options)
                              DIFF_FORMAT_NAME_STATUS |
                              DIFF_FORMAT_CHECKDIFF |
                              DIFF_FORMAT_NO_OUTPUT;
+       /*
+        * This must be signed because we're comparing against a potentially
+        * negative value.
+        */
+       const int hexsz = the_hash_algo->hexsz;
 
        if (options->set_default)
                options->set_default(options);
@@ -4218,8 +4225,8 @@ void diff_setup_done(struct diff_options *options)
                         */
                        read_cache();
        }
-       if (40 < options->abbrev)
-               options->abbrev = 40; /* full */
+       if (hexsz < options->abbrev)
+               options->abbrev = hexsz; /* full */
 
        /*
         * It does not make sense to show the first hit we happened
@@ -4334,7 +4341,7 @@ static int stat_opt(struct diff_options *options, const char **av)
        int argcount = 1;
 
        if (!skip_prefix(arg, "--stat", &arg))
-               die("BUG: stat option does not begin with --stat: %s", arg);
+               BUG("stat option does not begin with --stat: %s", arg);
        end = (char *)arg;
 
        switch (*arg) {
@@ -4797,8 +4804,8 @@ int diff_opt_parse(struct diff_options *options,
                options->abbrev = strtoul(arg, NULL, 10);
                if (options->abbrev < MINIMUM_ABBREV)
                        options->abbrev = MINIMUM_ABBREV;
-               else if (40 < options->abbrev)
-                       options->abbrev = 40;
+               else if (the_hash_algo->hexsz < options->abbrev)
+                       options->abbrev = the_hash_algo->hexsz;
        }
        else if ((argcount = parse_long_opt("src-prefix", av, &optarg))) {
                options->a_prefix = optarg;
@@ -5519,7 +5526,7 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o)
        struct diff_queue_struct *q = &diff_queued_diff;
 
        if (WSEH_NEW & WS_RULE_MASK)
-               die("BUG: WS rules bit mask overlaps with diff symbol flags");
+               BUG("WS rules bit mask overlaps with diff symbol flags");
 
        if (o->color_moved)
                o->emitted_symbols = &esm;
@@ -6053,7 +6060,7 @@ size_t fill_textconv(struct userdiff_driver *driver,
        }
 
        if (!driver->textconv)
-               die("BUG: fill_textconv called with non-textconv driver");
+               BUG("fill_textconv called with non-textconv driver");
 
        if (driver->textconv_cache && df->oid_valid) {
                *outbuf = notes_cache_get(driver->textconv_cache,
diff --git a/diff.h b/diff.h
index d29560f822ca0ee4738f769e2feb3939851d7ff1..dedac472ca5959bb5ff17888a54042ac75678631 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -324,6 +324,7 @@ extern int git_diff_ui_config(const char *var, const char *value, void *cb);
 extern void diff_setup(struct diff_options *);
 extern int diff_opt_parse(struct diff_options *, const char **, int, const char *);
 extern void diff_setup_done(struct diff_options *);
+extern int git_config_rename(const char *var, const char *value);
 
 #define DIFF_DETECT_RENAME     1
 #define DIFF_DETECT_COPY       2
index 239ce5122b3a45bec211e0c20113385c0e37805d..800a899c8621eaeba7f4246139df0f289ceb6ee2 100644 (file)
@@ -215,7 +215,6 @@ static void regcomp_or_die(regex_t *regex, const char *needle, int cflags)
                /* The POSIX.2 people are surely sick */
                char errbuf[1024];
                regerror(err, regex, errbuf, 1024);
-               regfree(regex);
                die("invalid regex: %s", errbuf);
        }
 }
index 34182a9a1c10244126e03a2b117bd8bc75bd9871..f2dcd82fde9723be1e53770ad25af42ec547869a 100644 (file)
@@ -188,7 +188,7 @@ struct dir_iterator *dir_iterator_begin(const char *path)
        struct dir_iterator *dir_iterator = &iter->base;
 
        if (!path || !*path)
-               die("BUG: empty path passed to dir_iterator_begin()");
+               BUG("empty path passed to dir_iterator_begin()");
 
        strbuf_init(&iter->base.path, PATH_MAX);
        strbuf_addstr(&iter->base.path, path);
diff --git a/dir.c b/dir.c
index be08d3d296f6d565202fc51586f928b5e274e8a9..ccf8b4975e8645182da783b2367e31401a4cdadb 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -829,7 +829,7 @@ static int add_excludes(const char *fname, const char *base, int baselen,
                if (size == 0) {
                        if (oid_stat) {
                                fill_stat_data(&oid_stat->stat, &st);
-                               oidcpy(&oid_stat->oid, &empty_blob_oid);
+                               oidcpy(&oid_stat->oid, the_hash_algo->empty_blob);
                                oid_stat->valid = 1;
                        }
                        close(fd);
@@ -1241,11 +1241,11 @@ static void prep_exclude(struct dir_struct *dir,
                    (!untracked || !untracked->valid ||
                     /*
                      * .. and .gitignore does not exist before
-                     * (i.e. null exclude_sha1). Then we can skip
+                     * (i.e. null exclude_oid). Then we can skip
                      * loading .gitignore, which would result in
                      * ENOENT anyway.
                      */
-                    !is_null_sha1(untracked->exclude_sha1))) {
+                    !is_null_oid(&untracked->exclude_oid))) {
                        /*
                         * dir->basebuf gets reused by the traversal, but we
                         * need fname to remain unchanged to ensure the src
@@ -1276,9 +1276,9 @@ static void prep_exclude(struct dir_struct *dir,
                 * order, though, if you do that.
                 */
                if (untracked &&
-                   hashcmp(oid_stat.oid.hash, untracked->exclude_sha1)) {
+                   oidcmp(&oid_stat.oid, &untracked->exclude_oid)) {
                        invalidate_gitignore(dir->untracked, untracked);
-                       hashcpy(untracked->exclude_sha1, oid_stat.oid.hash);
+                       oidcpy(&untracked->exclude_oid, &oid_stat.oid);
                }
                dir->exclude_stack = stk;
                current = stk->baselen;
@@ -2623,9 +2623,10 @@ static void write_one_dir(struct untracked_cache_dir *untracked,
                stat_data_to_disk(&stat_data, &untracked->stat_data);
                strbuf_add(&wd->sb_stat, &stat_data, sizeof(stat_data));
        }
-       if (!is_null_sha1(untracked->exclude_sha1)) {
+       if (!is_null_oid(&untracked->exclude_oid)) {
                ewah_set(wd->sha1_valid, i);
-               strbuf_add(&wd->sb_sha1, untracked->exclude_sha1, 20);
+               strbuf_add(&wd->sb_sha1, untracked->exclude_oid.hash,
+                          the_hash_algo->rawsz);
        }
 
        intlen = encode_varint(untracked->untracked_nr, intbuf);
@@ -2826,16 +2827,16 @@ static void read_stat(size_t pos, void *cb)
        ud->valid = 1;
 }
 
-static void read_sha1(size_t pos, void *cb)
+static void read_oid(size_t pos, void *cb)
 {
        struct read_data *rd = cb;
        struct untracked_cache_dir *ud = rd->ucd[pos];
-       if (rd->data + 20 > rd->end) {
+       if (rd->data + the_hash_algo->rawsz > rd->end) {
                rd->data = rd->end + 1;
                return;
        }
-       hashcpy(ud->exclude_sha1, rd->data);
-       rd->data += 20;
+       hashcpy(ud->exclude_oid.hash, rd->data);
+       rd->data += the_hash_algo->rawsz;
 }
 
 static void load_oid_stat(struct oid_stat *oid_stat, const unsigned char *data,
@@ -2918,7 +2919,7 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long
        ewah_each_bit(rd.check_only, set_check_only, &rd);
        rd.data = next + len;
        ewah_each_bit(rd.valid, read_stat, &rd);
-       ewah_each_bit(rd.sha1_valid, read_sha1, &rd);
+       ewah_each_bit(rd.sha1_valid, read_oid, &rd);
        next = rd.data;
 
 done:
@@ -2993,7 +2994,7 @@ void untracked_cache_invalidate_path(struct index_state *istate,
 {
        if (!istate->untracked || !istate->untracked->root)
                return;
-       if (!safe_path && !verify_path(path))
+       if (!safe_path && !verify_path(path, 0))
                return;
        invalidate_one_component(istate->untracked, istate->untracked->root,
                                 path, strlen(path));
diff --git a/dir.h b/dir.h
index 3870193e527b31186d5965888040af6c810e73ee..f5fdedbab2520547f8de2e7c827f5f9c85c932ac 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -3,6 +3,7 @@
 
 /* See Documentation/technical/api-directory-listing.txt */
 
+#include "cache.h"
 #include "strbuf.h"
 
 struct dir_entry {
@@ -118,8 +119,8 @@ struct untracked_cache_dir {
        /* all data except 'dirs' in this struct are good */
        unsigned int valid : 1;
        unsigned int recurse : 1;
-       /* null SHA-1 means this directory does not have .gitignore */
-       unsigned char exclude_sha1[20];
+       /* null object ID means this directory does not have .gitignore */
+       struct object_id exclude_oid;
        char name[FLEX_ARRAY];
 };
 
index 34edf3fb8f9012bf2fcaf231604ebb490e66940c..4d55910ab9a0634a553c01643035ef9afe6034c7 100644 (file)
@@ -1331,7 +1331,7 @@ static void *gfi_unpack_entry(
                 */
                p->pack_size = pack_size + the_hash_algo->rawsz;
        }
-       return unpack_entry(p, oe->idx.offset, &type, sizep);
+       return unpack_entry(the_repository, p, oe->idx.offset, &type, sizep);
 }
 
 static const char *get_mode(const char *str, uint16_t *modep)
@@ -1817,7 +1817,7 @@ static void dump_marks_helper(FILE *f,
 
 static void dump_marks(void)
 {
-       static struct lock_file mark_lock;
+       struct lock_file mark_lock = LOCK_INIT;
        FILE *f;
 
        if (!export_marks_file || (import_marks_file && !import_marks_file_done))
@@ -1872,7 +1872,8 @@ static void read_marks(void)
                        die("corrupt mark line: %s", line);
                e = find_object(&oid);
                if (!e) {
-                       enum object_type type = oid_object_info(&oid, NULL);
+                       enum object_type type = oid_object_info(the_repository,
+                                                               &oid, NULL);
                        if (type < 0)
                                die("object not found: %s", oid_to_hex(&oid));
                        e = insert_object(&oid);
@@ -2402,7 +2403,8 @@ static void file_change_m(const char *p, struct branch *b)
                enum object_type expected = S_ISDIR(mode) ?
                                                OBJ_TREE: OBJ_BLOB;
                enum object_type type = oe ? oe->type :
-                                       oid_object_info(&oid, NULL);
+                                       oid_object_info(the_repository, &oid,
+                                                       NULL);
                if (type < 0)
                        die("%s not found: %s",
                                        S_ISDIR(mode) ?  "Tree" : "Blob",
@@ -2563,7 +2565,8 @@ static void note_change_n(const char *p, struct branch *b, unsigned char *old_fa
                        die("Not a blob (actually a %s): %s",
                                type_name(oe->type), command_buf.buf);
        } else if (!is_null_oid(&oid)) {
-               enum object_type type = oid_object_info(&oid, NULL);
+               enum object_type type = oid_object_info(the_repository, &oid,
+                                                       NULL);
                if (type < 0)
                        die("Blob not found: %s", command_buf.buf);
                if (type != OBJ_BLOB)
@@ -2850,7 +2853,7 @@ static void parse_new_tag(const char *arg)
        } else if (!get_oid(from, &oid)) {
                struct object_entry *oe = find_object(&oid);
                if (!oe) {
-                       type = oid_object_info(&oid, NULL);
+                       type = oid_object_info(the_repository, &oid, NULL);
                        if (type < 0)
                                die("Not a valid object: %s", from);
                } else
@@ -3008,7 +3011,8 @@ static struct object_entry *dereference(struct object_entry *oe,
        unsigned long size;
        char *buf = NULL;
        if (!oe) {
-               enum object_type type = oid_object_info(oid, NULL);
+               enum object_type type = oid_object_info(the_repository, oid,
+                                                       NULL);
                if (type < 0)
                        die("object not found: %s", oid_to_hex(oid));
                /* cache it! */
index f93723fec41c772db0c0d6f5a8bb2239c9d370bd..a320ce9872b5e752aeb592a16cc9108ae87e9d71 100644 (file)
@@ -1174,6 +1174,13 @@ static int send_fetch_request(int fd_out, const struct fetch_pack_args *args,
                packet_buf_write(&req_buf, "command=fetch");
        if (server_supports_v2("agent", 0))
                packet_buf_write(&req_buf, "agent=%s", git_user_agent_sanitized());
+       if (args->server_options && args->server_options->nr &&
+           server_supports_v2("server-option", 1)) {
+               int i;
+               for (i = 0; i < args->server_options->nr; i++)
+                       packet_write_fmt(fd_out, "server-option=%s",
+                                        args->server_options->items[i].string);
+       }
 
        packet_buf_delim(&req_buf);
        if (args->use_thin_pack)
@@ -1191,14 +1198,29 @@ static int send_fetch_request(int fd_out, const struct fetch_pack_args *args,
        else if (is_repository_shallow() || args->deepen)
                die(_("Server does not support shallow requests"));
 
+       /* Add filter */
+       if (server_supports_feature("fetch", "filter", 0) &&
+           args->filter_options.choice) {
+               print_verbose(args, _("Server supports filter"));
+               packet_buf_write(&req_buf, "filter %s",
+                                args->filter_options.filter_spec);
+       } else if (args->filter_options.choice) {
+               warning("filtering not recognized by server, ignoring");
+       }
+
        /* add wants */
        add_wants(wants, &req_buf);
 
-       /* Add all of the common commits we've found in previous rounds */
-       add_common(&req_buf, common);
+       if (args->no_dependents) {
+               packet_buf_write(&req_buf, "done");
+               ret = 1;
+       } else {
+               /* Add all of the common commits we've found in previous rounds */
+               add_common(&req_buf, common);
 
-       /* Add initial haves */
-       ret = add_haves(&req_buf, haves_to_send, in_vain);
+               /* Add initial haves */
+               ret = add_haves(&req_buf, haves_to_send, in_vain);
+       }
 
        /* Send request */
        packet_buf_flush(&req_buf);
index 6afa08b48bb9f7cc2db1b2fb4644c576fd8d8f3e..bb45a366a82a4a7dae2524e0845ac33128076c57 100644 (file)
@@ -15,6 +15,7 @@ struct fetch_pack_args {
        const char *deepen_since;
        const struct string_list *deepen_not;
        struct list_objects_filter_options filter_options;
+       const struct string_list *server_options;
        unsigned deepen_relative:1;
        unsigned quiet:1;
        unsigned keep_pack:1;
diff --git a/fsck.c b/fsck.c
index 9218c2a643b83e68b9f59479bcc1be833ae8be96..bcae2c30e6531f6a71f518877a71d9a5a9b232ae 100644 (file)
--- a/fsck.c
+++ b/fsck.c
 #include "utf8.h"
 #include "sha1-array.h"
 #include "decorate.h"
+#include "oidset.h"
+#include "packfile.h"
+#include "submodule-config.h"
+#include "config.h"
+
+static struct oidset gitmodules_found = OIDSET_INIT;
+static struct oidset gitmodules_done = OIDSET_INIT;
 
 #define FSCK_FATAL -1
 #define FSCK_INFO -2
@@ -44,6 +51,7 @@
        FUNC(MISSING_TAG_ENTRY, ERROR) \
        FUNC(MISSING_TAG_OBJECT, ERROR) \
        FUNC(MISSING_TREE, ERROR) \
+       FUNC(MISSING_TREE_OBJECT, ERROR) \
        FUNC(MISSING_TYPE, ERROR) \
        FUNC(MISSING_TYPE_ENTRY, ERROR) \
        FUNC(MULTIPLE_AUTHORS, ERROR) \
        FUNC(TREE_NOT_SORTED, ERROR) \
        FUNC(UNKNOWN_TYPE, ERROR) \
        FUNC(ZERO_PADDED_DATE, ERROR) \
+       FUNC(GITMODULES_MISSING, ERROR) \
+       FUNC(GITMODULES_BLOB, ERROR) \
+       FUNC(GITMODULES_PARSE, ERROR) \
+       FUNC(GITMODULES_NAME, ERROR) \
+       FUNC(GITMODULES_SYMLINK, ERROR) \
        /* warnings */ \
        FUNC(BAD_FILEMODE, WARN) \
        FUNC(EMPTY_NAME, WARN) \
@@ -396,9 +409,11 @@ static int fsck_walk_commit(struct commit *commit, void *data, struct fsck_optio
 
        name = get_object_name(options, &commit->object);
        if (name)
-               put_object_name(options, &commit->tree->object, "%s:", name);
+               put_object_name(options, &get_commit_tree(commit)->object,
+                               "%s:", name);
 
-       result = options->walk((struct object *)commit->tree, OBJ_TREE, data, options);
+       result = options->walk((struct object *)get_commit_tree(commit),
+                              OBJ_TREE, data, options);
        if (result < 0)
                return result;
        res = result;
@@ -561,10 +576,18 @@ static int fsck_tree(struct tree *item, struct fsck_options *options)
                has_empty_name |= !*name;
                has_dot |= !strcmp(name, ".");
                has_dotdot |= !strcmp(name, "..");
-               has_dotgit |= (!strcmp(name, ".git") ||
-                              is_hfs_dotgit(name) ||
-                              is_ntfs_dotgit(name));
+               has_dotgit |= is_hfs_dotgit(name) || is_ntfs_dotgit(name);
                has_zero_pad |= *(char *)desc.buffer == '0';
+
+               if (is_hfs_dotgitmodules(name) || is_ntfs_dotgitmodules(name)) {
+                       if (!S_ISLNK(mode))
+                               oidset_insert(&gitmodules_found, oid);
+                       else
+                               retval += report(options, &item->object,
+                                                FSCK_MSG_GITMODULES_SYMLINK,
+                                                ".gitmodules is a symbolic link");
+               }
+
                if (update_tree_entry_gently(&desc)) {
                        retval += report(options, &item->object, FSCK_MSG_BAD_TREE, "cannot be parsed as a tree");
                        break;
@@ -711,30 +734,31 @@ static int fsck_ident(const char **ident, struct object *obj, struct fsck_option
 static int fsck_commit_buffer(struct commit *commit, const char *buffer,
        unsigned long size, struct fsck_options *options)
 {
-       unsigned char tree_sha1[20], sha1[20];
+       struct object_id tree_oid, oid;
        struct commit_graft *graft;
        unsigned parent_count, parent_line_count = 0, author_count;
        int err;
        const char *buffer_begin = buffer;
+       const char *p;
 
        if (verify_headers(buffer, size, &commit->object, options))
                return -1;
 
        if (!skip_prefix(buffer, "tree ", &buffer))
                return report(options, &commit->object, FSCK_MSG_MISSING_TREE, "invalid format - expected 'tree' line");
-       if (get_sha1_hex(buffer, tree_sha1) || buffer[40] != '\n') {
+       if (parse_oid_hex(buffer, &tree_oid, &p) || *p != '\n') {
                err = report(options, &commit->object, FSCK_MSG_BAD_TREE_SHA1, "invalid 'tree' line format - bad sha1");
                if (err)
                        return err;
        }
-       buffer += 41;
+       buffer = p + 1;
        while (skip_prefix(buffer, "parent ", &buffer)) {
-               if (get_sha1_hex(buffer, sha1) || buffer[40] != '\n') {
+               if (parse_oid_hex(buffer, &oid, &p) || *p != '\n') {
                        err = report(options, &commit->object, FSCK_MSG_BAD_PARENT_SHA1, "invalid 'parent' line format - bad sha1");
                        if (err)
                                return err;
                }
-               buffer += 41;
+               buffer = p + 1;
                parent_line_count++;
        }
        graft = lookup_commit_graft(&commit->object.oid);
@@ -772,8 +796,8 @@ static int fsck_commit_buffer(struct commit *commit, const char *buffer,
        err = fsck_ident(&buffer, &commit->object, options);
        if (err)
                return err;
-       if (!commit->tree) {
-               err = report(options, &commit->object, FSCK_MSG_BAD_TREE, "could not load commit's tree %s", sha1_to_hex(tree_sha1));
+       if (!get_commit_tree(commit)) {
+               err = report(options, &commit->object, FSCK_MSG_BAD_TREE, "could not load commit's tree %s", oid_to_hex(&tree_oid));
                if (err)
                        return err;
        }
@@ -799,11 +823,12 @@ static int fsck_commit(struct commit *commit, const char *data,
 static int fsck_tag_buffer(struct tag *tag, const char *data,
        unsigned long size, struct fsck_options *options)
 {
-       unsigned char sha1[20];
+       struct object_id oid;
        int ret = 0;
        const char *buffer;
        char *to_free = NULL, *eol;
        struct strbuf sb = STRBUF_INIT;
+       const char *p;
 
        if (data)
                buffer = data;
@@ -834,12 +859,12 @@ static int fsck_tag_buffer(struct tag *tag, const char *data,
                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') {
+       if (parse_oid_hex(buffer, &oid, &p) || *p != '\n') {
                ret = report(options, &tag->object, FSCK_MSG_BAD_OBJECT_SHA1, "invalid 'object' line format - bad sha1");
                if (ret)
                        goto done;
        }
-       buffer += 41;
+       buffer = p + 1;
 
        if (!skip_prefix(buffer, "type ", &buffer)) {
                ret = report(options, &tag->object, FSCK_MSG_MISSING_TYPE_ENTRY, "invalid format - expected 'type' line");
@@ -901,6 +926,66 @@ static int fsck_tag(struct tag *tag, const char *data,
        return fsck_tag_buffer(tag, data, size, options);
 }
 
+struct fsck_gitmodules_data {
+       struct object *obj;
+       struct fsck_options *options;
+       int ret;
+};
+
+static int fsck_gitmodules_fn(const char *var, const char *value, void *vdata)
+{
+       struct fsck_gitmodules_data *data = vdata;
+       const char *subsection, *key;
+       int subsection_len;
+       char *name;
+
+       if (parse_config_key(var, "submodule", &subsection, &subsection_len, &key) < 0 ||
+           !subsection)
+               return 0;
+
+       name = xmemdupz(subsection, subsection_len);
+       if (check_submodule_name(name) < 0)
+               data->ret |= report(data->options, data->obj,
+                                   FSCK_MSG_GITMODULES_NAME,
+                                   "disallowed submodule name: %s",
+                                   name);
+       free(name);
+
+       return 0;
+}
+
+static int fsck_blob(struct blob *blob, const char *buf,
+                    unsigned long size, struct fsck_options *options)
+{
+       struct fsck_gitmodules_data data;
+
+       if (!oidset_contains(&gitmodules_found, &blob->object.oid))
+               return 0;
+       oidset_insert(&gitmodules_done, &blob->object.oid);
+
+       if (!buf) {
+               /*
+                * A missing buffer here is a sign that the caller found the
+                * blob too gigantic to load into memory. Let's just consider
+                * that an error.
+                */
+               return report(options, &blob->object,
+                             FSCK_MSG_GITMODULES_PARSE,
+                             ".gitmodules too large to parse");
+       }
+
+       data.obj = &blob->object;
+       data.options = options;
+       data.ret = 0;
+       if (git_config_from_mem(fsck_gitmodules_fn, CONFIG_ORIGIN_BLOB,
+                               ".gitmodules", buf, size, &data))
+               data.ret |= report(options, &blob->object,
+                                  FSCK_MSG_GITMODULES_PARSE,
+                                  "could not parse gitmodules blob");
+
+       return data.ret;
+}
+
 int fsck_object(struct object *obj, void *data, unsigned long size,
        struct fsck_options *options)
 {
@@ -908,7 +993,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,
                return report(options, obj, FSCK_MSG_BAD_OBJECT_SHA1, "no valid object to fsck");
 
        if (obj->type == OBJ_BLOB)
-               return 0;
+               return fsck_blob((struct blob *)obj, data, size, options);
        if (obj->type == OBJ_TREE)
                return fsck_tree((struct tree *) obj, options);
        if (obj->type == OBJ_COMMIT)
@@ -932,3 +1017,52 @@ int fsck_error_function(struct fsck_options *o,
        error("object %s: %s", describe_object(o, obj), message);
        return 1;
 }
+
+int fsck_finish(struct fsck_options *options)
+{
+       int ret = 0;
+       struct oidset_iter iter;
+       const struct object_id *oid;
+
+       oidset_iter_init(&gitmodules_found, &iter);
+       while ((oid = oidset_iter_next(&iter))) {
+               struct blob *blob;
+               enum object_type type;
+               unsigned long size;
+               char *buf;
+
+               if (oidset_contains(&gitmodules_done, oid))
+                       continue;
+
+               blob = lookup_blob(oid);
+               if (!blob) {
+                       ret |= report(options, &blob->object,
+                                     FSCK_MSG_GITMODULES_BLOB,
+                                     "non-blob found at .gitmodules");
+                       continue;
+               }
+
+               buf = read_object_file(oid, &type, &size);
+               if (!buf) {
+                       if (is_promisor_object(&blob->object.oid))
+                               continue;
+                       ret |= report(options, &blob->object,
+                                     FSCK_MSG_GITMODULES_MISSING,
+                                     "unable to read .gitmodules blob");
+                       continue;
+               }
+
+               if (type == OBJ_BLOB)
+                       ret |= fsck_blob(blob, buf, size, options);
+               else
+                       ret |= report(options, &blob->object,
+                                     FSCK_MSG_GITMODULES_BLOB,
+                                     "non-blob found at .gitmodules");
+               free(buf);
+       }
+
+
+       oidset_clear(&gitmodules_found);
+       oidset_clear(&gitmodules_done);
+       return ret;
+}
diff --git a/fsck.h b/fsck.h
index 4525510d99abfc0ae523e0d556345db84fc8c302..c3cf5e00347bee21e161a558c6f3b8fbcad0be66 100644 (file)
--- a/fsck.h
+++ b/fsck.h
@@ -53,4 +53,11 @@ int fsck_walk(struct object *obj, void *data, struct fsck_options *options);
 int fsck_object(struct object *obj, void *data, unsigned long size,
        struct fsck_options *options);
 
+/*
+ * Some fsck checks are context-dependent, and may end up queued; run this
+ * after completing all fsck_object() calls in order to resolve any remaining
+ * checks.
+ */
+int fsck_finish(struct fsck_options *options);
+
 #endif
index ed3d1a074d60309063d16044bfd83beb78ef0e9d..665bd2d4254b12edf38e94cb0e8b2200ae3bf34a 100644 (file)
@@ -97,19 +97,13 @@ void write_fsmonitor_extension(struct strbuf *sb, struct index_state *istate)
 static int query_fsmonitor(int version, uint64_t last_update, struct strbuf *query_result)
 {
        struct child_process cp = CHILD_PROCESS_INIT;
-       char ver[64];
-       char date[64];
-       const char *argv[4];
 
-       if (!(argv[0] = core_fsmonitor))
+       if (!core_fsmonitor)
                return -1;
 
-       snprintf(ver, sizeof(ver), "%d", version);
-       snprintf(date, sizeof(date), "%" PRIuMAX, (uintmax_t)last_update);
-       argv[1] = ver;
-       argv[2] = date;
-       argv[3] = NULL;
-       cp.argv = argv;
+       argv_array_push(&cp.args, core_fsmonitor);
+       argv_array_pushf(&cp.args, "%d", version);
+       argv_array_pushf(&cp.args, "%" PRIuMAX, (uintmax_t)last_update);
        cp.use_shell = 1;
        cp.dir = get_git_work_tree();
 
index eeea4b67ea7f1ccee1770c18a36d018b5f3b32b0..8d6d8b45ceb0138797eb7e54b15f7546925598ed 100755 (executable)
@@ -1,50 +1,90 @@
 #!/bin/sh
 
-echo "/* Automatically generated by generate-cmdlist.sh */
-struct cmdname_help {
-       char name[16];
-       char help[80];
-       unsigned char group;
-};
+die () {
+       echo "$@" >&2
+       exit 1
+}
+
+command_list () {
+       grep -v '^#' "$1"
+}
 
-static const char *common_cmd_groups[] = {"
-
-grps=grps$$.tmp
-match=match$$.tmp
-trap "rm -f '$grps' '$match'" 0 1 2 3 15
-
-sed -n '
-       1,/^### common groups/b
-       /^### command list/q
-       /^#/b
-       /^[     ]*$/b
-       h;s/^[^         ][^     ]*[     ][      ]*\(.*\)/       N_("\1"),/p
-       g;s/^\([^       ][^     ]*\)[   ].*/\1/w '$grps'
-       ' "$1"
-printf '};\n\n'
-
-n=0
-substnum=
-while read grp
-do
-       echo "^git-..*[         ]$grp"
-       substnum="$substnum${substnum:+;}s/[    ]$grp/$n/"
-       n=$(($n+1))
-done <"$grps" >"$match"
-
-printf 'static struct cmdname_help common_cmds[] = {\n'
-grep -f "$match" "$1" |
-sed 's/^git-//' |
-sort |
-while read cmd tags
-do
-       tag=$(echo "$tags" | sed "$substnum; s/[^0-9]//g")
+get_categories () {
+       tr ' ' '\n'|
+       grep -v '^$' |
+       sort |
+       uniq
+}
+
+category_list () {
+       command_list "$1" |
+       cut -c 40- |
+       get_categories
+}
+
+get_synopsis () {
        sed -n '
-               /^NAME/,/git-'"$cmd"'/H
+               /^NAME/,/'"$1"'/H
                ${
                        x
-                       s/.*git-'"$cmd"' - \(.*\)/      {"'"$cmd"'", N_("\1"), '$tag'},/
+                       s/.*'"$1"' - \(.*\)/N_("\1")/
                        p
-               }' "Documentation/git-$cmd.txt"
-done
-echo "};"
+               }' "Documentation/$1.txt"
+}
+
+define_categories () {
+       echo
+       echo "/* Command categories */"
+       bit=0
+       category_list "$1" |
+       while read cat
+       do
+               echo "#define CAT_$cat (1UL << $bit)"
+               bit=$(($bit+1))
+       done
+       test "$bit" -gt 32 && die "Urgh.. too many categories?"
+}
+
+define_category_names () {
+       echo
+       echo "/* Category names */"
+       echo "static const char *category_names[] = {"
+       bit=0
+       category_list "$1" |
+       while read cat
+       do
+               echo "  \"$cat\", /* (1UL << $bit) */"
+               bit=$(($bit+1))
+       done
+       echo "  NULL"
+       echo "};"
+}
+
+print_command_list () {
+       echo "static struct cmdname_help command_list[] = {"
+
+       command_list "$1" |
+       while read cmd rest
+       do
+               printf "        { \"$cmd\", $(get_synopsis $cmd), 0"
+               for cat in $(echo "$rest" | get_categories)
+               do
+                       printf " | CAT_$cat"
+               done
+               echo " },"
+       done
+       echo "};"
+}
+
+echo "/* Automatically generated by generate-cmdlist.sh */
+struct cmdname_help {
+       const char *name;
+       const char *help;
+       uint32_t category;
+};
+"
+define_categories "$1"
+echo
+define_category_names "$1"
+echo
+print_command_list "$1"
index c1f52e457f6c5b03947766555e10fba33e7e1b38..36f38ced902e1902a626933bdcda09819acf39a7 100755 (executable)
@@ -205,8 +205,15 @@ sub list_untracked {
        }
 }
 
-sub get_empty_tree {
-       return '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
+{
+       my $empty_tree;
+       sub get_empty_tree {
+               return $empty_tree if defined $empty_tree;
+
+               $empty_tree = run_cmd_pipe(qw(git hash-object -t tree /dev/null));
+               chomp $empty_tree;
+               return $empty_tree;
+       }
 }
 
 sub get_diff_reference {
index f1d7ada890bc7a905186056c4e9257beb8d8441d..9a64998b24b1de277772f1ac97a989aae3133898 100644 (file)
@@ -284,6 +284,10 @@ extern char *gitdirname(char *);
 #include <openssl/err.h>
 #endif
 
+#ifdef HAVE_SYSINFO
+# include <sys/sysinfo.h>
+#endif
+
 /* On most systems <netdb.h> would have given us this, but
  * not on some systems (e.g. z/OS).
  */
@@ -1002,6 +1006,23 @@ static inline int sane_iscase(int x, int is_lower)
                return (x & 0x20) == 0;
 }
 
+/*
+ * Like skip_prefix, but compare case-insensitively. Note that the comparison
+ * is done via tolower(), so it is strictly ASCII (no multi-byte characters or
+ * locale-specific conversions).
+ */
+static inline int skip_iprefix(const char *str, const char *prefix,
+                              const char **out)
+{
+       do {
+               if (!*prefix) {
+                       *out = str;
+                       return 1;
+               }
+       } while (tolower(*str++) == tolower(*prefix++));
+       return 0;
+}
+
 static inline int strtoul_ui(char const *s, int base, unsigned int *result)
 {
        unsigned long ul;
@@ -1053,7 +1074,7 @@ int git_qsort_s(void *base, size_t nmemb, size_t size,
 
 #define QSORT_S(base, n, compar, ctx) do {                     \
        if (qsort_s((base), (n), sizeof(*(base)), compar, ctx)) \
-               die("BUG: qsort_s() failed");                   \
+               BUG("qsort_s() failed");                        \
 } while (0)
 
 #ifndef REG_STARTEND
@@ -1112,6 +1133,9 @@ static inline int regexec_buf(const regex_t *preg, const char *buf, size_t size,
 #define HAVE_VARIADIC_MACROS 1
 #endif
 
+/* usage.c: only to be used for testing BUG() implementation (see test-tool) */
+extern int BUG_exit_code;
+
 #ifdef HAVE_VARIADIC_MACROS
 __attribute__((format (printf, 3, 4))) NORETURN
 void BUG_fl(const char *file, int line, const char *fmt, ...);
index 64f21547c1ab99ef3ad98d57fe0fbadff9621838..ccceaf19a74373bb23b2a2b1aa71b7f0bb909d54 100755 (executable)
@@ -11,6 +11,8 @@
 # The following functions will also be available in the commit filter:
 
 functions=$(cat << \EOF
+EMPTY_TREE=$(git hash-object -t tree /dev/null)
+
 warn () {
        echo "$*" >&2
 }
@@ -46,7 +48,7 @@ git_commit_non_empty_tree()
 {
        if test $# = 3 && test "$1" = $(git rev-parse "$3^{tree}"); then
                map "$3"
-       elif test $# = 1 && test "$1" = 4b825dc642cb6eb9a060e54bf8d69288fbee4904; then
+       elif test $# = 1 && test "$1" = $EMPTY_TREE; then
                :
        else
                git commit-tree "$@"
index 9879c59395edff1a02e390397fc623bc60724c57..f6d9852d2f618393412016cc81c73b1e8362a59d 100755 (executable)
@@ -120,7 +120,7 @@ case "${1:-.}${2:-.}${3:-.}" in
        case "$1" in
        '')
                echo "Added $4 in both, but differently."
-               orig=$(git unpack-file e69de29bb2d1d6434b8b29ae775ad8c2e48c5391)
+               orig=$(git unpack-file $(git hash-object /dev/null))
                ;;
        *)
                echo "Auto-merging $4"
index 7bb9cadc69738a4348bb54ccce50d6bd35a0753f..18bdd4228ba71e9b7c3d33a61d50f0da7fac84e7 100755 (executable)
--- a/git-p4.py
+++ b/git-p4.py
@@ -316,12 +316,17 @@ def p4_last_change():
     results = p4CmdList(["changes", "-m", "1"], skip_info=True)
     return int(results[0]['change'])
 
-def p4_describe(change):
+def p4_describe(change, shelved=False):
     """Make sure it returns a valid result by checking for
        the presence of field "time".  Return a dict of the
        results."""
 
-    ds = p4CmdList(["describe", "-s", str(change)], skip_info=True)
+    cmd = ["describe", "-s"]
+    if shelved:
+        cmd += ["-S"]
+    cmd += [str(change)]
+
+    ds = p4CmdList(cmd, skip_info=True)
     if len(ds) != 1:
         die("p4 describe -s %d did not return 1 result: %s" % (change, str(ds)))
 
@@ -662,6 +667,12 @@ def gitBranchExists(branch):
                             stderr=subprocess.PIPE, stdout=subprocess.PIPE);
     return proc.wait() == 0;
 
+def gitUpdateRef(ref, newvalue):
+    subprocess.check_call(["git", "update-ref", ref, newvalue])
+
+def gitDeleteRef(ref):
+    subprocess.check_call(["git", "update-ref", "-d", ref])
+
 _gitConfig = {}
 
 def gitConfig(key, typeSpecifier=None):
@@ -2099,11 +2110,11 @@ def run(self, args):
 
         commits = []
         if self.master:
-            commitish = self.master
+            committish = self.master
         else:
-            commitish = 'HEAD'
+            committish = 'HEAD'
 
-        for line in read_pipe_lines(["git", "rev-list", "--no-merges", "%s..%s" % (self.origin, commitish)]):
+        for line in read_pipe_lines(["git", "rev-list", "--no-merges", "%s..%s" % (self.origin, committish)]):
             commits.append(line.strip())
         commits.reverse()
 
@@ -2411,6 +2422,7 @@ def __init__(self):
         self.tempBranches = []
         self.tempBranchLocation = "refs/git-p4-tmp"
         self.largeFileSystem = None
+        self.suppress_meta_comment = False
 
         if gitConfig('git-p4.largeFileSystem'):
             largeFileSystemConstructor = globals()[gitConfig('git-p4.largeFileSystem')]
@@ -2421,6 +2433,18 @@ def __init__(self):
         if gitConfig("git-p4.syncFromOrigin") == "false":
             self.syncWithOrigin = False
 
+        self.depotPaths = []
+        self.changeRange = ""
+        self.previousDepotPaths = []
+        self.hasOrigin = False
+
+        # map from branch depot path to parent branch
+        self.knownBranches = {}
+        self.initialParents = {}
+
+        self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
+        self.labels = {}
+
     # Force a checkpoint in fast-import and wait for it to finish
     def checkpoint(self):
         self.gitStream.write("checkpoint\n\n")
@@ -2429,7 +2453,20 @@ def checkpoint(self):
         if self.verbose:
             print "checkpoint finished: " + out
 
-    def extractFilesFromCommit(self, commit):
+    def cmp_shelved(self, path, filerev, revision):
+        """ Determine if a path at revision #filerev is the same as the file
+            at revision @revision for a shelved changelist. If they don't match,
+            unshelving won't be safe (we will get other changes mixed in).
+
+            This is comparing the revision that the shelved changelist is *based* on, not
+            the shelved changelist itself.
+        """
+        ret = p4Cmd(["diff2", "{0}#{1}".format(path, filerev), "{0}@{1}".format(path, revision)])
+        if verbose:
+            print("p4 diff2 path %s filerev %s revision %s => %s" % (path, filerev, revision, ret))
+        return ret["status"] == "identical"
+
+    def extractFilesFromCommit(self, commit, shelved=False, shelved_cl = 0, origin_revision = 0):
         self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
                              for path in self.cloneExclude]
         files = []
@@ -2452,6 +2489,19 @@ def extractFilesFromCommit(self, commit):
             file["rev"] = commit["rev%s" % fnum]
             file["action"] = commit["action%s" % fnum]
             file["type"] = commit["type%s" % fnum]
+            if shelved:
+                file["shelved_cl"] = int(shelved_cl)
+
+                # For shelved changelists, check that the revision of each file that the
+                # shelve was based on matches the revision that we are using for the
+                # starting point for git-fast-import (self.initialParent). Otherwise
+                # the resulting diff will contain deltas from multiple commits.
+
+                if file["action"] != "add" and \
+                    not self.cmp_shelved(path, file["rev"], origin_revision):
+                    sys.exit("change {0} not based on {1} for {2}, cannot unshelve".format(
+                        commit["change"], self.initialParent, path))
+
             files.append(file)
             fnum = fnum + 1
         return files
@@ -2743,7 +2793,16 @@ def streamP4Files(self, files):
             def streamP4FilesCbSelf(entry):
                 self.streamP4FilesCb(entry)
 
-            fileArgs = ['%s#%s' % (f['path'], f['rev']) for f in filesToRead]
+            fileArgs = []
+            for f in filesToRead:
+                if 'shelved_cl' in f:
+                    # Handle shelved CLs using the "p4 print file@=N" syntax to print
+                    # the contents
+                    fileArg = '%s@=%d' % (f['path'], f['shelved_cl'])
+                else:
+                    fileArg = '%s#%s' % (f['path'], f['rev'])
+
+                fileArgs.append(fileArg)
 
             p4CmdList(["-x", "-", "print"],
                       stdin=fileArgs,
@@ -2844,11 +2903,15 @@ def commit(self, details, files, branch, parent = ""):
         self.gitStream.write(details["desc"])
         if len(jobs) > 0:
             self.gitStream.write("\nJobs: %s" % (' '.join(jobs)))
-        self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s" %
-                             (','.join(self.branchPrefixes), details["change"]))
-        if len(details['options']) > 0:
-            self.gitStream.write(": options = %s" % details['options'])
-        self.gitStream.write("]\nEOT\n\n")
+
+        if not self.suppress_meta_comment:
+            self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s" %
+                                (','.join(self.branchPrefixes), details["change"]))
+            if len(details['options']) > 0:
+                self.gitStream.write(": options = %s" % details['options'])
+            self.gitStream.write("]\n")
+
+        self.gitStream.write("EOT\n\n")
 
         if len(parent) > 0:
             if self.verbose:
@@ -3162,10 +3225,10 @@ def searchParent(self, parent, branch, target):
         else:
             return None
 
-    def importChanges(self, changes):
+    def importChanges(self, changes, shelved=False, origin_revision=0):
         cnt = 1
         for change in changes:
-            description = p4_describe(change)
+            description = p4_describe(change, shelved)
             self.updateOptionDict(description)
 
             if not self.silent:
@@ -3235,7 +3298,7 @@ def importChanges(self, changes):
                                 print "Parent of %s not found. Committing into head of %s" % (branch, parent)
                             self.commit(description, filesForCommit, branch, parent)
                 else:
-                    files = self.extractFilesFromCommit(description)
+                    files = self.extractFilesFromCommit(description, shelved, change, origin_revision)
                     self.commit(description, files, self.branch,
                                 self.initialParent)
                     # only needed once, to connect to the previous commit
@@ -3300,17 +3363,23 @@ def importHeadRevision(self, revision):
             print "IO error with git fast-import. Is your git version recent enough?"
             print self.gitError.read()
 
+    def openStreams(self):
+        self.importProcess = subprocess.Popen(["git", "fast-import"],
+                                              stdin=subprocess.PIPE,
+                                              stdout=subprocess.PIPE,
+                                              stderr=subprocess.PIPE);
+        self.gitOutput = self.importProcess.stdout
+        self.gitStream = self.importProcess.stdin
+        self.gitError = self.importProcess.stderr
 
-    def run(self, args):
-        self.depotPaths = []
-        self.changeRange = ""
-        self.previousDepotPaths = []
-        self.hasOrigin = False
-
-        # map from branch depot path to parent branch
-        self.knownBranches = {}
-        self.initialParents = {}
+    def closeStreams(self):
+        self.gitStream.close()
+        if self.importProcess.wait() != 0:
+            die("fast-import failed: %s" % self.gitError.read())
+        self.gitOutput.close()
+        self.gitError.close()
 
+    def run(self, args):
         if self.importIntoRemotes:
             self.refPrefix = "refs/remotes/p4/"
         else:
@@ -3497,15 +3566,7 @@ def run(self, args):
                     b = b[len(self.projectName):]
                 self.createdBranches.add(b)
 
-        self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
-
-        self.importProcess = subprocess.Popen(["git", "fast-import"],
-                                              stdin=subprocess.PIPE,
-                                              stdout=subprocess.PIPE,
-                                              stderr=subprocess.PIPE);
-        self.gitOutput = self.importProcess.stdout
-        self.gitStream = self.importProcess.stdin
-        self.gitError = self.importProcess.stderr
+        self.openStreams()
 
         if revision:
             self.importHeadRevision(revision)
@@ -3585,11 +3646,7 @@ def run(self, args):
             missingP4Labels = p4Labels - gitTags
             self.importP4Labels(self.gitStream, missingP4Labels)
 
-        self.gitStream.close()
-        if self.importProcess.wait() != 0:
-            die("fast-import failed: %s" % self.gitError.read())
-        self.gitOutput.close()
-        self.gitError.close()
+        self.closeStreams()
 
         # Cleanup temporary branches created during import
         if self.tempBranches != []:
@@ -3721,6 +3778,89 @@ def run(self, args):
 
         return True
 
+class P4Unshelve(Command):
+    def __init__(self):
+        Command.__init__(self)
+        self.options = []
+        self.origin = "HEAD"
+        self.description = "Unshelve a P4 changelist into a git commit"
+        self.usage = "usage: %prog [options] changelist"
+        self.options += [
+                optparse.make_option("--origin", dest="origin",
+                    help="Use this base revision instead of the default (%s)" % self.origin),
+        ]
+        self.verbose = False
+        self.noCommit = False
+        self.destbranch = "refs/remotes/p4/unshelved"
+
+    def renameBranch(self, branch_name):
+        """ Rename the existing branch to branch_name.N
+        """
+
+        found = True
+        for i in range(0,1000):
+            backup_branch_name = "{0}.{1}".format(branch_name, i)
+            if not gitBranchExists(backup_branch_name):
+                gitUpdateRef(backup_branch_name, branch_name) # copy ref to backup
+                gitDeleteRef(branch_name)
+                found = True
+                print("renamed old unshelve branch to {0}".format(backup_branch_name))
+                break
+
+        if not found:
+            sys.exit("gave up trying to rename existing branch {0}".format(sync.branch))
+
+    def findLastP4Revision(self, starting_point):
+        """ Look back from starting_point for the first commit created by git-p4
+            to find the P4 commit we are based on, and the depot-paths.
+        """
+
+        for parent in (range(65535)):
+            log = extractLogMessageFromGitCommit("{0}^{1}".format(starting_point, parent))
+            settings = extractSettingsGitLog(log)
+            if settings.has_key('change'):
+                return settings
+
+        sys.exit("could not find git-p4 commits in {0}".format(self.origin))
+
+    def run(self, args):
+        if len(args) != 1:
+            return False
+
+        if not gitBranchExists(self.origin):
+            sys.exit("origin branch {0} does not exist".format(self.origin))
+
+        sync = P4Sync()
+        changes = args
+        sync.initialParent = self.origin
+
+        # use the first change in the list to construct the branch to unshelve into
+        change = changes[0]
+
+        # if the target branch already exists, rename it
+        branch_name = "{0}/{1}".format(self.destbranch, change)
+        if gitBranchExists(branch_name):
+            self.renameBranch(branch_name)
+        sync.branch = branch_name
+
+        sync.verbose = self.verbose
+        sync.suppress_meta_comment = True
+
+        settings = self.findLastP4Revision(self.origin)
+        origin_revision = settings['change']
+        sync.depotPaths = settings['depot-paths']
+        sync.branchPrefixes = sync.depotPaths
+
+        sync.openStreams()
+        sync.loadUserMapFromCache()
+        sync.silent = True
+        sync.importChanges(changes, shelved=True, origin_revision=origin_revision)
+        sync.closeStreams()
+
+        print("unshelved changelist {0} into {1}".format(change, branch_name))
+
+        return True
+
 class P4Branches(Command):
     def __init__(self):
         Command.__init__(self)
@@ -3775,7 +3915,8 @@ def printUsage(commands):
     "rebase" : P4Rebase,
     "clone" : P4Clone,
     "rollback" : P4RollBack,
-    "branches" : P4Branches
+    "branches" : P4Branches,
+    "unshelve" : P4Unshelve,
 }
 
 
index 9947e6265fe7c0bc4531fd928bbf2373a0ae7552..06a7b793075d7701b5dafa69b3576d13ba7c9de6 100644 (file)
@@ -81,6 +81,8 @@ rewritten_pending="$state_dir"/rewritten-pending
 # and leaves CR at the end instead.
 cr=$(printf "\015")
 
+empty_tree=$(git hash-object -t tree /dev/null)
+
 strategy_args=${strategy:+--strategy=$strategy}
 test -n "$strategy_opts" &&
 eval '
@@ -155,13 +157,19 @@ reschedule_last_action () {
 append_todo_help () {
        gettext "
 Commands:
-p, pick = use commit
-r, reword = use commit, but edit the commit message
-e, edit = use commit, but stop for amending
-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
+p, pick <commit> = use commit
+r, reword <commit> = use commit, but edit the commit message
+e, edit <commit> = use commit, but stop for amending
+s, squash <commit> = use commit, but meld into previous commit
+f, fixup <commit> = like \"squash\", but discard this commit's log message
+x, exec <command> = run command (the rest of the line) using shell
+d, drop <commit> = remove commit
+l, label <label> = label current HEAD with a name
+t, reset <label> = reset HEAD to a label
+m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
+.       create a merge commit using the original merge commit's
+.       message (or the oneline, if no original merge commit was
+.       specified). Use -c <commit> to reword the commit message.
 
 These lines can be re-ordered; they are executed from top to bottom.
 " | git stripspace --comment-lines >>"$todo"
@@ -238,7 +246,7 @@ is_empty_commit() {
                die "$(eval_gettext "\$sha1: not a commit that can be picked")"
        }
        ptree=$(git rev-parse -q --verify "$1"^^{tree} 2>/dev/null) ||
-               ptree=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+               ptree=$empty_tree
        test "$tree" = "$ptree"
 }
 
@@ -888,6 +896,8 @@ init_revisions_and_shortrevisions () {
        else
                revisions=$onto...$orig_head
                shortrevisions=$shorthead
+               test -z "$squash_onto" ||
+               echo "$squash_onto" >"$state_dir"/squash-onto
        fi
 }
 
@@ -942,7 +952,7 @@ EOF
        die "Could not skip unnecessary pick commands"
 
        checkout_onto
-       if test -z "$rebase_root" && test ! -d "$rewritten"
+       if test ! -d "$rewritten"
        then
                require_clean_work_tree "rebase"
                exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \
@@ -964,6 +974,8 @@ git_rebase__interactive () {
        init_revisions_and_shortrevisions
 
        git rebase--helper --make-script ${keep_empty:+--keep-empty} \
+               ${rebase_merges:+--rebase-merges} \
+               ${rebase_cousins:+--rebase-cousins} \
                $revisions ${restrict_revision+^$restrict_revision} >"$todo" ||
        die "$(gettext "Could not generate todo list")"
 
index ded5de085a87505b244b7449b1977c3779650640..40be59ecc4704da05a0e153f1709d4af2c62cd18 100755 (executable)
@@ -17,6 +17,7 @@ q,quiet!           be quiet. implies --no-stat
 autostash          automatically stash/stash pop before and after
 fork-point         use 'merge-base --fork-point' to refine upstream
 onto=!             rebase onto given branch instead of upstream
+r,rebase-merges?   try to rebase merges instead of skipping them
 p,preserve-merges! try to recreate merges instead of ignoring them
 s,strategy=!       use the given merge strategy
 no-ff!             cherry-pick all commits, even if unchanged
@@ -89,6 +90,8 @@ type=
 state_dir=
 # One of {'', continue, skip, abort}, as parsed from command line
 action=
+rebase_merges=
+rebase_cousins=
 preserve_merges=
 autosquash=
 keep_empty=
@@ -280,6 +283,19 @@ do
        --no-keep-empty)
                keep_empty=
                ;;
+       --rebase-merges)
+               rebase_merges=t
+               test -z "$interactive_rebase" && interactive_rebase=implied
+               ;;
+       --rebase-merges=*)
+               rebase_merges=t
+               case "${1#*=}" in
+               rebase-cousins) rebase_cousins=t;;
+               no-rebase-cousins) rebase_cousins=;;
+               *) die "Unknown mode: $1";;
+               esac
+               test -z "$interactive_rebase" && interactive_rebase=implied
+               ;;
        --preserve-merges)
                preserve_merges=t
                test -z "$interactive_rebase" && interactive_rebase=implied
index 7157397fd03a0791b7722ab49aa696181977381c..8ec70e58ed5c4caace7701549038b63f9a3e9aad 100755 (executable)
@@ -1330,9 +1330,14 @@ sub file_name_is_absolute {
        return File::Spec::Functions::file_name_is_absolute($path);
 }
 
-# Returns 1 if the message was sent, and 0 otherwise.
-# In actuality, the whole program dies when there
-# is an error sending a message.
+# Prepares the email, then asks the user what to do.
+#
+# If the user chooses to send the email, it's sent and 1 is returned.
+# If the user chooses not to send the email, 0 is returned.
+# If the user decides they want to make further edits, -1 is returned and the
+# caller is expected to call send_message again after the edits are performed.
+#
+# If an error occurs sending the email, this just dies.
 
 sub send_message {
        my @recipients = unique_email_list(@to);
@@ -1404,15 +1409,17 @@ sub send_message {
 
 EOF
                }
-               # TRANSLATORS: Make sure to include [y] [n] [q] [a] in your
+               # TRANSLATORS: Make sure to include [y] [n] [e] [q] [a] in your
                # translation. The program will only accept English input
                # at this point.
-               $_ = ask(__("Send this email? ([y]es|[n]o|[q]uit|[a]ll): "),
-                        valid_re => qr/^(?:yes|y|no|n|quit|q|all|a)/i,
+               $_ = ask(__("Send this email? ([y]es|[n]o|[e]dit|[q]uit|[a]ll): "),
+                        valid_re => qr/^(?:yes|y|no|n|edit|e|quit|q|all|a)/i,
                         default => $ask_default);
                die __("Send this email reply required") unless defined $_;
                if (/^n/i) {
                        return 0;
+               } elsif (/^e/i) {
+                       return -1;
                } elsif (/^q/i) {
                        cleanup_compose_files();
                        exit(0);
@@ -1552,7 +1559,12 @@ sub send_message {
 $subject = $initial_subject;
 $message_num = 0;
 
-foreach my $t (@files) {
+# Prepares the email, prompts the user, sends it out
+# Returns 0 if an edit was done and the function should be called again, or 1
+# otherwise.
+sub process_file {
+       my ($t) = @_;
+
        open my $fh, "<", $t or die sprintf(__("can't open file %s"), $t);
 
        my $author = undef;
@@ -1760,6 +1772,10 @@ sub send_message {
        }
 
        my $message_was_sent = send_message();
+       if ($message_was_sent == -1) {
+               do_edit($t);
+               return 0;
+       }
 
        # set up for the next message
        if ($thread && $message_was_sent &&
@@ -1781,6 +1797,14 @@ sub send_message {
                undef $auth;
                sleep($relogin_delay) if defined $relogin_delay;
        }
+
+       return 1;
+}
+
+foreach my $t (@files) {
+       while (!process_file($t)) {
+               # user edited the file
+       }
 }
 
 # Execute a command (e.g. $to_cmd) to get a list of email addresses
index 24914963ca23c837e0cc46ca2dc0fa46bf9886a6..78073cd87d1b1775bb4f4264557aba18c73c5911 100755 (executable)
@@ -42,6 +42,7 @@ prefix=
 custom_name=
 depth=
 progress=
+dissociate=
 
 die_if_unmatched ()
 {
@@ -117,6 +118,9 @@ cmd_add()
                -q|--quiet)
                        GIT_QUIET=1
                        ;;
+               --progress)
+                       progress=1
+                       ;;
                --reference)
                        case "$2" in '') usage ;; esac
                        reference_path=$2
@@ -125,6 +129,9 @@ cmd_add()
                --reference=*)
                        reference_path="${1#--reference=}"
                        ;;
+               --dissociate)
+                       dissociate=1
+                       ;;
                --name)
                        case "$2" in '') usage ;; esac
                        custom_name=$2
@@ -229,6 +236,11 @@ Use -f if you really want to add it." >&2
                sm_name="$sm_path"
        fi
 
+       if ! git submodule--helper check-name "$sm_name"
+       then
+               die "$(eval_gettext "'$sm_name' is not a valid submodule name")"
+       fi
+
        # perhaps the path exists and is already a git repo, else clone it
        if test -e "$sm_path"
        then
@@ -255,7 +267,7 @@ or you are unsure what this means choose another name with the '--name' option."
                                eval_gettextln "Reactivating local git directory for submodule '\$sm_name'."
                        fi
                fi
-               git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" ${reference:+"$reference"} ${depth:+"$depth"} || exit
+               git submodule--helper clone ${GIT_QUIET:+--quiet} ${progress:+"--progress"} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" ${reference:+"$reference"} ${dissociate:+"--dissociate"} ${depth:+"$depth"} || exit
                (
                        sanitize_submodule_env
                        cd "$sm_path" &&
@@ -465,7 +477,7 @@ cmd_update()
                        GIT_QUIET=1
                        ;;
                --progress)
-                       progress="--progress"
+                       progress=1
                        ;;
                -i|--init)
                        init=1
@@ -490,6 +502,9 @@ cmd_update()
                --reference=*)
                        reference="$1"
                        ;;
+               --dissociate)
+                       dissociate=1
+                       ;;
                -m|--merge)
                        update="merge"
                        ;;
@@ -542,14 +557,15 @@ cmd_update()
 
        {
        git submodule--helper update-clone ${GIT_QUIET:+--quiet} \
-               ${progress:+"$progress"} \
+               ${progress:+"--progress"} \
                ${wt_prefix:+--prefix "$wt_prefix"} \
                ${prefix:+--recursive-prefix "$prefix"} \
                ${update:+--update "$update"} \
                ${reference:+"$reference"} \
+               ${dissociate:+"--dissociate"} \
                ${depth:+--depth "$depth"} \
-               ${recommend_shallow:+"$recommend_shallow"} \
-               ${jobs:+$jobs} \
+               $recommend_shallow \
+               $jobs \
                "$@" || echo "#unmatched" $?
        } | {
        err=
@@ -614,7 +630,7 @@ cmd_update()
                                # is not reachable from a ref.
                                is_tip_reachable "$sm_path" "$sha1" ||
                                fetch_in_submodule "$sm_path" $depth ||
-                               die "$(eval_gettext "Unable to fetch in submodule path '\$displaypath'")"
+                               say "$(eval_gettext "Unable to fetch in submodule path '\$displaypath'")"
 
                                # Now we tried the usual fetch, but $sha1 may
                                # not be reachable from any of the refs
diff --git a/git.c b/git.c
index bab6bbfded0a66c9577bf37e0b87cd640c8c50be..c2f48d53dd4aaba0752aa5f2e6633242d320b248 100644 (file)
--- a/git.c
+++ b/git.c
@@ -3,6 +3,7 @@
 #include "exec-cmd.h"
 #include "help.h"
 #include "run-command.h"
+#include "alias.h"
 
 #define RUN_SETUP              (1<<0)
 #define RUN_SETUP_GENTLY       (1<<1)
@@ -25,7 +26,7 @@ struct cmd_struct {
 const char git_usage_string[] =
        N_("git [--version] [--help] [-C <path>] [-c <name>=<value>]\n"
           "           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n"
-          "           [-p | --paginate | --no-pager] [--no-replace-objects] [--bare]\n"
+          "           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]\n"
           "           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\n"
           "           <command> [<args>]");
 
@@ -36,7 +37,66 @@ const char git_more_info_string[] =
 
 static int use_pager = -1;
 
-static void list_builtins(unsigned int exclude_option, char sep);
+static void list_builtins(struct string_list *list, unsigned int exclude_option);
+
+static void exclude_helpers_from_list(struct string_list *list)
+{
+       int i = 0;
+
+       while (i < list->nr) {
+               if (strstr(list->items[i].string, "--"))
+                       unsorted_string_list_delete_item(list, i, 0);
+               else
+                       i++;
+       }
+}
+
+static int match_token(const char *spec, int len, const char *token)
+{
+       int token_len = strlen(token);
+
+       return len == token_len && !strncmp(spec, token, token_len);
+}
+
+static int list_cmds(const char *spec)
+{
+       struct string_list list = STRING_LIST_INIT_DUP;
+       int i;
+
+       while (*spec) {
+               const char *sep = strchrnul(spec, ',');
+               int len = sep - spec;
+
+               if (match_token(spec, len, "builtins"))
+                       list_builtins(&list, 0);
+               else if (match_token(spec, len, "main"))
+                       list_all_main_cmds(&list);
+               else if (match_token(spec, len, "others"))
+                       list_all_other_cmds(&list);
+               else if (match_token(spec, len, "nohelpers"))
+                       exclude_helpers_from_list(&list);
+               else if (match_token(spec, len, "alias"))
+                       list_aliases(&list);
+               else if (match_token(spec, len, "config"))
+                       list_cmds_by_config(&list);
+               else if (len > 5 && !strncmp(spec, "list-", 5)) {
+                       struct strbuf sb = STRBUF_INIT;
+
+                       strbuf_add(&sb, spec + 5, len - 5);
+                       list_cmds_by_category(&list, sb.buf);
+                       strbuf_release(&sb);
+               }
+               else
+                       die(_("unsupported command listing type '%s'"), spec);
+               spec += len;
+               if (*spec == ',')
+                       spec++;
+       }
+       for (i = 0; i < list.nr; i++)
+               puts(list.items[i].string);
+       string_list_clear(&list, 0);
+       return 0;
+}
 
 static void commit_pager_choice(void) {
        switch (use_pager) {
@@ -99,7 +159,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
                        exit(0);
                } else if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) {
                        use_pager = 1;
-               } else if (!strcmp(cmd, "--no-pager")) {
+               } else if (!strcmp(cmd, "-P") || !strcmp(cmd, "--no-pager")) {
                        use_pager = 0;
                        if (envchanged)
                                *envchanged = 1;
@@ -223,12 +283,19 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
                        }
                        (*argv)++;
                        (*argc)--;
-               } else if (!strcmp(cmd, "--list-builtins")) {
-                       list_builtins(0, '\n');
-                       exit(0);
-               } else if (!strcmp(cmd, "--list-parseopt-builtins")) {
-                       list_builtins(NO_PARSEOPT, ' ');
-                       exit(0);
+               } else if (skip_prefix(cmd, "--list-cmds=", &cmd)) {
+                       if (!strcmp(cmd, "parseopt")) {
+                               struct string_list list = STRING_LIST_INIT_DUP;
+                               int i;
+
+                               list_builtins(&list, NO_PARSEOPT);
+                               for (i = 0; i < list.nr; i++)
+                                       printf("%s ", list.items[i].string);
+                               string_list_clear(&list, 0);
+                               exit(0);
+                       } else {
+                               exit(list_cmds(cmd));
+                       }
                } else {
                        fprintf(stderr, _("unknown option: %s\n"), cmd);
                        usage(git_usage_string);
@@ -511,14 +578,14 @@ int is_builtin(const char *s)
        return !!get_builtin(s);
 }
 
-static void list_builtins(unsigned int exclude_option, char sep)
+static void list_builtins(struct string_list *out, unsigned int exclude_option)
 {
        int i;
        for (i = 0; i < ARRAY_SIZE(commands); i++) {
                if (exclude_option &&
                    (commands[i].option & exclude_option))
                        continue;
-               printf("%s%c", commands[i].cmd, sep);
+               string_list_append(out, commands[i].cmd);
        }
 }
 
diff --git a/grep.c b/grep.c
index 65b90c10a38c136d2ce9a42fee5c543cd7d4717d..45ec7e636c004dbd35d4e0d05c243bc1d1479d19 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -404,7 +404,7 @@ static void compile_pcre1_regexp(struct grep_pat *p, const struct grep_opt *opt)
                        die("Couldn't allocate PCRE JIT stack");
                pcre_assign_jit_stack(p->pcre1_extra_info, NULL, p->pcre1_jit_stack);
        } else if (p->pcre1_jit_on != 0) {
-               die("BUG: The pcre1_jit_on variable should be 0 or 1, not %d",
+               BUG("The pcre1_jit_on variable should be 0 or 1, not %d",
                    p->pcre1_jit_on);
        }
 #endif
@@ -550,7 +550,7 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt
                        die("Couldn't allocate PCRE2 match context");
                pcre2_jit_stack_assign(p->pcre2_match_context, NULL, p->pcre2_jit_stack);
        } else if (p->pcre2_jit_on != 0) {
-               die("BUG: The pcre2_jit_on variable should be 0 or 1, not %d",
+               BUG("The pcre2_jit_on variable should be 0 or 1, not %d",
                    p->pcre1_jit_on);
        }
 }
@@ -636,7 +636,6 @@ static void compile_fixed_regexp(struct grep_pat *p, struct grep_opt *opt)
        if (err) {
                char errbuf[1024];
                regerror(err, &p->regexp, errbuf, sizeof(errbuf));
-               regfree(&p->regexp);
                compile_regexp_failed(p, errbuf);
        }
 }
@@ -701,7 +700,6 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
        if (err) {
                char errbuf[1024];
                regerror(err, &p->regexp, errbuf, 1024);
-               regfree(&p->regexp);
                compile_regexp_failed(p, errbuf);
        }
 }
@@ -917,10 +915,10 @@ static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
 
        for (p = opt->header_list; p; p = p->next) {
                if (p->token != GREP_PATTERN_HEAD)
-                       die("BUG: a non-header pattern in grep header list.");
+                       BUG("a non-header pattern in grep header list.");
                if (p->field < GREP_HEADER_FIELD_MIN ||
                    GREP_HEADER_FIELD_MAX <= p->field)
-                       die("BUG: unknown header field %d", p->field);
+                       BUG("unknown header field %d", p->field);
                compile_regexp(p, opt);
        }
 
@@ -933,7 +931,7 @@ static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
 
                h = compile_pattern_atom(&pp);
                if (!h || pp != p->next)
-                       die("BUG: malformed header expr");
+                       BUG("malformed header expr");
                if (!header_group[p->field]) {
                        header_group[p->field] = h;
                        continue;
@@ -1652,7 +1650,7 @@ static int fill_textconv_grep(struct userdiff_driver *driver,
                fill_filespec(df, &null_oid, 0, 0100644);
                break;
        default:
-               die("BUG: attempt to textconv something without a path?");
+               BUG("attempt to textconv something without a path?");
        }
 
        /*
@@ -1748,7 +1746,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
                case GREP_BINARY_TEXT:
                        break;
                default:
-                       die("BUG: unknown binary handling mode");
+                       BUG("unknown binary handling mode");
                }
        }
 
@@ -2072,7 +2070,7 @@ static int grep_source_load(struct grep_source *gs)
        case GREP_SOURCE_BUF:
                return gs->buf ? 0 : -1;
        }
-       die("BUG: invalid grep_source type to load");
+       BUG("invalid grep_source type to load");
 }
 
 void grep_source_load_driver(struct grep_source *gs)
diff --git a/help.c b/help.c
index a4feef2ffe90ae853c220b9368642c4edb503e5e..dd35fcc133094e4dc89898463868e89162637076 100644 (file)
--- a/help.c
+++ b/help.c
 #include "run-command.h"
 #include "levenshtein.h"
 #include "help.h"
-#include "common-cmds.h"
+#include "command-list.h"
 #include "string-list.h"
 #include "column.h"
 #include "version.h"
 #include "refs.h"
 #include "parse-options.h"
 
+struct category_description {
+       uint32_t category;
+       const char *desc;
+};
+static uint32_t common_mask =
+       CAT_init | CAT_worktree | CAT_info |
+       CAT_history | CAT_remote;
+static struct category_description common_categories[] = {
+       { CAT_init, N_("start a working area (see also: git help tutorial)") },
+       { CAT_worktree, N_("work on the current change (see also: git help everyday)") },
+       { CAT_info, N_("examine the history and state (see also: git help revisions)") },
+       { CAT_history, N_("grow, mark and tweak your common history") },
+       { CAT_remote, N_("collaborate (see also: git help workflows)") },
+       { 0, NULL }
+};
+static struct category_description main_categories[] = {
+       { CAT_mainporcelain, N_("Main Porcelain Commands") },
+       { CAT_ancillarymanipulators, N_("Ancillary Commands / Manipulators") },
+       { CAT_ancillaryinterrogators, N_("Ancillary Commands / Interrogators") },
+       { CAT_foreignscminterface, N_("Interacting with Others") },
+       { CAT_plumbingmanipulators, N_("Low-level Commands / Manipulators") },
+       { CAT_plumbinginterrogators, N_("Low-level Commands / Interrogators") },
+       { CAT_synchingrepositories, N_("Low-level Commands / Synching Repositories") },
+       { CAT_purehelpers, N_("Low-level Commands / Internal Helpers") },
+       { 0, NULL }
+};
+
+static const char *drop_prefix(const char *name, uint32_t category)
+{
+       const char *new_name;
+
+       if (skip_prefix(name, "git-", &new_name))
+               return new_name;
+       if (category == CAT_guide && skip_prefix(name, "git", &new_name))
+               return new_name;
+       return name;
+
+}
+
+static void extract_cmds(struct cmdname_help **p_cmds, uint32_t mask)
+{
+       int i, nr = 0;
+       struct cmdname_help *cmds;
+
+       if (ARRAY_SIZE(command_list) == 0)
+               BUG("empty command_list[] is a sign of broken generate-cmdlist.sh");
+
+       ALLOC_ARRAY(cmds, ARRAY_SIZE(command_list) + 1);
+
+       for (i = 0; i < ARRAY_SIZE(command_list); i++) {
+               const struct cmdname_help *cmd = command_list + i;
+
+               if (!(cmd->category & mask))
+                       continue;
+
+               cmds[nr] = *cmd;
+               cmds[nr].name = drop_prefix(cmd->name, cmd->category);
+
+               nr++;
+       }
+       cmds[nr].name = NULL;
+       *p_cmds = cmds;
+}
+
+static void print_command_list(const struct cmdname_help *cmds,
+                              uint32_t mask, int longest)
+{
+       int i;
+
+       for (i = 0; cmds[i].name; i++) {
+               if (cmds[i].category & mask) {
+                       printf("   %s   ", cmds[i].name);
+                       mput_char(' ', longest - strlen(cmds[i].name));
+                       puts(_(cmds[i].help));
+               }
+       }
+}
+
+static int cmd_name_cmp(const void *elem1, const void *elem2)
+{
+       const struct cmdname_help *e1 = elem1;
+       const struct cmdname_help *e2 = elem2;
+
+       return strcmp(e1->name, e2->name);
+}
+
+static void print_cmd_by_category(const struct category_description *catdesc)
+{
+       struct cmdname_help *cmds;
+       int longest = 0;
+       int i, nr = 0;
+       uint32_t mask = 0;
+
+       for (i = 0; catdesc[i].desc; i++)
+               mask |= catdesc[i].category;
+
+       extract_cmds(&cmds, mask);
+
+       for (i = 0; cmds[i].name; i++, nr++) {
+               if (longest < strlen(cmds[i].name))
+                       longest = strlen(cmds[i].name);
+       }
+       QSORT(cmds, nr, cmd_name_cmp);
+
+       for (i = 0; catdesc[i].desc; i++) {
+               uint32_t mask = catdesc[i].category;
+               const char *desc = catdesc[i].desc;
+
+               printf("\n%s\n", _(desc));
+               print_command_list(cmds, mask, longest);
+       }
+       free(cmds);
+}
+
 void add_cmdname(struct cmdnames *cmds, const char *name, int len)
 {
        struct cmdname *ent;
@@ -190,44 +304,116 @@ void list_commands(unsigned int colopts,
        }
 }
 
-static int cmd_group_cmp(const void *elem1, const void *elem2)
+void list_common_cmds_help(void)
 {
-       const struct cmdname_help *e1 = elem1;
-       const struct cmdname_help *e2 = elem2;
+       puts(_("These are common Git commands used in various situations:"));
+       print_cmd_by_category(common_categories);
+}
 
-       if (e1->group < e2->group)
-               return -1;
-       if (e1->group > e2->group)
-               return 1;
-       return strcmp(e1->name, e2->name);
+void list_all_main_cmds(struct string_list *list)
+{
+       struct cmdnames main_cmds, other_cmds;
+       int i;
+
+       memset(&main_cmds, 0, sizeof(main_cmds));
+       memset(&other_cmds, 0, sizeof(other_cmds));
+       load_command_list("git-", &main_cmds, &other_cmds);
+
+       for (i = 0; i < main_cmds.cnt; i++)
+               string_list_append(list, main_cmds.names[i]->name);
+
+       clean_cmdnames(&main_cmds);
+       clean_cmdnames(&other_cmds);
 }
 
-void list_common_cmds_help(void)
+void list_all_other_cmds(struct string_list *list)
 {
-       int i, longest = 0;
-       int current_grp = -1;
+       struct cmdnames main_cmds, other_cmds;
+       int i;
 
-       for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
-               if (longest < strlen(common_cmds[i].name))
-                       longest = strlen(common_cmds[i].name);
-       }
+       memset(&main_cmds, 0, sizeof(main_cmds));
+       memset(&other_cmds, 0, sizeof(other_cmds));
+       load_command_list("git-", &main_cmds, &other_cmds);
 
-       QSORT(common_cmds, ARRAY_SIZE(common_cmds), cmd_group_cmp);
+       for (i = 0; i < other_cmds.cnt; i++)
+               string_list_append(list, other_cmds.names[i]->name);
 
-       puts(_("These are common Git commands used in various situations:"));
+       clean_cmdnames(&main_cmds);
+       clean_cmdnames(&other_cmds);
+}
+
+void list_cmds_by_category(struct string_list *list,
+                          const char *cat)
+{
+       int i, n = ARRAY_SIZE(command_list);
+       uint32_t cat_id = 0;
 
-       for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
-               if (common_cmds[i].group != current_grp) {
-                       printf("\n%s\n", _(common_cmd_groups[common_cmds[i].group]));
-                       current_grp = common_cmds[i].group;
+       for (i = 0; category_names[i]; i++) {
+               if (!strcmp(cat, category_names[i])) {
+                       cat_id = 1UL << i;
+                       break;
                }
+       }
+       if (!cat_id)
+               die(_("unsupported command listing type '%s'"), cat);
+
+       for (i = 0; i < n; i++) {
+               struct cmdname_help *cmd = command_list + i;
 
-               printf("   %s   ", common_cmds[i].name);
-               mput_char(' ', longest - strlen(common_cmds[i].name));
-               puts(_(common_cmds[i].help));
+               if (!(cmd->category & cat_id))
+                       continue;
+               string_list_append(list, drop_prefix(cmd->name, cmd->category));
        }
 }
 
+void list_cmds_by_config(struct string_list *list)
+{
+       const char *cmd_list;
+
+       /*
+        * There's no actual repository setup at this point (and even
+        * if there is, we don't really care; only global config
+        * matters). If we accidentally set up a repository, it's ok
+        * too since the caller (git --list-cmds=) should exit shortly
+        * anyway.
+        */
+       if (git_config_get_string_const("completion.commands", &cmd_list))
+               return;
+
+       string_list_sort(list);
+       string_list_remove_duplicates(list, 0);
+
+       while (*cmd_list) {
+               struct strbuf sb = STRBUF_INIT;
+               const char *p = strchrnul(cmd_list, ' ');
+
+               strbuf_add(&sb, cmd_list, p - cmd_list);
+               if (*cmd_list == '-')
+                       string_list_remove(list, cmd_list + 1, 0);
+               else
+                       string_list_insert(list, sb.buf);
+               strbuf_release(&sb);
+               while (*p == ' ')
+                       p++;
+               cmd_list = p;
+       }
+}
+
+void list_common_guides_help(void)
+{
+       struct category_description catdesc[] = {
+               { CAT_guide, N_("The common Git guides are:") },
+               { 0, NULL }
+       };
+       print_cmd_by_category(catdesc);
+       putchar('\n');
+}
+
+void list_all_cmds_help(void)
+{
+       print_cmd_by_category(main_categories);
+}
+
 int is_in_cmdlist(struct cmdnames *c, const char *s)
 {
        int i;
@@ -285,6 +471,7 @@ const char *help_unknown_cmd(const char *cmd)
 {
        int i, n, best_similarity = 0;
        struct cmdnames main_cmds, other_cmds;
+       struct cmdname_help *common_cmds;
 
        memset(&main_cmds, 0, sizeof(main_cmds));
        memset(&other_cmds, 0, sizeof(other_cmds));
@@ -299,6 +486,8 @@ const char *help_unknown_cmd(const char *cmd)
        QSORT(main_cmds.names, main_cmds.cnt, cmdname_compare);
        uniq(&main_cmds);
 
+       extract_cmds(&common_cmds, common_mask);
+
        /* This abuses cmdname->len for levenshtein distance */
        for (i = 0, n = 0; i < main_cmds.cnt; i++) {
                int cmp = 0; /* avoid compiler stupidity */
@@ -313,10 +502,10 @@ const char *help_unknown_cmd(const char *cmd)
                        die(_(bad_interpreter_advice), cmd, cmd);
 
                /* Does the candidate appear in common_cmds list? */
-               while (n < ARRAY_SIZE(common_cmds) &&
+               while (common_cmds[n].name &&
                       (cmp = strcmp(common_cmds[n].name, candidate)) < 0)
                        n++;
-               if ((n < ARRAY_SIZE(common_cmds)) && !cmp) {
+               if (common_cmds[n].name && !cmp) {
                        /* Yes, this is one of the common commands */
                        n++; /* use the entry from common_cmds[] */
                        if (starts_with(candidate, cmd)) {
@@ -329,6 +518,7 @@ const char *help_unknown_cmd(const char *cmd)
                main_cmds.names[i]->len =
                        levenshtein(cmd, candidate, 0, 2, 1, 3) + 1;
        }
+       FREE_AND_NULL(common_cmds);
 
        QSORT(main_cmds.names, main_cmds.cnt, levenshtein_compare);
 
diff --git a/help.h b/help.h
index b21d7c94e8ce429ac1f58bd566c69e154d84d6a0..3b38292a1b317ba32d934be6006cb86e0e7d0d94 100644 (file)
--- a/help.h
+++ b/help.h
@@ -1,6 +1,8 @@
 #ifndef HELP_H
 #define HELP_H
 
+struct string_list;
+
 struct cmdnames {
        int alloc;
        int cnt;
@@ -17,6 +19,14 @@ static inline void mput_char(char c, unsigned int num)
 }
 
 extern void list_common_cmds_help(void);
+extern void list_all_cmds_help(void);
+extern void list_common_guides_help(void);
+
+extern void list_all_main_cmds(struct string_list *list);
+extern void list_all_other_cmds(struct string_list *list);
+extern void list_cmds_by_category(struct string_list *list,
+                                 const char *category);
+extern void list_cmds_by_config(struct string_list *list);
 extern const char *help_unknown_cmd(const char *cmd);
 extern void load_command_list(const char *prefix,
                              struct cmdnames *main_cmds,
index ea5af6227e075389417b4f541b3cfbe3041bb354..7e38522098bf91c144adaee7f9f5476f8bc72cd2 100644 (file)
@@ -1331,7 +1331,7 @@ static int get_delta(struct rev_info *revs, struct remote_lock *lock)
        int count = 0;
 
        while ((commit = get_revision(revs)) != NULL) {
-               p = process_tree(commit->tree, p);
+               p = process_tree(get_commit_tree(commit), p);
                commit->object.flags |= LOCAL;
                if (!(commit->object.flags & UNINTERESTING))
                        count += add_send_request(&commit->object, lock);
diff --git a/http.c b/http.c
index fed13b2169a49aa602fc2ff21d45e368e1734d79..b4bfbceaeb7e58e79cac17f8768a7e36cff3405e 100644 (file)
--- a/http.c
+++ b/http.c
@@ -1788,7 +1788,7 @@ static int http_request(const char *url,
 
        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers);
-       curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "gzip");
+       curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "");
 
        ret = run_one_slot(slot, &results);
 
@@ -1846,7 +1846,7 @@ static int update_url_from_redirect(struct strbuf *base,
                return 0;
 
        if (!skip_prefix(asked, base->buf, &tail))
-               die("BUG: update_url_from_redirect: %s is not a superset of %s",
+               BUG("update_url_from_redirect: %s is not a superset of %s",
                    asked, base->buf);
 
        new_len = got->len;
@@ -1894,7 +1894,7 @@ static int http_request_reauth(const char *url,
                        strbuf_reset(result);
                        break;
                default:
-                       die("BUG: HTTP_KEEP_ERROR is only supported with strbufs");
+                       BUG("HTTP_KEEP_ERROR is only supported with strbufs");
                }
        }
 
@@ -2038,7 +2038,8 @@ int http_get_info_packs(const char *base_url, struct packed_git **packs_head)
        int ret = 0, i = 0;
        char *url, *data;
        struct strbuf buf = STRBUF_INIT;
-       unsigned char sha1[20];
+       unsigned char hash[GIT_MAX_RAWSZ];
+       const unsigned hexsz = the_hash_algo->hexsz;
 
        end_url_with_slash(&buf, base_url);
        strbuf_addstr(&buf, "objects/info/packs");
@@ -2054,13 +2055,13 @@ int http_get_info_packs(const char *base_url, struct packed_git **packs_head)
                switch (data[i]) {
                case 'P':
                        i++;
-                       if (i + 52 <= buf.len &&
+                       if (i + hexsz + 12 <= buf.len &&
                            starts_with(data + i, " pack-") &&
-                           starts_with(data + i + 46, ".pack\n")) {
-                               get_sha1_hex(data + i + 6, sha1);
-                               fetch_and_setup_pack_index(packs_head, sha1,
+                           starts_with(data + i + hexsz + 6, ".pack\n")) {
+                               get_sha1_hex(data + i + 6, hash);
+                               fetch_and_setup_pack_index(packs_head, hash,
                                                      base_url);
-                               i += 51;
+                               i += hexsz + 11;
                                break;
                        }
                default:
@@ -2082,6 +2083,7 @@ void release_http_pack_request(struct http_pack_request *preq)
                preq->packfile = NULL;
        }
        preq->slot = NULL;
+       strbuf_release(&preq->tmpfile);
        free(preq->url);
        free(preq);
 }
@@ -2104,19 +2106,19 @@ int finish_http_pack_request(struct http_pack_request *preq)
                lst = &((*lst)->next);
        *lst = (*lst)->next;
 
-       if (!strip_suffix(preq->tmpfile, ".pack.temp", &len))
-               die("BUG: pack tmpfile does not end in .pack.temp?");
-       tmp_idx = xstrfmt("%.*s.idx.temp", (int)len, preq->tmpfile);
+       if (!strip_suffix(preq->tmpfile.buf, ".pack.temp", &len))
+               BUG("pack tmpfile does not end in .pack.temp?");
+       tmp_idx = xstrfmt("%.*s.idx.temp", (int)len, preq->tmpfile.buf);
 
        argv_array_push(&ip.args, "index-pack");
        argv_array_pushl(&ip.args, "-o", tmp_idx, NULL);
-       argv_array_push(&ip.args, preq->tmpfile);
+       argv_array_push(&ip.args, preq->tmpfile.buf);
        ip.git_cmd = 1;
        ip.no_stdin = 1;
        ip.no_stdout = 1;
 
        if (run_command(&ip)) {
-               unlink(preq->tmpfile);
+               unlink(preq->tmpfile.buf);
                unlink(tmp_idx);
                free(tmp_idx);
                return -1;
@@ -2124,7 +2126,7 @@ int finish_http_pack_request(struct http_pack_request *preq)
 
        unlink(sha1_pack_index_name(p->sha1));
 
-       if (finalize_object_file(preq->tmpfile, sha1_pack_name(p->sha1))
+       if (finalize_object_file(preq->tmpfile.buf, sha1_pack_name(p->sha1))
         || finalize_object_file(tmp_idx, sha1_pack_index_name(p->sha1))) {
                free(tmp_idx);
                return -1;
@@ -2143,6 +2145,7 @@ struct http_pack_request *new_http_pack_request(
        struct http_pack_request *preq;
 
        preq = xcalloc(1, sizeof(*preq));
+       strbuf_init(&preq->tmpfile, 0);
        preq->target = target;
 
        end_url_with_slash(&buf, base_url);
@@ -2150,12 +2153,11 @@ struct http_pack_request *new_http_pack_request(
                sha1_to_hex(target->sha1));
        preq->url = strbuf_detach(&buf, NULL);
 
-       snprintf(preq->tmpfile, sizeof(preq->tmpfile), "%s.temp",
-               sha1_pack_name(target->sha1));
-       preq->packfile = fopen(preq->tmpfile, "a");
+       strbuf_addf(&preq->tmpfile, "%s.temp", sha1_pack_name(target->sha1));
+       preq->packfile = fopen(preq->tmpfile.buf, "a");
        if (!preq->packfile) {
                error("Unable to open local file %s for pack",
-                     preq->tmpfile);
+                     preq->tmpfile.buf);
                goto abort;
        }
 
@@ -2182,6 +2184,7 @@ struct http_pack_request *new_http_pack_request(
        return preq;
 
 abort:
+       strbuf_release(&preq->tmpfile);
        free(preq->url);
        free(preq);
        return NULL;
@@ -2201,7 +2204,7 @@ static size_t fwrite_sha1_file(char *ptr, size_t eltsize, size_t nmemb,
                CURLcode c = curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE,
                                                &slot->http_code);
                if (c != CURLE_OK)
-                       die("BUG: curl_easy_getinfo for HTTP code failed: %s",
+                       BUG("curl_easy_getinfo for HTTP code failed: %s",
                                curl_easy_strerror(c));
                if (slot->http_code >= 300)
                        return size;
@@ -2232,7 +2235,7 @@ struct http_object_request *new_http_object_request(const char *base_url,
 {
        char *hex = sha1_to_hex(sha1);
        struct strbuf filename = STRBUF_INIT;
-       char prevfile[PATH_MAX];
+       struct strbuf prevfile = STRBUF_INIT;
        int prevlocal;
        char prev_buf[PREV_BUF_SIZE];
        ssize_t prev_read = 0;
@@ -2240,40 +2243,41 @@ struct http_object_request *new_http_object_request(const char *base_url,
        struct http_object_request *freq;
 
        freq = xcalloc(1, sizeof(*freq));
+       strbuf_init(&freq->tmpfile, 0);
        hashcpy(freq->sha1, sha1);
        freq->localfile = -1;
 
        sha1_file_name(the_repository, &filename, sha1);
-       snprintf(freq->tmpfile, sizeof(freq->tmpfile),
-                "%s.temp", filename.buf);
+       strbuf_addf(&freq->tmpfile, "%s.temp", filename.buf);
 
-       snprintf(prevfile, sizeof(prevfile), "%s.prev", filename.buf);
-       unlink_or_warn(prevfile);
-       rename(freq->tmpfile, prevfile);
-       unlink_or_warn(freq->tmpfile);
+       strbuf_addf(&prevfile, "%s.prev", filename.buf);
+       unlink_or_warn(prevfile.buf);
+       rename(freq->tmpfile.buf, prevfile.buf);
+       unlink_or_warn(freq->tmpfile.buf);
        strbuf_release(&filename);
 
        if (freq->localfile != -1)
                error("fd leakage in start: %d", freq->localfile);
-       freq->localfile = open(freq->tmpfile,
+       freq->localfile = open(freq->tmpfile.buf,
                               O_WRONLY | O_CREAT | O_EXCL, 0666);
        /*
         * This could have failed due to the "lazy directory creation";
         * try to mkdir the last path component.
         */
        if (freq->localfile < 0 && errno == ENOENT) {
-               char *dir = strrchr(freq->tmpfile, '/');
+               char *dir = strrchr(freq->tmpfile.buf, '/');
                if (dir) {
                        *dir = 0;
-                       mkdir(freq->tmpfile, 0777);
+                       mkdir(freq->tmpfile.buf, 0777);
                        *dir = '/';
                }
-               freq->localfile = open(freq->tmpfile,
+               freq->localfile = open(freq->tmpfile.buf,
                                       O_WRONLY | O_CREAT | O_EXCL, 0666);
        }
 
        if (freq->localfile < 0) {
-               error_errno("Couldn't create temporary file %s", freq->tmpfile);
+               error_errno("Couldn't create temporary file %s",
+                           freq->tmpfile.buf);
                goto abort;
        }
 
@@ -2287,7 +2291,7 @@ struct http_object_request *new_http_object_request(const char *base_url,
         * If a previous temp file is present, process what was already
         * fetched.
         */
-       prevlocal = open(prevfile, O_RDONLY);
+       prevlocal = open(prevfile.buf, O_RDONLY);
        if (prevlocal != -1) {
                do {
                        prev_read = xread(prevlocal, prev_buf, PREV_BUF_SIZE);
@@ -2304,7 +2308,8 @@ struct http_object_request *new_http_object_request(const char *base_url,
                } while (prev_read > 0);
                close(prevlocal);
        }
-       unlink_or_warn(prevfile);
+       unlink_or_warn(prevfile.buf);
+       strbuf_release(&prevfile);
 
        /*
         * Reset inflate/SHA1 if there was an error reading the previous temp
@@ -2319,7 +2324,7 @@ struct http_object_request *new_http_object_request(const char *base_url,
                        lseek(freq->localfile, 0, SEEK_SET);
                        if (ftruncate(freq->localfile, 0) < 0) {
                                error_errno("Couldn't truncate temporary file %s",
-                                           freq->tmpfile);
+                                           freq->tmpfile.buf);
                                goto abort;
                        }
                }
@@ -2349,6 +2354,7 @@ struct http_object_request *new_http_object_request(const char *base_url,
        return freq;
 
 abort:
+       strbuf_release(&prevfile);
        free(freq->url);
        free(freq);
        return NULL;
@@ -2376,24 +2382,24 @@ int finish_http_object_request(struct http_object_request *freq)
        if (freq->http_code == 416) {
                warning("requested range invalid; we may already have all the data.");
        } else if (freq->curl_result != CURLE_OK) {
-               if (stat(freq->tmpfile, &st) == 0)
+               if (stat(freq->tmpfile.buf, &st) == 0)
                        if (st.st_size == 0)
-                               unlink_or_warn(freq->tmpfile);
+                               unlink_or_warn(freq->tmpfile.buf);
                return -1;
        }
 
        git_inflate_end(&freq->stream);
        git_SHA1_Final(freq->real_sha1, &freq->c);
        if (freq->zret != Z_STREAM_END) {
-               unlink_or_warn(freq->tmpfile);
+               unlink_or_warn(freq->tmpfile.buf);
                return -1;
        }
        if (hashcmp(freq->sha1, freq->real_sha1)) {
-               unlink_or_warn(freq->tmpfile);
+               unlink_or_warn(freq->tmpfile.buf);
                return -1;
        }
        sha1_file_name(the_repository, &filename, freq->sha1);
-       freq->rename = finalize_object_file(freq->tmpfile, filename.buf);
+       freq->rename = finalize_object_file(freq->tmpfile.buf, filename.buf);
        strbuf_release(&filename);
 
        return freq->rename;
@@ -2401,7 +2407,7 @@ int finish_http_object_request(struct http_object_request *freq)
 
 void abort_http_object_request(struct http_object_request *freq)
 {
-       unlink_or_warn(freq->tmpfile);
+       unlink_or_warn(freq->tmpfile.buf);
 
        release_http_object_request(freq);
 }
@@ -2421,4 +2427,5 @@ void release_http_object_request(struct http_object_request *freq)
                release_active_slot(freq->slot);
                freq->slot = NULL;
        }
+       strbuf_release(&freq->tmpfile);
 }
diff --git a/http.h b/http.h
index 4df4a25e1abc232c27f1b00ba0a97b05a689db2e..d305ca1dc7a3f931a81353c56060e98f4039c692 100644 (file)
--- a/http.h
+++ b/http.h
@@ -207,7 +207,7 @@ struct http_pack_request {
        struct packed_git *target;
        struct packed_git **lst;
        FILE *packfile;
-       char tmpfile[PATH_MAX];
+       struct strbuf tmpfile;
        struct active_request_slot *slot;
 };
 
@@ -219,7 +219,7 @@ extern void release_http_pack_request(struct http_pack_request *preq);
 /* Helpers for fetching object */
 struct http_object_request {
        char *url;
-       char tmpfile[PATH_MAX];
+       struct strbuf tmpfile;
        int localfile;
        CURLcode curl_result;
        char errorstr[CURL_ERROR_SIZE];
index 3573cbfb0fca7d524f3d3531f9f97ebeabdd15e6..b4eb886e2a6a40bce1e478d8d24f43f64ed2e3aa 100644 (file)
@@ -511,7 +511,7 @@ static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
 
        va_start(va, fmt);
        if (blen <= 0 || (unsigned)(ret = vsnprintf(buf, blen, fmt, va)) >= (unsigned)blen)
-               die("BUG: buffer too small. Please report a bug.");
+               BUG("buffer too small. Please report a bug.");
        va_end(va);
        return ret;
 }
index ecdce08c4be24cc14109796d9a7c165c53753a88..fa9cfd5bdbb5dfe4a1ca5c7f7711435caf091805 100644 (file)
@@ -816,8 +816,8 @@ static void queue_diffs(struct line_log_data *range,
        assert(commit);
 
        DIFF_QUEUE_CLEAR(&diff_queued_diff);
-       diff_tree_oid(parent ? &parent->tree->object.oid : NULL,
-                     &commit->tree->object.oid, "", opt);
+       diff_tree_oid(parent ? get_commit_tree_oid(parent) : NULL,
+                     get_commit_tree_oid(commit), "", opt);
        if (opt->detect_rename) {
                filter_diffs_for_paths(range, 1);
                if (diff_might_be_rename())
index 247717561f8fb6e2320b0128cad4325a4fca7da5..5b14d2711ad3791169f5d8cbc03836b4254bd55c 100644 (file)
@@ -117,7 +117,7 @@ static enum list_objects_filter_result filter_blobs_limit(
                assert(obj->type == OBJ_BLOB);
                assert((obj->flags & SEEN) == 0);
 
-               t = oid_object_info(&obj->oid, &object_length);
+               t = oid_object_info(the_repository, &obj->oid, &object_length);
                if (t != OBJ_BLOB) { /* probably OBJ_NONE */
                        /*
                         * We DO NOT have the blob locally, so we cannot
index 168bef688a89489a9d88d3e1f773483dbc1c8860..3eec510357337f5e33eea08a05faa22373d98c07 100644 (file)
@@ -195,7 +195,7 @@ static void mark_edge_parents_uninteresting(struct commit *commit,
                struct commit *parent = parents->item;
                if (!(parent->object.flags & UNINTERESTING))
                        continue;
-               mark_tree_uninteresting(parent->tree);
+               mark_tree_uninteresting(get_commit_tree(parent));
                if (revs->edge_hint && !(parent->object.flags & SHOWN)) {
                        parent->object.flags |= SHOWN;
                        show_edge(parent);
@@ -212,7 +212,7 @@ void mark_edges_uninteresting(struct rev_info *revs, show_edge_fn show_edge)
                struct commit *commit = list->item;
 
                if (commit->object.flags & UNINTERESTING) {
-                       mark_tree_uninteresting(commit->tree);
+                       mark_tree_uninteresting(get_commit_tree(commit));
                        if (revs->edge_hint_aggressive && !(commit->object.flags & SHOWN)) {
                                commit->object.flags |= SHOWN;
                                show_edge(commit);
@@ -227,7 +227,7 @@ void mark_edges_uninteresting(struct rev_info *revs, show_edge_fn show_edge)
                        struct commit *commit = (struct commit *)obj;
                        if (obj->type != OBJ_COMMIT || !(obj->flags & UNINTERESTING))
                                continue;
-                       mark_tree_uninteresting(commit->tree);
+                       mark_tree_uninteresting(get_commit_tree(commit));
                        if (!(obj->flags & SHOWN)) {
                                obj->flags |= SHOWN;
                                show_edge(commit);
@@ -300,8 +300,8 @@ static void do_traverse(struct rev_info *revs,
                 * an uninteresting boundary commit may not have its tree
                 * parsed yet, but we are not going to show them anyway
                 */
-               if (commit->tree)
-                       add_pending_tree(revs, commit->tree);
+               if (get_commit_tree(commit))
+                       add_pending_tree(revs, get_commit_tree(commit));
                show_commit(commit, show_data);
 
                if (revs->tree_blobs_in_commit_order)
index efcb7d7dfe30a2fbad03244ba4ca9e536a3649e4..8e8ab4f29f3eaf05f056211042b5fd2f01727363 100644 (file)
@@ -193,7 +193,7 @@ char *get_locked_file_path(struct lock_file *lk)
        strbuf_addstr(&ret, get_tempfile_path(lk->tempfile));
        if (ret.len <= LOCK_SUFFIX_LEN ||
            strcmp(ret.buf + ret.len - LOCK_SUFFIX_LEN, LOCK_SUFFIX))
-               die("BUG: get_locked_file_path() called for malformed lock object");
+               BUG("get_locked_file_path() called for malformed lock object");
        /* remove ".lock": */
        strbuf_setlen(&ret, ret.len - LOCK_SUFFIX_LEN);
        return strbuf_detach(&ret, NULL);
index d1c0bedf244fce0c8894175bf800384c0474b312..4aef85331e0b696d0373cf1bdb0743a7e6b58c36 100644 (file)
@@ -362,7 +362,8 @@ void fmt_output_email_subject(struct strbuf *sb, struct rev_info *opt)
 
 void log_write_email_headers(struct rev_info *opt, struct commit *commit,
                             const char **extra_headers_p,
-                            int *need_8bit_cte_p)
+                            int *need_8bit_cte_p,
+                            int maybe_multipart)
 {
        const char *extra_headers = opt->extra_headers;
        const char *name = oid_to_hex(opt->zero_commit ?
@@ -385,12 +386,16 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit,
                               opt->ref_message_ids->items[i].string);
                graph_show_oneline(opt->graph);
        }
-       if (opt->mime_boundary) {
-               static char subject_buffer[1024];
-               static char buffer[1024];
+       if (opt->mime_boundary && maybe_multipart) {
+               static struct strbuf subject_buffer = STRBUF_INIT;
+               static struct strbuf buffer = STRBUF_INIT;
                struct strbuf filename =  STRBUF_INIT;
                *need_8bit_cte_p = -1; /* NEVER */
-               snprintf(subject_buffer, sizeof(subject_buffer) - 1,
+
+               strbuf_reset(&subject_buffer);
+               strbuf_reset(&buffer);
+
+               strbuf_addf(&subject_buffer,
                         "%s"
                         "MIME-Version: 1.0\n"
                         "Content-Type: multipart/mixed;"
@@ -405,13 +410,13 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit,
                         extra_headers ? extra_headers : "",
                         mime_boundary_leader, opt->mime_boundary,
                         mime_boundary_leader, opt->mime_boundary);
-               extra_headers = subject_buffer;
+               extra_headers = subject_buffer.buf;
 
                if (opt->numbered_files)
                        strbuf_addf(&filename, "%d", opt->nr);
                else
                        fmt_output_commit(&filename, commit, opt);
-               snprintf(buffer, sizeof(buffer) - 1,
+               strbuf_addf(&buffer,
                         "\n--%s%s\n"
                         "Content-Type: text/x-patch;"
                         " name=\"%s\"\n"
@@ -422,7 +427,7 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit,
                         filename.buf,
                         opt->no_inline ? "attachment" : "inline",
                         filename.buf);
-               opt->diffopt.stat_sep = buffer;
+               opt->diffopt.stat_sep = buffer.buf;
                strbuf_release(&filename);
        }
        *extra_headers_p = extra_headers;
@@ -488,9 +493,9 @@ static int is_common_merge(const struct commit *commit)
                && !commit->parents->next->next);
 }
 
-static void show_one_mergetag(struct commit *commit,
-                             struct commit_extra_header *extra,
-                             void *data)
+static int show_one_mergetag(struct commit *commit,
+                            struct commit_extra_header *extra,
+                            void *data)
 {
        struct rev_info *opt = (struct rev_info *)data;
        struct object_id oid;
@@ -502,7 +507,7 @@ static void show_one_mergetag(struct commit *commit,
        hash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &oid);
        tag = lookup_tag(&oid);
        if (!tag)
-               return; /* error message already given */
+               return -1; /* error message already given */
 
        strbuf_init(&verify_message, 256);
        if (parse_tag_buffer(tag, extra->value, extra->len))
@@ -536,11 +541,12 @@ static void show_one_mergetag(struct commit *commit,
 
        show_sig_lines(opt, status, verify_message.buf);
        strbuf_release(&verify_message);
+       return 0;
 }
 
-static void show_mergetag(struct rev_info *opt, struct commit *commit)
+static int show_mergetag(struct rev_info *opt, struct commit *commit)
 {
-       for_each_mergetag(show_one_mergetag, commit, opt);
+       return for_each_mergetag(show_one_mergetag, commit, opt);
 }
 
 void show_log(struct rev_info *opt)
@@ -610,7 +616,7 @@ void show_log(struct rev_info *opt)
 
        if (cmit_fmt_is_mail(opt->commit_format)) {
                log_write_email_headers(opt, commit, &extra_headers,
-                                       &ctx.need_8bit_cte);
+                                       &ctx.need_8bit_cte, 1);
                ctx.rev = opt;
                ctx.print_email_subject = 1;
        } else if (opt->commit_format != CMIT_FMT_USERFORMAT) {
@@ -806,7 +812,7 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
                return 0;
 
        parse_commit_or_die(commit);
-       oid = &commit->tree->object.oid;
+       oid = get_commit_tree_oid(commit);
 
        /* Root commit? */
        parents = get_saved_parents(opt, commit);
@@ -831,7 +837,7 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
                         * we merged _in_.
                         */
                        parse_commit_or_die(parents->item);
-                       diff_tree_oid(&parents->item->tree->object.oid,
+                       diff_tree_oid(get_commit_tree_oid(parents->item),
                                      oid, "", &opt->diffopt);
                        log_tree_diff_flush(opt);
                        return !opt->loginfo;
@@ -846,7 +852,7 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
                struct commit *parent = parents->item;
 
                parse_commit_or_die(parent);
-               diff_tree_oid(&parent->tree->object.oid,
+               diff_tree_oid(get_commit_tree_oid(parent),
                              oid, "", &opt->diffopt);
                log_tree_diff_flush(opt);
 
index deba035187185bc28a83520b47480c3141d8398e..e66862807463a12e28573be04cbcfcf2d200e6f3 100644 (file)
@@ -27,7 +27,8 @@ void format_decorations_extended(struct strbuf *sb, const struct commit *commit,
 void show_decorations(struct rev_info *opt, struct commit *commit);
 void log_write_email_headers(struct rev_info *opt, struct commit *commit,
                             const char **extra_headers_p,
-                            int *need_8bit_cte_p);
+                            int *need_8bit_cte_p,
+                            int maybe_multipart);
 void load_ref_decorations(struct decoration_filter *filter, int flags);
 
 #define FORMAT_PATCH_NAME_MAX 64
index d04142ccc76067e83f1839b6315b292268e90c58..3281a37d51830a60e864c78dff16dbf4eba9d800 100644 (file)
@@ -716,7 +716,7 @@ static void flush_inbody_header_accum(struct mailinfo *mi)
        if (!mi->inbody_header_accum.len)
                return;
        if (!check_header(mi, &mi->inbody_header_accum, mi->s_hdr_data, 0))
-               die("BUG: inbody_header_accum, if not empty, must always contain a valid in-body header");
+               BUG("inbody_header_accum, if not empty, must always contain a valid in-body header");
        strbuf_reset(&mi->inbody_header_accum);
 }
 
index 0c0d48624da1d6162f0804e4a105841d965bb2c2..ac27abbd4cd708fdc2526c6508680b7d246773b6 100644 (file)
@@ -23,6 +23,7 @@
 #include "merge-recursive.h"
 #include "dir.h"
 #include "submodule.h"
+#include "revision.h"
 
 struct path_hashmap_entry {
        struct hashmap_entry e;
@@ -49,6 +50,67 @@ static unsigned int path_hash(const char *path)
        return ignore_case ? strihash(path) : strhash(path);
 }
 
+static struct dir_rename_entry *dir_rename_find_entry(struct hashmap *hashmap,
+                                                     char *dir)
+{
+       struct dir_rename_entry key;
+
+       if (dir == NULL)
+               return NULL;
+       hashmap_entry_init(&key, strhash(dir));
+       key.dir = dir;
+       return hashmap_get(hashmap, &key, NULL);
+}
+
+static int dir_rename_cmp(const void *unused_cmp_data,
+                         const void *entry,
+                         const void *entry_or_key,
+                         const void *unused_keydata)
+{
+       const struct dir_rename_entry *e1 = entry;
+       const struct dir_rename_entry *e2 = entry_or_key;
+
+       return strcmp(e1->dir, e2->dir);
+}
+
+static void dir_rename_init(struct hashmap *map)
+{
+       hashmap_init(map, dir_rename_cmp, NULL, 0);
+}
+
+static void dir_rename_entry_init(struct dir_rename_entry *entry,
+                                 char *directory)
+{
+       hashmap_entry_init(entry, strhash(directory));
+       entry->dir = directory;
+       entry->non_unique_new_dir = 0;
+       strbuf_init(&entry->new_dir, 0);
+       string_list_init(&entry->possible_new_dirs, 0);
+}
+
+static struct collision_entry *collision_find_entry(struct hashmap *hashmap,
+                                                   char *target_file)
+{
+       struct collision_entry key;
+
+       hashmap_entry_init(&key, strhash(target_file));
+       key.target_file = target_file;
+       return hashmap_get(hashmap, &key, NULL);
+}
+
+static int collision_cmp(void *unused_cmp_data,
+                        const struct collision_entry *e1,
+                        const struct collision_entry *e2,
+                        const void *unused_keydata)
+{
+       return strcmp(e1->target_file, e2->target_file);
+}
+
+static void collision_init(struct hashmap *map)
+{
+       hashmap_init(map, (hashmap_cmp_fn) collision_cmp, NULL, 0);
+}
+
 static void flush_output(struct merge_options *o)
 {
        if (o->buffer_output < 2 && o->obuf.len) {
@@ -101,7 +163,7 @@ static struct commit *make_virtual_commit(struct tree *tree, const char *comment
        struct commit *commit = alloc_commit_node();
 
        set_merge_remote_desc(commit, comment, (struct object *)commit);
-       commit->tree = tree;
+       commit->maybe_tree = tree;
        commit->object.parsed = 1;
        return commit;
 }
@@ -119,6 +181,7 @@ static int oid_eq(const struct object_id *a, const struct object_id *b)
 
 enum rename_type {
        RENAME_NORMAL = 0,
+       RENAME_DIR,
        RENAME_DELETE,
        RENAME_ONE_FILE_TO_ONE,
        RENAME_ONE_FILE_TO_TWO,
@@ -254,7 +317,7 @@ static int add_cacheinfo(struct merge_options *o,
 
        ce = make_cache_entry(mode, oid ? oid->hash : null_sha1, path, stage, 0);
        if (!ce)
-               return err(o, _("addinfo_cache failed for path '%s'"), path);
+               return err(o, _("add_cacheinfo failed for path '%s'; merge aborting."), path);
 
        ret = add_cache_entry(ce, options);
        if (refresh) {
@@ -262,7 +325,7 @@ static int add_cacheinfo(struct merge_options *o,
 
                nce = refresh_cache_entry(ce, CE_MATCH_REFRESH | CE_MATCH_IGNORE_MISSING);
                if (!nce)
-                       return err(o, _("addinfo_cache failed for path '%s'"), path);
+                       return err(o, _("add_cacheinfo failed to refresh for path '%s'; merge aborting."), path);
                if (nce != ce)
                        ret = add_cache_entry(nce, options);
        }
@@ -275,36 +338,54 @@ static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)
        init_tree_desc(desc, tree->buffer, tree->size);
 }
 
-static int git_merge_trees(int index_only,
-                          struct tree *common,
-                          struct tree *head,
-                          struct tree *merge)
+static int unpack_trees_start(struct merge_options *o,
+                             struct tree *common,
+                             struct tree *head,
+                             struct tree *merge)
 {
        int rc;
        struct tree_desc t[3];
-       struct unpack_trees_options opts;
+       struct index_state tmp_index = { NULL };
 
-       memset(&opts, 0, sizeof(opts));
-       if (index_only)
-               opts.index_only = 1;
+       memset(&o->unpack_opts, 0, sizeof(o->unpack_opts));
+       if (o->call_depth)
+               o->unpack_opts.index_only = 1;
        else
-               opts.update = 1;
-       opts.merge = 1;
-       opts.head_idx = 2;
-       opts.fn = threeway_merge;
-       opts.src_index = &the_index;
-       opts.dst_index = &the_index;
-       setup_unpack_trees_porcelain(&opts, "merge");
+               o->unpack_opts.update = 1;
+       o->unpack_opts.merge = 1;
+       o->unpack_opts.head_idx = 2;
+       o->unpack_opts.fn = threeway_merge;
+       o->unpack_opts.src_index = &the_index;
+       o->unpack_opts.dst_index = &tmp_index;
+       o->unpack_opts.aggressive = !merge_detect_rename(o);
+       setup_unpack_trees_porcelain(&o->unpack_opts, "merge");
 
        init_tree_desc_from_tree(t+0, common);
        init_tree_desc_from_tree(t+1, head);
        init_tree_desc_from_tree(t+2, merge);
 
-       rc = unpack_trees(3, t, &opts);
+       rc = unpack_trees(3, t, &o->unpack_opts);
        cache_tree_free(&active_cache_tree);
+
+       /*
+        * Update the_index to match the new results, AFTER saving a copy
+        * in o->orig_index.  Update src_index to point to the saved copy.
+        * (verify_uptodate() checks src_index, and the original index is
+        * the one that had the necessary modification timestamps.)
+        */
+       o->orig_index = the_index;
+       the_index = tmp_index;
+       o->unpack_opts.src_index = &o->orig_index;
+
        return rc;
 }
 
+static void unpack_trees_finish(struct merge_options *o)
+{
+       discard_index(&o->orig_index);
+       clear_unpack_trees_porcelain(&o->unpack_opts);
+}
+
 struct tree *write_tree_from_memory(struct merge_options *o)
 {
        struct tree *result = NULL;
@@ -318,7 +399,7 @@ struct tree *write_tree_from_memory(struct merge_options *o)
                                fprintf(stderr, "BUG: %d %.*s\n", ce_stage(ce),
                                        (int)ce_namelen(ce), ce->name);
                }
-               die("BUG: unmerged index entries in merge-recursive.c");
+               BUG("unmerged index entries in merge-recursive.c");
        }
 
        if (!active_cache_tree)
@@ -360,6 +441,21 @@ static void get_files_dirs(struct merge_options *o, struct tree *tree)
        read_tree_recursive(tree, "", 0, 0, &match_all, save_files_dirs, o);
 }
 
+static int get_tree_entry_if_blob(const struct object_id *tree,
+                                 const char *path,
+                                 struct object_id *hashy,
+                                 unsigned int *mode_o)
+{
+       int ret;
+
+       ret = get_tree_entry(tree, path, hashy, mode_o);
+       if (S_ISDIR(*mode_o)) {
+               oidcpy(hashy, &null_oid);
+               *mode_o = 0;
+       }
+       return ret;
+}
+
 /*
  * Returns an index_entry instance which doesn't have to correspond to
  * a real cache entry in Git's index.
@@ -370,12 +466,12 @@ static struct stage_data *insert_stage_data(const char *path,
 {
        struct string_list_item *item;
        struct stage_data *e = xcalloc(1, sizeof(struct stage_data));
-       get_tree_entry(&o->object.oid, path,
-                       &e->stages[1].oid, &e->stages[1].mode);
-       get_tree_entry(&a->object.oid, path,
-                       &e->stages[2].oid, &e->stages[2].mode);
-       get_tree_entry(&b->object.oid, path,
-                       &e->stages[3].oid, &e->stages[3].mode);
+       get_tree_entry_if_blob(&o->object.oid, path,
+                              &e->stages[1].oid, &e->stages[1].mode);
+       get_tree_entry_if_blob(&a->object.oid, path,
+                              &e->stages[2].oid, &e->stages[2].mode);
+       get_tree_entry_if_blob(&b->object.oid, path,
+                              &e->stages[3].oid, &e->stages[3].mode);
        item = string_list_insert(entries, path);
        item->util = e;
        return e;
@@ -534,78 +630,10 @@ struct rename {
         */
        struct stage_data *src_entry;
        struct stage_data *dst_entry;
+       unsigned add_turned_into_rename:1;
        unsigned processed:1;
 };
 
-/*
- * Get information of all renames which occurred between 'o_tree' and
- * 'tree'. We need the three trees in the merge ('o_tree', 'a_tree' and
- * 'b_tree') to be able to associate the correct cache entries with
- * the rename information. 'tree' is always equal to either a_tree or b_tree.
- */
-static struct string_list *get_renames(struct merge_options *o,
-                                      struct tree *tree,
-                                      struct tree *o_tree,
-                                      struct tree *a_tree,
-                                      struct tree *b_tree,
-                                      struct string_list *entries)
-{
-       int i;
-       struct string_list *renames;
-       struct diff_options opts;
-
-       renames = xcalloc(1, sizeof(struct string_list));
-       if (!o->detect_rename)
-               return renames;
-
-       diff_setup(&opts);
-       opts.flags.recursive = 1;
-       opts.flags.rename_empty = 0;
-       opts.detect_rename = DIFF_DETECT_RENAME;
-       opts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit :
-                           o->diff_rename_limit >= 0 ? o->diff_rename_limit :
-                           1000;
-       opts.rename_score = o->rename_score;
-       opts.show_rename_progress = o->show_rename_progress;
-       opts.output_format = DIFF_FORMAT_NO_OUTPUT;
-       diff_setup_done(&opts);
-       diff_tree_oid(&o_tree->object.oid, &tree->object.oid, "", &opts);
-       diffcore_std(&opts);
-       if (opts.needed_rename_limit > o->needed_rename_limit)
-               o->needed_rename_limit = opts.needed_rename_limit;
-       for (i = 0; i < diff_queued_diff.nr; ++i) {
-               struct string_list_item *item;
-               struct rename *re;
-               struct diff_filepair *pair = diff_queued_diff.queue[i];
-               if (pair->status != 'R') {
-                       diff_free_filepair(pair);
-                       continue;
-               }
-               re = xmalloc(sizeof(*re));
-               re->processed = 0;
-               re->pair = pair;
-               item = string_list_lookup(entries, re->pair->one->path);
-               if (!item)
-                       re->src_entry = insert_stage_data(re->pair->one->path,
-                                       o_tree, a_tree, b_tree, entries);
-               else
-                       re->src_entry = item->util;
-
-               item = string_list_lookup(entries, re->pair->two->path);
-               if (!item)
-                       re->dst_entry = insert_stage_data(re->pair->two->path,
-                                       o_tree, a_tree, b_tree, entries);
-               else
-                       re->dst_entry = item->util;
-               item = string_list_insert(renames, pair->one->path);
-               item->util = re;
-       }
-       opts.output_format = DIFF_FORMAT_NO_OUTPUT;
-       diff_queued_diff.nr = 0;
-       diff_flush(&opts);
-       return renames;
-}
-
 static int update_stages(struct merge_options *opt, const char *path,
                         const struct diff_filespec *o,
                         const struct diff_filespec *a,
@@ -637,6 +665,27 @@ static int update_stages(struct merge_options *opt, const char *path,
        return 0;
 }
 
+static int update_stages_for_stage_data(struct merge_options *opt,
+                                       const char *path,
+                                       const struct stage_data *stage_data)
+{
+       struct diff_filespec o, a, b;
+
+       o.mode = stage_data->stages[1].mode;
+       oidcpy(&o.oid, &stage_data->stages[1].oid);
+
+       a.mode = stage_data->stages[2].mode;
+       oidcpy(&a.oid, &stage_data->stages[2].oid);
+
+       b.mode = stage_data->stages[3].mode;
+       oidcpy(&b.oid, &stage_data->stages[3].oid);
+
+       return update_stages(opt, path,
+                            is_null_oid(&o.oid) ? NULL : &o,
+                            is_null_oid(&a.oid) ? NULL : &a,
+                            is_null_oid(&b.oid) ? NULL : &b);
+}
+
 static void update_entry(struct stage_data *entry,
                         struct diff_filespec *o,
                         struct diff_filespec *a,
@@ -738,31 +787,92 @@ static int dir_in_way(const char *path, int check_working_copy, int empty_ok)
                !(empty_ok && is_empty_dir(path));
 }
 
-static int was_tracked(const char *path)
+/*
+ * Returns whether path was tracked in the index before the merge started,
+ * and its oid and mode match the specified values
+ */
+static int was_tracked_and_matches(struct merge_options *o, const char *path,
+                                  const struct object_id *oid, unsigned mode)
 {
-       int pos = cache_name_pos(path, strlen(path));
+       int pos = index_name_pos(&o->orig_index, path, strlen(path));
+       struct cache_entry *ce;
+
+       if (0 > pos)
+               /* we were not tracking this path before the merge */
+               return 0;
+
+       /* See if the file we were tracking before matches */
+       ce = o->orig_index.cache[pos];
+       return (oid_eq(&ce->oid, oid) && ce->ce_mode == mode);
+}
+
+/*
+ * Returns whether path was tracked in the index before the merge started
+ */
+static int was_tracked(struct merge_options *o, const char *path)
+{
+       int pos = index_name_pos(&o->orig_index, path, strlen(path));
 
        if (0 <= pos)
-               /* we have been tracking this path */
+               /* we were tracking this path before the merge */
                return 1;
 
-       /*
-        * Look for an unmerged entry for the path,
-        * specifically stage #2, which would indicate
-        * that "our" side before the merge started
-        * had the path tracked (and resulted in a conflict).
-        */
-       for (pos = -1 - pos;
-            pos < active_nr && !strcmp(path, active_cache[pos]->name);
-            pos++)
-               if (ce_stage(active_cache[pos]) == 2)
-                       return 1;
        return 0;
 }
 
 static int would_lose_untracked(const char *path)
 {
-       return !was_tracked(path) && file_exists(path);
+       /*
+        * This may look like it can be simplified to:
+        *   return !was_tracked(o, path) && file_exists(path)
+        * but it can't.  This function needs to know whether path was in
+        * the working tree due to EITHER having been tracked in the index
+        * before the merge OR having been put into the working copy and
+        * index by unpack_trees().  Due to that either-or requirement, we
+        * check the current index instead of the original one.
+        *
+        * Note that we do not need to worry about merge-recursive itself
+        * updating the index after unpack_trees() and before calling this
+        * function, because we strictly require all code paths in
+        * merge-recursive to update the working tree first and the index
+        * second.  Doing otherwise would break
+        * update_file()/would_lose_untracked(); see every comment in this
+        * file which mentions "update_stages".
+        */
+       int pos = cache_name_pos(path, strlen(path));
+
+       if (pos < 0)
+               pos = -1 - pos;
+       while (pos < active_nr &&
+              !strcmp(path, active_cache[pos]->name)) {
+               /*
+                * If stage #0, it is definitely tracked.
+                * If it has stage #2 then it was tracked
+                * before this merge started.  All other
+                * cases the path was not tracked.
+                */
+               switch (ce_stage(active_cache[pos])) {
+               case 0:
+               case 2:
+                       return 0;
+               }
+               pos++;
+       }
+       return file_exists(path);
+}
+
+static int was_dirty(struct merge_options *o, const char *path)
+{
+       struct cache_entry *ce;
+       int dirty = 1;
+
+       if (o->call_depth || !was_tracked(o, path))
+               return !dirty;
+
+       ce = index_file_exists(o->unpack_opts.src_index,
+                              path, strlen(path), ignore_case);
+       dirty = verify_uptodate(ce, &o->unpack_opts) != 0;
+       return dirty;
 }
 
 static int make_room_for_path(struct merge_options *o, const char *path)
@@ -893,7 +1003,9 @@ static int update_file_flags(struct merge_options *o,
        }
  update_index:
        if (!ret && update_cache)
-               add_cacheinfo(o, mode, oid, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
+               if (add_cacheinfo(o, mode, oid, path, 0, update_wd,
+                                 ADD_CACHE_OK_TO_ADD))
+                       return -1;
        return ret;
 }
 
@@ -977,13 +1089,193 @@ static int merge_3way(struct merge_options *o,
        return merge_status;
 }
 
+static int find_first_merges(struct object_array *result, const char *path,
+               struct commit *a, struct commit *b)
+{
+       int i, j;
+       struct object_array merges = OBJECT_ARRAY_INIT;
+       struct commit *commit;
+       int contains_another;
+
+       char merged_revision[42];
+       const char *rev_args[] = { "rev-list", "--merges", "--ancestry-path",
+                                  "--all", merged_revision, NULL };
+       struct rev_info revs;
+       struct setup_revision_opt rev_opts;
+
+       memset(result, 0, sizeof(struct object_array));
+       memset(&rev_opts, 0, sizeof(rev_opts));
+
+       /* get all revisions that merge commit a */
+       xsnprintf(merged_revision, sizeof(merged_revision), "^%s",
+                       oid_to_hex(&a->object.oid));
+       init_revisions(&revs, NULL);
+       rev_opts.submodule = path;
+       /* FIXME: can't handle linked worktrees in submodules yet */
+       revs.single_worktree = path != NULL;
+       setup_revisions(ARRAY_SIZE(rev_args)-1, rev_args, &revs, &rev_opts);
+
+       /* save all revisions from the above list that contain b */
+       if (prepare_revision_walk(&revs))
+               die("revision walk setup failed");
+       while ((commit = get_revision(&revs)) != NULL) {
+               struct object *o = &(commit->object);
+               if (in_merge_bases(b, commit))
+                       add_object_array(o, NULL, &merges);
+       }
+       reset_revision_walk();
+
+       /* Now we've got all merges that contain a and b. Prune all
+        * merges that contain another found merge and save them in
+        * result.
+        */
+       for (i = 0; i < merges.nr; i++) {
+               struct commit *m1 = (struct commit *) merges.objects[i].item;
+
+               contains_another = 0;
+               for (j = 0; j < merges.nr; j++) {
+                       struct commit *m2 = (struct commit *) merges.objects[j].item;
+                       if (i != j && in_merge_bases(m2, m1)) {
+                               contains_another = 1;
+                               break;
+                       }
+               }
+
+               if (!contains_another)
+                       add_object_array(merges.objects[i].item, NULL, result);
+       }
+
+       object_array_clear(&merges);
+       return result->nr;
+}
+
+static void print_commit(struct commit *commit)
+{
+       struct strbuf sb = STRBUF_INIT;
+       struct pretty_print_context ctx = {0};
+       ctx.date_mode.type = DATE_NORMAL;
+       format_commit_message(commit, " %h: %m %s", &sb, &ctx);
+       fprintf(stderr, "%s\n", sb.buf);
+       strbuf_release(&sb);
+}
+
+static int merge_submodule(struct merge_options *o,
+                          struct object_id *result, const char *path,
+                          const struct object_id *base, const struct object_id *a,
+                          const struct object_id *b)
+{
+       struct commit *commit_base, *commit_a, *commit_b;
+       int parent_count;
+       struct object_array merges;
+
+       int i;
+       int search = !o->call_depth;
+
+       /* store a in result in case we fail */
+       oidcpy(result, a);
+
+       /* we can not handle deletion conflicts */
+       if (is_null_oid(base))
+               return 0;
+       if (is_null_oid(a))
+               return 0;
+       if (is_null_oid(b))
+               return 0;
+
+       if (add_submodule_odb(path)) {
+               output(o, 1, _("Failed to merge submodule %s (not checked out)"), path);
+               return 0;
+       }
+
+       if (!(commit_base = lookup_commit_reference(base)) ||
+           !(commit_a = lookup_commit_reference(a)) ||
+           !(commit_b = lookup_commit_reference(b))) {
+               output(o, 1, _("Failed to merge submodule %s (commits not present)"), path);
+               return 0;
+       }
+
+       /* check whether both changes are forward */
+       if (!in_merge_bases(commit_base, commit_a) ||
+           !in_merge_bases(commit_base, commit_b)) {
+               output(o, 1, _("Failed to merge submodule %s (commits don't follow merge-base)"), path);
+               return 0;
+       }
+
+       /* Case #1: a is contained in b or vice versa */
+       if (in_merge_bases(commit_a, commit_b)) {
+               oidcpy(result, b);
+               if (show(o, 3)) {
+                       output(o, 3, _("Fast-forwarding submodule %s to the following commit:"), path);
+                       output_commit_title(o, commit_b);
+               } else if (show(o, 2))
+                       output(o, 2, _("Fast-forwarding submodule %s to %s"), path, oid_to_hex(b));
+               else
+                       ; /* no output */
+
+               return 1;
+       }
+       if (in_merge_bases(commit_b, commit_a)) {
+               oidcpy(result, a);
+               if (show(o, 3)) {
+                       output(o, 3, _("Fast-forwarding submodule %s to the following commit:"), path);
+                       output_commit_title(o, commit_a);
+               } else if (show(o, 2))
+                       output(o, 2, _("Fast-forwarding submodule %s to %s"), path, oid_to_hex(a));
+               else
+                       ; /* no output */
+
+               return 1;
+       }
+
+       /*
+        * Case #2: There are one or more merges that contain a and b in
+        * the submodule. If there is only one, then present it as a
+        * suggestion to the user, but leave it marked unmerged so the
+        * user needs to confirm the resolution.
+        */
+
+       /* Skip the search if makes no sense to the calling context.  */
+       if (!search)
+               return 0;
+
+       /* find commit which merges them */
+       parent_count = find_first_merges(&merges, path, commit_a, commit_b);
+       switch (parent_count) {
+       case 0:
+               output(o, 1, _("Failed to merge submodule %s (merge following commits not found)"), path);
+               break;
+
+       case 1:
+               output(o, 1, _("Failed to merge submodule %s (not fast-forward)"), path);
+               output(o, 2, _("Found a possible merge resolution for the submodule:\n"));
+               print_commit((struct commit *) merges.objects[0].item);
+               output(o, 2, _(
+                       "If this is correct simply add it to the index "
+                       "for example\n"
+                       "by using:\n\n"
+                       "  git update-index --cacheinfo 160000 %s \"%s\"\n\n"
+                       "which will accept this suggestion.\n"),
+                       oid_to_hex(&merges.objects[0].item->oid), path);
+               break;
+
+       default:
+               output(o, 1, _("Failed to merge submodule %s (multiple merges found)"), path);
+               for (i = 0; i < merges.nr; i++)
+                       print_commit((struct commit *) merges.objects[i].item);
+       }
+
+       object_array_clear(&merges);
+       return 0;
+}
+
 static int merge_file_1(struct merge_options *o,
-                                          const struct diff_filespec *one,
-                                          const struct diff_filespec *a,
-                                          const struct diff_filespec *b,
-                                          const char *branch1,
-                                          const char *branch2,
-                                          struct merge_file_info *result)
+                       const struct diff_filespec *one,
+                       const struct diff_filespec *a,
+                       const struct diff_filespec *b,
+                       const char *filename,
+                       const char *branch1,
+                       const char *branch2,
+                       struct merge_file_info *result)
 {
        result->merge = 0;
        result->clean = 1;
@@ -1039,12 +1331,11 @@ static int merge_file_1(struct merge_options *o,
                                return ret;
                        result->clean = (merge_status == 0);
                } else if (S_ISGITLINK(a->mode)) {
-                       result->clean = merge_submodule(&result->oid,
+                       result->clean = merge_submodule(o, &result->oid,
                                                       one->path,
                                                       &one->oid,
                                                       &a->oid,
-                                                      &b->oid,
-                                                      !o->call_depth);
+                                                      &b->oid);
                } else if (S_ISLNK(a->mode)) {
                        switch (o->recursive_variant) {
                        case MERGE_RECURSIVE_NORMAL:
@@ -1060,21 +1351,25 @@ static int merge_file_1(struct merge_options *o,
                                break;
                        }
                } else
-                       die("BUG: unsupported object type in the tree");
+                       BUG("unsupported object type in the tree");
        }
 
+       if (result->merge)
+               output(o, 2, _("Auto-merging %s"), filename);
+
        return 0;
 }
 
 static int merge_file_special_markers(struct merge_options *o,
-                          const struct diff_filespec *one,
-                          const struct diff_filespec *a,
-                          const struct diff_filespec *b,
-                          const char *branch1,
-                          const char *filename1,
-                          const char *branch2,
-                          const char *filename2,
-                          struct merge_file_info *mfi)
+                                     const struct diff_filespec *one,
+                                     const struct diff_filespec *a,
+                                     const struct diff_filespec *b,
+                                     const char *target_filename,
+                                     const char *branch1,
+                                     const char *filename1,
+                                     const char *branch2,
+                                     const char *filename2,
+                                     struct merge_file_info *mfi)
 {
        char *side1 = NULL;
        char *side2 = NULL;
@@ -1085,22 +1380,23 @@ static int merge_file_special_markers(struct merge_options *o,
        if (filename2)
                side2 = xstrfmt("%s:%s", branch2, filename2);
 
-       ret = merge_file_1(o, one, a, b,
+       ret = merge_file_1(o, one, a, b, target_filename,
                           side1 ? side1 : branch1,
                           side2 ? side2 : branch2, mfi);
+
        free(side1);
        free(side2);
        return ret;
 }
 
 static int merge_file_one(struct merge_options *o,
-                                        const char *path,
-                                        const struct object_id *o_oid, int o_mode,
-                                        const struct object_id *a_oid, int a_mode,
-                                        const struct object_id *b_oid, int b_mode,
-                                        const char *branch1,
-                                        const char *branch2,
-                                        struct merge_file_info *mfi)
+                         const char *path,
+                         const struct object_id *o_oid, int o_mode,
+                         const struct object_id *a_oid, int a_mode,
+                         const struct object_id *b_oid, int b_mode,
+                         const char *branch1,
+                         const char *branch2,
+                         struct merge_file_info *mfi)
 {
        struct diff_filespec one, a, b;
 
@@ -1111,7 +1407,39 @@ static int merge_file_one(struct merge_options *o,
        a.mode = a_mode;
        oidcpy(&b.oid, b_oid);
        b.mode = b_mode;
-       return merge_file_1(o, &one, &a, &b, branch1, branch2, mfi);
+       return merge_file_1(o, &one, &a, &b, path, branch1, branch2, mfi);
+}
+
+static int conflict_rename_dir(struct merge_options *o,
+                              struct diff_filepair *pair,
+                              const char *rename_branch,
+                              const char *other_branch)
+{
+       const struct diff_filespec *dest = pair->two;
+
+       if (!o->call_depth && would_lose_untracked(dest->path)) {
+               char *alt_path = unique_path(o, dest->path, rename_branch);
+
+               output(o, 1, _("Error: Refusing to lose untracked file at %s; "
+                              "writing to %s instead."),
+                      dest->path, alt_path);
+               /*
+                * Write the file in worktree at alt_path, but not in the
+                * index.  Instead, write to dest->path for the index but
+                * only at the higher appropriate stage.
+                */
+               if (update_file(o, 0, &dest->oid, dest->mode, alt_path))
+                       return -1;
+               free(alt_path);
+               return update_stages(o, dest->path, NULL,
+                                    rename_branch == o->branch1 ? dest : NULL,
+                                    rename_branch == o->branch1 ? NULL : dest);
+       }
+
+       /* Update dest->path both in index and in worktree */
+       if (update_file(o, 1, &dest->oid, dest->mode, dest->path))
+               return -1;
+       return 0;
 }
 
 static int handle_change_delete(struct merge_options *o,
@@ -1127,7 +1455,8 @@ static int handle_change_delete(struct merge_options *o,
        const char *update_path = path;
        int ret = 0;
 
-       if (dir_in_way(path, !o->call_depth, 0)) {
+       if (dir_in_way(path, !o->call_depth, 0) ||
+           (!o->call_depth && would_lose_untracked(path))) {
                update_path = alt_path = unique_path(o, path, change_branch);
        }
 
@@ -1242,17 +1571,34 @@ static int handle_file(struct merge_options *o,
 
        add = filespec_from_entry(&other, dst_entry, stage ^ 1);
        if (add) {
+               int ren_src_was_dirty = was_dirty(o, rename->path);
                char *add_name = unique_path(o, rename->path, other_branch);
                if (update_file(o, 0, &add->oid, add->mode, add_name))
                        return -1;
 
-               remove_file(o, 0, rename->path, 0);
+               if (ren_src_was_dirty) {
+                       output(o, 1, _("Refusing to lose dirty file at %s"),
+                              rename->path);
+               }
+               /*
+                * Because the double negatives somehow keep confusing me...
+                *    1) update_wd iff !ren_src_was_dirty.
+                *    2) no_wd iff !update_wd
+                *    3) so, no_wd == !!ren_src_was_dirty == ren_src_was_dirty
+                */
+               remove_file(o, 0, rename->path, ren_src_was_dirty);
                dst_name = unique_path(o, rename->path, cur_branch);
        } else {
                if (dir_in_way(rename->path, !o->call_depth, 0)) {
                        dst_name = unique_path(o, rename->path, cur_branch);
                        output(o, 1, _("%s is a directory in %s adding as %s instead"),
                               rename->path, other_branch, dst_name);
+               } else if (!o->call_depth &&
+                          would_lose_untracked(rename->path)) {
+                       dst_name = unique_path(o, rename->path, cur_branch);
+                       output(o, 1, _("Refusing to lose untracked file at %s; "
+                                      "adding as %s instead"),
+                              rename->path, dst_name);
                }
        }
        if ((ret = update_file(o, 0, &rename->oid, rename->mode, dst_name)))
@@ -1339,6 +1685,8 @@ static int conflict_rename_rename_2to1(struct merge_options *o,
        struct diff_filespec *c1 = ci->pair1->two;
        struct diff_filespec *c2 = ci->pair2->two;
        char *path = c1->path; /* == c2->path */
+       char *path_side_1_desc;
+       char *path_side_2_desc;
        struct merge_file_info mfi_c1;
        struct merge_file_info mfi_c2;
        int ret;
@@ -1352,13 +1700,19 @@ static int conflict_rename_rename_2to1(struct merge_options *o,
        remove_file(o, 1, a->path, o->call_depth || would_lose_untracked(a->path));
        remove_file(o, 1, b->path, o->call_depth || would_lose_untracked(b->path));
 
+       path_side_1_desc = xstrfmt("%s (was %s)", path, a->path);
+       path_side_2_desc = xstrfmt("%s (was %s)", path, b->path);
        if (merge_file_special_markers(o, a, c1, &ci->ren1_other,
+                                      path_side_1_desc,
                                       o->branch1, c1->path,
                                       o->branch2, ci->ren1_other.path, &mfi_c1) ||
            merge_file_special_markers(o, b, &ci->ren2_other, c2,
+                                      path_side_2_desc,
                                       o->branch1, ci->ren2_other.path,
                                       o->branch2, c2->path, &mfi_c2))
                return -1;
+       free(path_side_1_desc);
+       free(path_side_2_desc);
 
        if (o->call_depth) {
                /*
@@ -1378,11 +1732,43 @@ static int conflict_rename_rename_2to1(struct merge_options *o,
                char *new_path2 = unique_path(o, path, ci->branch2);
                output(o, 1, _("Renaming %s to %s and %s to %s instead"),
                       a->path, new_path1, b->path, new_path2);
-               remove_file(o, 0, path, 0);
+               if (was_dirty(o, path))
+                       output(o, 1, _("Refusing to lose dirty file at %s"),
+                              path);
+               else if (would_lose_untracked(path))
+                       /*
+                        * Only way we get here is if both renames were from
+                        * a directory rename AND user had an untracked file
+                        * at the location where both files end up after the
+                        * two directory renames.  See testcase 10d of t6043.
+                        */
+                       output(o, 1, _("Refusing to lose untracked file at "
+                                      "%s, even though it's in the way."),
+                              path);
+               else
+                       remove_file(o, 0, path, 0);
                ret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, new_path1);
                if (!ret)
                        ret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,
                                          new_path2);
+               /*
+                * unpack_trees() actually populates the index for us for
+                * "normal" rename/rename(2to1) situtations so that the
+                * correct entries are at the higher stages, which would
+                * make the call below to update_stages_for_stage_data
+                * unnecessary.  However, if either of the renames came
+                * from a directory rename, then unpack_trees() will not
+                * have gotten the right data loaded into the index, so we
+                * need to do so now.  (While it'd be tempting to move this
+                * call to update_stages_for_stage_data() to
+                * apply_directory_rename_modifications(), that would break
+                * our intermediate calls to would_lose_untracked() since
+                * those rely on the current in-memory index.  See also the
+                * big "NOTE" in update_stages()).
+                */
+               if (update_stages_for_stage_data(o, path, ci->dst_entry1))
+                       ret = -1;
+
                free(new_path2);
                free(new_path1);
        }
@@ -1390,25 +1776,781 @@ static int conflict_rename_rename_2to1(struct merge_options *o,
        return ret;
 }
 
-static int process_renames(struct merge_options *o,
-                          struct string_list *a_renames,
-                          struct string_list *b_renames)
+/*
+ * Get the diff_filepairs changed between o_tree and tree.
+ */
+static struct diff_queue_struct *get_diffpairs(struct merge_options *o,
+                                              struct tree *o_tree,
+                                              struct tree *tree)
 {
-       int clean_merge = 1, i, j;
-       struct string_list a_by_dst = STRING_LIST_INIT_NODUP;
-       struct string_list b_by_dst = STRING_LIST_INIT_NODUP;
-       const struct rename *sre;
+       struct diff_queue_struct *ret;
+       struct diff_options opts;
 
-       for (i = 0; i < a_renames->nr; i++) {
-               sre = a_renames->items[i].util;
-               string_list_insert(&a_by_dst, sre->pair->two->path)->util
-                       = (void *)sre;
-       }
-       for (i = 0; i < b_renames->nr; i++) {
-               sre = b_renames->items[i].util;
-               string_list_insert(&b_by_dst, sre->pair->two->path)->util
-                       = (void *)sre;
-       }
+       diff_setup(&opts);
+       opts.flags.recursive = 1;
+       opts.flags.rename_empty = 0;
+       opts.detect_rename = merge_detect_rename(o);
+       /*
+        * We do not have logic to handle the detection of copies.  In
+        * fact, it may not even make sense to add such logic: would we
+        * really want a change to a base file to be propagated through
+        * multiple other files by a merge?
+        */
+       if (opts.detect_rename > DIFF_DETECT_RENAME)
+               opts.detect_rename = DIFF_DETECT_RENAME;
+       opts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit :
+                           o->diff_rename_limit >= 0 ? o->diff_rename_limit :
+                           1000;
+       opts.rename_score = o->rename_score;
+       opts.show_rename_progress = o->show_rename_progress;
+       opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+       diff_setup_done(&opts);
+       diff_tree_oid(&o_tree->object.oid, &tree->object.oid, "", &opts);
+       diffcore_std(&opts);
+       if (opts.needed_rename_limit > o->needed_rename_limit)
+               o->needed_rename_limit = opts.needed_rename_limit;
+
+       ret = xmalloc(sizeof(*ret));
+       *ret = diff_queued_diff;
+
+       opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+       diff_queued_diff.nr = 0;
+       diff_queued_diff.queue = NULL;
+       diff_flush(&opts);
+       return ret;
+}
+
+static int tree_has_path(struct tree *tree, const char *path)
+{
+       struct object_id hashy;
+       unsigned int mode_o;
+
+       return !get_tree_entry(&tree->object.oid, path,
+                              &hashy, &mode_o);
+}
+
+/*
+ * Return a new string that replaces the beginning portion (which matches
+ * entry->dir), with entry->new_dir.  In perl-speak:
+ *   new_path_name = (old_path =~ s/entry->dir/entry->new_dir/);
+ * NOTE:
+ *   Caller must ensure that old_path starts with entry->dir + '/'.
+ */
+static char *apply_dir_rename(struct dir_rename_entry *entry,
+                             const char *old_path)
+{
+       struct strbuf new_path = STRBUF_INIT;
+       int oldlen, newlen;
+
+       if (entry->non_unique_new_dir)
+               return NULL;
+
+       oldlen = strlen(entry->dir);
+       newlen = entry->new_dir.len + (strlen(old_path) - oldlen) + 1;
+       strbuf_grow(&new_path, newlen);
+       strbuf_addbuf(&new_path, &entry->new_dir);
+       strbuf_addstr(&new_path, &old_path[oldlen]);
+
+       return strbuf_detach(&new_path, NULL);
+}
+
+static void get_renamed_dir_portion(const char *old_path, const char *new_path,
+                                   char **old_dir, char **new_dir)
+{
+       char *end_of_old, *end_of_new;
+       int old_len, new_len;
+
+       *old_dir = NULL;
+       *new_dir = NULL;
+
+       /*
+        * For
+        *    "a/b/c/d/e/foo.c" -> "a/b/some/thing/else/e/foo.c"
+        * the "e/foo.c" part is the same, we just want to know that
+        *    "a/b/c/d" was renamed to "a/b/some/thing/else"
+        * so, for this example, this function returns "a/b/c/d" in
+        * *old_dir and "a/b/some/thing/else" in *new_dir.
+        *
+        * Also, if the basename of the file changed, we don't care.  We
+        * want to know which portion of the directory, if any, changed.
+        */
+       end_of_old = strrchr(old_path, '/');
+       end_of_new = strrchr(new_path, '/');
+
+       if (end_of_old == NULL || end_of_new == NULL)
+               return;
+       while (*--end_of_new == *--end_of_old &&
+              end_of_old != old_path &&
+              end_of_new != new_path)
+               ; /* Do nothing; all in the while loop */
+       /*
+        * We've found the first non-matching character in the directory
+        * paths.  That means the current directory we were comparing
+        * represents the rename.  Move end_of_old and end_of_new back
+        * to the full directory name.
+        */
+       if (*end_of_old == '/')
+               end_of_old++;
+       if (*end_of_old != '/')
+               end_of_new++;
+       end_of_old = strchr(end_of_old, '/');
+       end_of_new = strchr(end_of_new, '/');
+
+       /*
+        * It may have been the case that old_path and new_path were the same
+        * directory all along.  Don't claim a rename if they're the same.
+        */
+       old_len = end_of_old - old_path;
+       new_len = end_of_new - new_path;
+
+       if (old_len != new_len || strncmp(old_path, new_path, old_len)) {
+               *old_dir = xstrndup(old_path, old_len);
+               *new_dir = xstrndup(new_path, new_len);
+       }
+}
+
+static void remove_hashmap_entries(struct hashmap *dir_renames,
+                                  struct string_list *items_to_remove)
+{
+       int i;
+       struct dir_rename_entry *entry;
+
+       for (i = 0; i < items_to_remove->nr; i++) {
+               entry = items_to_remove->items[i].util;
+               hashmap_remove(dir_renames, entry, NULL);
+       }
+       string_list_clear(items_to_remove, 0);
+}
+
+/*
+ * See if there is a directory rename for path, and if there are any file
+ * level conflicts for the renamed location.  If there is a rename and
+ * there are no conflicts, return the new name.  Otherwise, return NULL.
+ */
+static char *handle_path_level_conflicts(struct merge_options *o,
+                                        const char *path,
+                                        struct dir_rename_entry *entry,
+                                        struct hashmap *collisions,
+                                        struct tree *tree)
+{
+       char *new_path = NULL;
+       struct collision_entry *collision_ent;
+       int clean = 1;
+       struct strbuf collision_paths = STRBUF_INIT;
+
+       /*
+        * entry has the mapping of old directory name to new directory name
+        * that we want to apply to path.
+        */
+       new_path = apply_dir_rename(entry, path);
+
+       if (!new_path) {
+               /* This should only happen when entry->non_unique_new_dir set */
+               if (!entry->non_unique_new_dir)
+                       BUG("entry->non_unqiue_dir not set and !new_path");
+               output(o, 1, _("CONFLICT (directory rename split): "
+                              "Unclear where to place %s because directory "
+                              "%s was renamed to multiple other directories, "
+                              "with no destination getting a majority of the "
+                              "files."),
+                      path, entry->dir);
+               clean = 0;
+               return NULL;
+       }
+
+       /*
+        * The caller needs to have ensured that it has pre-populated
+        * collisions with all paths that map to new_path.  Do a quick check
+        * to ensure that's the case.
+        */
+       collision_ent = collision_find_entry(collisions, new_path);
+       if (collision_ent == NULL)
+               BUG("collision_ent is NULL");
+
+       /*
+        * Check for one-sided add/add/.../add conflicts, i.e.
+        * where implicit renames from the other side doing
+        * directory rename(s) can affect this side of history
+        * to put multiple paths into the same location.  Warn
+        * and bail on directory renames for such paths.
+        */
+       if (collision_ent->reported_already) {
+               clean = 0;
+       } else if (tree_has_path(tree, new_path)) {
+               collision_ent->reported_already = 1;
+               strbuf_add_separated_string_list(&collision_paths, ", ",
+                                                &collision_ent->source_files);
+               output(o, 1, _("CONFLICT (implicit dir rename): Existing "
+                              "file/dir at %s in the way of implicit "
+                              "directory rename(s) putting the following "
+                              "path(s) there: %s."),
+                      new_path, collision_paths.buf);
+               clean = 0;
+       } else if (collision_ent->source_files.nr > 1) {
+               collision_ent->reported_already = 1;
+               strbuf_add_separated_string_list(&collision_paths, ", ",
+                                                &collision_ent->source_files);
+               output(o, 1, _("CONFLICT (implicit dir rename): Cannot map "
+                              "more than one path to %s; implicit directory "
+                              "renames tried to put these paths there: %s"),
+                      new_path, collision_paths.buf);
+               clean = 0;
+       }
+
+       /* Free memory we no longer need */
+       strbuf_release(&collision_paths);
+       if (!clean && new_path) {
+               free(new_path);
+               return NULL;
+       }
+
+       return new_path;
+}
+
+/*
+ * There are a couple things we want to do at the directory level:
+ *   1. Check for both sides renaming to the same thing, in order to avoid
+ *      implicit renaming of files that should be left in place.  (See
+ *      testcase 6b in t6043 for details.)
+ *   2. Prune directory renames if there are still files left in the
+ *      the original directory.  These represent a partial directory rename,
+ *      i.e. a rename where only some of the files within the directory
+ *      were renamed elsewhere.  (Technically, this could be done earlier
+ *      in get_directory_renames(), except that would prevent us from
+ *      doing the previous check and thus failing testcase 6b.)
+ *   3. Check for rename/rename(1to2) conflicts (at the directory level).
+ *      In the future, we could potentially record this info as well and
+ *      omit reporting rename/rename(1to2) conflicts for each path within
+ *      the affected directories, thus cleaning up the merge output.
+ *   NOTE: We do NOT check for rename/rename(2to1) conflicts at the
+ *         directory level, because merging directories is fine.  If it
+ *         causes conflicts for files within those merged directories, then
+ *         that should be detected at the individual path level.
+ */
+static void handle_directory_level_conflicts(struct merge_options *o,
+                                            struct hashmap *dir_re_head,
+                                            struct tree *head,
+                                            struct hashmap *dir_re_merge,
+                                            struct tree *merge)
+{
+       struct hashmap_iter iter;
+       struct dir_rename_entry *head_ent;
+       struct dir_rename_entry *merge_ent;
+
+       struct string_list remove_from_head = STRING_LIST_INIT_NODUP;
+       struct string_list remove_from_merge = STRING_LIST_INIT_NODUP;
+
+       hashmap_iter_init(dir_re_head, &iter);
+       while ((head_ent = hashmap_iter_next(&iter))) {
+               merge_ent = dir_rename_find_entry(dir_re_merge, head_ent->dir);
+               if (merge_ent &&
+                   !head_ent->non_unique_new_dir &&
+                   !merge_ent->non_unique_new_dir &&
+                   !strbuf_cmp(&head_ent->new_dir, &merge_ent->new_dir)) {
+                       /* 1. Renamed identically; remove it from both sides */
+                       string_list_append(&remove_from_head,
+                                          head_ent->dir)->util = head_ent;
+                       strbuf_release(&head_ent->new_dir);
+                       string_list_append(&remove_from_merge,
+                                          merge_ent->dir)->util = merge_ent;
+                       strbuf_release(&merge_ent->new_dir);
+               } else if (tree_has_path(head, head_ent->dir)) {
+                       /* 2. This wasn't a directory rename after all */
+                       string_list_append(&remove_from_head,
+                                          head_ent->dir)->util = head_ent;
+                       strbuf_release(&head_ent->new_dir);
+               }
+       }
+
+       remove_hashmap_entries(dir_re_head, &remove_from_head);
+       remove_hashmap_entries(dir_re_merge, &remove_from_merge);
+
+       hashmap_iter_init(dir_re_merge, &iter);
+       while ((merge_ent = hashmap_iter_next(&iter))) {
+               head_ent = dir_rename_find_entry(dir_re_head, merge_ent->dir);
+               if (tree_has_path(merge, merge_ent->dir)) {
+                       /* 2. This wasn't a directory rename after all */
+                       string_list_append(&remove_from_merge,
+                                          merge_ent->dir)->util = merge_ent;
+               } else if (head_ent &&
+                          !head_ent->non_unique_new_dir &&
+                          !merge_ent->non_unique_new_dir) {
+                       /* 3. rename/rename(1to2) */
+                       /*
+                        * We can assume it's not rename/rename(1to1) because
+                        * that was case (1), already checked above.  So we
+                        * know that head_ent->new_dir and merge_ent->new_dir
+                        * are different strings.
+                        */
+                       output(o, 1, _("CONFLICT (rename/rename): "
+                                      "Rename directory %s->%s in %s. "
+                                      "Rename directory %s->%s in %s"),
+                              head_ent->dir, head_ent->new_dir.buf, o->branch1,
+                              head_ent->dir, merge_ent->new_dir.buf, o->branch2);
+                       string_list_append(&remove_from_head,
+                                          head_ent->dir)->util = head_ent;
+                       strbuf_release(&head_ent->new_dir);
+                       string_list_append(&remove_from_merge,
+                                          merge_ent->dir)->util = merge_ent;
+                       strbuf_release(&merge_ent->new_dir);
+               }
+       }
+
+       remove_hashmap_entries(dir_re_head, &remove_from_head);
+       remove_hashmap_entries(dir_re_merge, &remove_from_merge);
+}
+
+static struct hashmap *get_directory_renames(struct diff_queue_struct *pairs,
+                                            struct tree *tree)
+{
+       struct hashmap *dir_renames;
+       struct hashmap_iter iter;
+       struct dir_rename_entry *entry;
+       int i;
+
+       /*
+        * Typically, we think of a directory rename as all files from a
+        * certain directory being moved to a target directory.  However,
+        * what if someone first moved two files from the original
+        * directory in one commit, and then renamed the directory
+        * somewhere else in a later commit?  At merge time, we just know
+        * that files from the original directory went to two different
+        * places, and that the bulk of them ended up in the same place.
+        * We want each directory rename to represent where the bulk of the
+        * files from that directory end up; this function exists to find
+        * where the bulk of the files went.
+        *
+        * The first loop below simply iterates through the list of file
+        * renames, finding out how often each directory rename pair
+        * possibility occurs.
+        */
+       dir_renames = xmalloc(sizeof(*dir_renames));
+       dir_rename_init(dir_renames);
+       for (i = 0; i < pairs->nr; ++i) {
+               struct string_list_item *item;
+               int *count;
+               struct diff_filepair *pair = pairs->queue[i];
+               char *old_dir, *new_dir;
+
+               /* File not part of directory rename if it wasn't renamed */
+               if (pair->status != 'R')
+                       continue;
+
+               get_renamed_dir_portion(pair->one->path, pair->two->path,
+                                       &old_dir,        &new_dir);
+               if (!old_dir)
+                       /* Directory didn't change at all; ignore this one. */
+                       continue;
+
+               entry = dir_rename_find_entry(dir_renames, old_dir);
+               if (!entry) {
+                       entry = xmalloc(sizeof(*entry));
+                       dir_rename_entry_init(entry, old_dir);
+                       hashmap_put(dir_renames, entry);
+               } else {
+                       free(old_dir);
+               }
+               item = string_list_lookup(&entry->possible_new_dirs, new_dir);
+               if (!item) {
+                       item = string_list_insert(&entry->possible_new_dirs,
+                                                 new_dir);
+                       item->util = xcalloc(1, sizeof(int));
+               } else {
+                       free(new_dir);
+               }
+               count = item->util;
+               *count += 1;
+       }
+
+       /*
+        * For each directory with files moved out of it, we find out which
+        * target directory received the most files so we can declare it to
+        * be the "winning" target location for the directory rename.  This
+        * winner gets recorded in new_dir.  If there is no winner
+        * (multiple target directories received the same number of files),
+        * we set non_unique_new_dir.  Once we've determined the winner (or
+        * that there is no winner), we no longer need possible_new_dirs.
+        */
+       hashmap_iter_init(dir_renames, &iter);
+       while ((entry = hashmap_iter_next(&iter))) {
+               int max = 0;
+               int bad_max = 0;
+               char *best = NULL;
+
+               for (i = 0; i < entry->possible_new_dirs.nr; i++) {
+                       int *count = entry->possible_new_dirs.items[i].util;
+
+                       if (*count == max)
+                               bad_max = max;
+                       else if (*count > max) {
+                               max = *count;
+                               best = entry->possible_new_dirs.items[i].string;
+                       }
+               }
+               if (bad_max == max)
+                       entry->non_unique_new_dir = 1;
+               else {
+                       assert(entry->new_dir.len == 0);
+                       strbuf_addstr(&entry->new_dir, best);
+               }
+               /*
+                * The relevant directory sub-portion of the original full
+                * filepaths were xstrndup'ed before inserting into
+                * possible_new_dirs, and instead of manually iterating the
+                * list and free'ing each, just lie and tell
+                * possible_new_dirs that it did the strdup'ing so that it
+                * will free them for us.
+                */
+               entry->possible_new_dirs.strdup_strings = 1;
+               string_list_clear(&entry->possible_new_dirs, 1);
+       }
+
+       return dir_renames;
+}
+
+static struct dir_rename_entry *check_dir_renamed(const char *path,
+                                                 struct hashmap *dir_renames)
+{
+       char temp[PATH_MAX];
+       char *end;
+       struct dir_rename_entry *entry;
+
+       strcpy(temp, path);
+       while ((end = strrchr(temp, '/'))) {
+               *end = '\0';
+               entry = dir_rename_find_entry(dir_renames, temp);
+               if (entry)
+                       return entry;
+       }
+       return NULL;
+}
+
+static void compute_collisions(struct hashmap *collisions,
+                              struct hashmap *dir_renames,
+                              struct diff_queue_struct *pairs)
+{
+       int i;
+
+       /*
+        * Multiple files can be mapped to the same path due to directory
+        * renames done by the other side of history.  Since that other
+        * side of history could have merged multiple directories into one,
+        * if our side of history added the same file basename to each of
+        * those directories, then all N of them would get implicitly
+        * renamed by the directory rename detection into the same path,
+        * and we'd get an add/add/.../add conflict, and all those adds
+        * from *this* side of history.  This is not representable in the
+        * index, and users aren't going to easily be able to make sense of
+        * it.  So we need to provide a good warning about what's
+        * happening, and fall back to no-directory-rename detection
+        * behavior for those paths.
+        *
+        * See testcases 9e and all of section 5 from t6043 for examples.
+        */
+       collision_init(collisions);
+
+       for (i = 0; i < pairs->nr; ++i) {
+               struct dir_rename_entry *dir_rename_ent;
+               struct collision_entry *collision_ent;
+               char *new_path;
+               struct diff_filepair *pair = pairs->queue[i];
+
+               if (pair->status != 'A' && pair->status != 'R')
+                       continue;
+               dir_rename_ent = check_dir_renamed(pair->two->path,
+                                                  dir_renames);
+               if (!dir_rename_ent)
+                       continue;
+
+               new_path = apply_dir_rename(dir_rename_ent, pair->two->path);
+               if (!new_path)
+                       /*
+                        * dir_rename_ent->non_unique_new_path is true, which
+                        * means there is no directory rename for us to use,
+                        * which means it won't cause us any additional
+                        * collisions.
+                        */
+                       continue;
+               collision_ent = collision_find_entry(collisions, new_path);
+               if (!collision_ent) {
+                       collision_ent = xcalloc(1,
+                                               sizeof(struct collision_entry));
+                       hashmap_entry_init(collision_ent, strhash(new_path));
+                       hashmap_put(collisions, collision_ent);
+                       collision_ent->target_file = new_path;
+               } else {
+                       free(new_path);
+               }
+               string_list_insert(&collision_ent->source_files,
+                                  pair->two->path);
+       }
+}
+
+static char *check_for_directory_rename(struct merge_options *o,
+                                       const char *path,
+                                       struct tree *tree,
+                                       struct hashmap *dir_renames,
+                                       struct hashmap *dir_rename_exclusions,
+                                       struct hashmap *collisions,
+                                       int *clean_merge)
+{
+       char *new_path = NULL;
+       struct dir_rename_entry *entry = check_dir_renamed(path, dir_renames);
+       struct dir_rename_entry *oentry = NULL;
+
+       if (!entry)
+               return new_path;
+
+       /*
+        * This next part is a little weird.  We do not want to do an
+        * implicit rename into a directory we renamed on our side, because
+        * that will result in a spurious rename/rename(1to2) conflict.  An
+        * example:
+        *   Base commit: dumbdir/afile, otherdir/bfile
+        *   Side 1:      smrtdir/afile, otherdir/bfile
+        *   Side 2:      dumbdir/afile, dumbdir/bfile
+        * Here, while working on Side 1, we could notice that otherdir was
+        * renamed/merged to dumbdir, and change the diff_filepair for
+        * otherdir/bfile into a rename into dumbdir/bfile.  However, Side
+        * 2 will notice the rename from dumbdir to smrtdir, and do the
+        * transitive rename to move it from dumbdir/bfile to
+        * smrtdir/bfile.  That gives us bfile in dumbdir vs being in
+        * smrtdir, a rename/rename(1to2) conflict.  We really just want
+        * the file to end up in smrtdir.  And the way to achieve that is
+        * to not let Side1 do the rename to dumbdir, since we know that is
+        * the source of one of our directory renames.
+        *
+        * That's why oentry and dir_rename_exclusions is here.
+        *
+        * As it turns out, this also prevents N-way transient rename
+        * confusion; See testcases 9c and 9d of t6043.
+        */
+       oentry = dir_rename_find_entry(dir_rename_exclusions, entry->new_dir.buf);
+       if (oentry) {
+               output(o, 1, _("WARNING: Avoiding applying %s -> %s rename "
+                              "to %s, because %s itself was renamed."),
+                      entry->dir, entry->new_dir.buf, path, entry->new_dir.buf);
+       } else {
+               new_path = handle_path_level_conflicts(o, path, entry,
+                                                      collisions, tree);
+               *clean_merge &= (new_path != NULL);
+       }
+
+       return new_path;
+}
+
+static void apply_directory_rename_modifications(struct merge_options *o,
+                                                struct diff_filepair *pair,
+                                                char *new_path,
+                                                struct rename *re,
+                                                struct tree *tree,
+                                                struct tree *o_tree,
+                                                struct tree *a_tree,
+                                                struct tree *b_tree,
+                                                struct string_list *entries,
+                                                int *clean)
+{
+       struct string_list_item *item;
+       int stage = (tree == a_tree ? 2 : 3);
+       int update_wd;
+
+       /*
+        * In all cases where we can do directory rename detection,
+        * unpack_trees() will have read pair->two->path into the
+        * index and the working copy.  We need to remove it so that
+        * we can instead place it at new_path.  It is guaranteed to
+        * not be untracked (unpack_trees() would have errored out
+        * saying the file would have been overwritten), but it might
+        * be dirty, though.
+        */
+       update_wd = !was_dirty(o, pair->two->path);
+       if (!update_wd)
+               output(o, 1, _("Refusing to lose dirty file at %s"),
+                      pair->two->path);
+       remove_file(o, 1, pair->two->path, !update_wd);
+
+       /* Find or create a new re->dst_entry */
+       item = string_list_lookup(entries, new_path);
+       if (item) {
+               /*
+                * Since we're renaming on this side of history, and it's
+                * due to a directory rename on the other side of history
+                * (which we only allow when the directory in question no
+                * longer exists on the other side of history), the
+                * original entry for re->dst_entry is no longer
+                * necessary...
+                */
+               re->dst_entry->processed = 1;
+
+               /*
+                * ...because we'll be using this new one.
+                */
+               re->dst_entry = item->util;
+       } else {
+               /*
+                * re->dst_entry is for the before-dir-rename path, and we
+                * need it to hold information for the after-dir-rename
+                * path.  Before creating a new entry, we need to mark the
+                * old one as unnecessary (...unless it is shared by
+                * src_entry, i.e. this didn't use to be a rename, in which
+                * case we can just allow the normal processing to happen
+                * for it).
+                */
+               if (pair->status == 'R')
+                       re->dst_entry->processed = 1;
+
+               re->dst_entry = insert_stage_data(new_path,
+                                                 o_tree, a_tree, b_tree,
+                                                 entries);
+               item = string_list_insert(entries, new_path);
+               item->util = re->dst_entry;
+       }
+
+       /*
+        * Update the stage_data with the information about the path we are
+        * moving into place.  That slot will be empty and available for us
+        * to write to because of the collision checks in
+        * handle_path_level_conflicts().  In other words,
+        * re->dst_entry->stages[stage].oid will be the null_oid, so it's
+        * open for us to write to.
+        *
+        * It may be tempting to actually update the index at this point as
+        * well, using update_stages_for_stage_data(), but as per the big
+        * "NOTE" in update_stages(), doing so will modify the current
+        * in-memory index which will break calls to would_lose_untracked()
+        * that we need to make.  Instead, we need to just make sure that
+        * the various conflict_rename_*() functions update the index
+        * explicitly rather than relying on unpack_trees() to have done it.
+        */
+       get_tree_entry(&tree->object.oid,
+                      pair->two->path,
+                      &re->dst_entry->stages[stage].oid,
+                      &re->dst_entry->stages[stage].mode);
+
+       /* Update pair status */
+       if (pair->status == 'A') {
+               /*
+                * Recording rename information for this add makes it look
+                * like a rename/delete conflict.  Make sure we can
+                * correctly handle this as an add that was moved to a new
+                * directory instead of reporting a rename/delete conflict.
+                */
+               re->add_turned_into_rename = 1;
+       }
+       /*
+        * We don't actually look at pair->status again, but it seems
+        * pedagogically correct to adjust it.
+        */
+       pair->status = 'R';
+
+       /*
+        * Finally, record the new location.
+        */
+       pair->two->path = new_path;
+}
+
+/*
+ * Get information of all renames which occurred in 'pairs', making use of
+ * any implicit directory renames inferred from the other side of history.
+ * We need the three trees in the merge ('o_tree', 'a_tree' and 'b_tree')
+ * to be able to associate the correct cache entries with the rename
+ * information; tree is always equal to either a_tree or b_tree.
+ */
+static struct string_list *get_renames(struct merge_options *o,
+                                      struct diff_queue_struct *pairs,
+                                      struct hashmap *dir_renames,
+                                      struct hashmap *dir_rename_exclusions,
+                                      struct tree *tree,
+                                      struct tree *o_tree,
+                                      struct tree *a_tree,
+                                      struct tree *b_tree,
+                                      struct string_list *entries,
+                                      int *clean_merge)
+{
+       int i;
+       struct hashmap collisions;
+       struct hashmap_iter iter;
+       struct collision_entry *e;
+       struct string_list *renames;
+
+       compute_collisions(&collisions, dir_renames, pairs);
+       renames = xcalloc(1, sizeof(struct string_list));
+
+       for (i = 0; i < pairs->nr; ++i) {
+               struct string_list_item *item;
+               struct rename *re;
+               struct diff_filepair *pair = pairs->queue[i];
+               char *new_path; /* non-NULL only with directory renames */
+
+               if (pair->status != 'A' && pair->status != 'R') {
+                       diff_free_filepair(pair);
+                       continue;
+               }
+               new_path = check_for_directory_rename(o, pair->two->path, tree,
+                                                     dir_renames,
+                                                     dir_rename_exclusions,
+                                                     &collisions,
+                                                     clean_merge);
+               if (pair->status != 'R' && !new_path) {
+                       diff_free_filepair(pair);
+                       continue;
+               }
+
+               re = xmalloc(sizeof(*re));
+               re->processed = 0;
+               re->add_turned_into_rename = 0;
+               re->pair = pair;
+               item = string_list_lookup(entries, re->pair->one->path);
+               if (!item)
+                       re->src_entry = insert_stage_data(re->pair->one->path,
+                                       o_tree, a_tree, b_tree, entries);
+               else
+                       re->src_entry = item->util;
+
+               item = string_list_lookup(entries, re->pair->two->path);
+               if (!item)
+                       re->dst_entry = insert_stage_data(re->pair->two->path,
+                                       o_tree, a_tree, b_tree, entries);
+               else
+                       re->dst_entry = item->util;
+               item = string_list_insert(renames, pair->one->path);
+               item->util = re;
+               if (new_path)
+                       apply_directory_rename_modifications(o, pair, new_path,
+                                                            re, tree, o_tree,
+                                                            a_tree, b_tree,
+                                                            entries,
+                                                            clean_merge);
+       }
+
+       hashmap_iter_init(&collisions, &iter);
+       while ((e = hashmap_iter_next(&iter))) {
+               free(e->target_file);
+               string_list_clear(&e->source_files, 0);
+       }
+       hashmap_free(&collisions, 1);
+       return renames;
+}
+
+static int process_renames(struct merge_options *o,
+                          struct string_list *a_renames,
+                          struct string_list *b_renames)
+{
+       int clean_merge = 1, i, j;
+       struct string_list a_by_dst = STRING_LIST_INIT_NODUP;
+       struct string_list b_by_dst = STRING_LIST_INIT_NODUP;
+       const struct rename *sre;
+
+       for (i = 0; i < a_renames->nr; i++) {
+               sre = a_renames->items[i].util;
+               string_list_insert(&a_by_dst, sre->pair->two->path)->util
+                       = (void *)sre;
+       }
+       for (i = 0; i < b_renames->nr; i++) {
+               sre = b_renames->items[i].util;
+               string_list_insert(&b_by_dst, sre->pair->two->path)->util
+                       = (void *)sre;
+       }
 
        for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) {
                struct string_list *renames1, *renames2Dst;
@@ -1462,7 +2604,7 @@ static int process_renames(struct merge_options *o,
                        const char *ren2_dst = ren2->pair->two->path;
                        enum rename_type rename_type;
                        if (strcmp(ren1_src, ren2_src) != 0)
-                               die("BUG: ren1_src != ren2_src");
+                               BUG("ren1_src != ren2_src");
                        ren2->dst_entry->processed = 1;
                        ren2->processed = 1;
                        if (strcmp(ren1_dst, ren2_dst) != 0) {
@@ -1496,7 +2638,7 @@ static int process_renames(struct merge_options *o,
                        ren2 = lookup->util;
                        ren2_dst = ren2->pair->two->path;
                        if (strcmp(ren1_dst, ren2_dst) != 0)
-                               die("BUG: ren1_dst != ren2_dst");
+                               BUG("ren1_dst != ren2_dst");
 
                        clean_merge = 0;
                        ren2->processed = 1;
@@ -1538,7 +2680,7 @@ static int process_renames(struct merge_options *o,
                         * add-source case).
                         */
                        remove_file(o, 1, ren1_src,
-                                   renamed_stage == 2 || !was_tracked(ren1_src));
+                                   renamed_stage == 2 || !was_tracked(o, ren1_src));
 
                        oidcpy(&src_other.oid,
                               &ren1->src_entry->stages[other_stage].oid);
@@ -1548,7 +2690,19 @@ static int process_renames(struct merge_options *o,
                        dst_other.mode = ren1->dst_entry->stages[other_stage].mode;
                        try_merge = 0;
 
-                       if (oid_eq(&src_other.oid, &null_oid)) {
+                       if (oid_eq(&src_other.oid, &null_oid) &&
+                           ren1->add_turned_into_rename) {
+                               setup_rename_conflict_info(RENAME_DIR,
+                                                          ren1->pair,
+                                                          NULL,
+                                                          branch1,
+                                                          branch2,
+                                                          ren1->dst_entry,
+                                                          NULL,
+                                                          o,
+                                                          NULL,
+                                                          NULL);
+                       } else if (oid_eq(&src_other.oid, &null_oid)) {
                                setup_rename_conflict_info(RENAME_DELETE,
                                                           ren1->pair,
                                                           NULL,
@@ -1645,6 +2799,105 @@ static int process_renames(struct merge_options *o,
        return clean_merge;
 }
 
+struct rename_info {
+       struct string_list *head_renames;
+       struct string_list *merge_renames;
+};
+
+static void initial_cleanup_rename(struct diff_queue_struct *pairs,
+                                  struct hashmap *dir_renames)
+{
+       struct hashmap_iter iter;
+       struct dir_rename_entry *e;
+
+       hashmap_iter_init(dir_renames, &iter);
+       while ((e = hashmap_iter_next(&iter))) {
+               free(e->dir);
+               strbuf_release(&e->new_dir);
+               /* possible_new_dirs already cleared in get_directory_renames */
+       }
+       hashmap_free(dir_renames, 1);
+       free(dir_renames);
+
+       free(pairs->queue);
+       free(pairs);
+}
+
+static int handle_renames(struct merge_options *o,
+                         struct tree *common,
+                         struct tree *head,
+                         struct tree *merge,
+                         struct string_list *entries,
+                         struct rename_info *ri)
+{
+       struct diff_queue_struct *head_pairs, *merge_pairs;
+       struct hashmap *dir_re_head, *dir_re_merge;
+       int clean = 1;
+
+       ri->head_renames = NULL;
+       ri->merge_renames = NULL;
+
+       if (!merge_detect_rename(o))
+               return 1;
+
+       head_pairs = get_diffpairs(o, common, head);
+       merge_pairs = get_diffpairs(o, common, merge);
+
+       dir_re_head = get_directory_renames(head_pairs, head);
+       dir_re_merge = get_directory_renames(merge_pairs, merge);
+
+       handle_directory_level_conflicts(o,
+                                        dir_re_head, head,
+                                        dir_re_merge, merge);
+
+       ri->head_renames  = get_renames(o, head_pairs,
+                                       dir_re_merge, dir_re_head, head,
+                                       common, head, merge, entries,
+                                       &clean);
+       if (clean < 0)
+               goto cleanup;
+       ri->merge_renames = get_renames(o, merge_pairs,
+                                       dir_re_head, dir_re_merge, merge,
+                                       common, head, merge, entries,
+                                       &clean);
+       if (clean < 0)
+               goto cleanup;
+       clean &= process_renames(o, ri->head_renames, ri->merge_renames);
+
+cleanup:
+       /*
+        * Some cleanup is deferred until cleanup_renames() because the
+        * data structures are still needed and referenced in
+        * process_entry().  But there are a few things we can free now.
+        */
+       initial_cleanup_rename(head_pairs, dir_re_head);
+       initial_cleanup_rename(merge_pairs, dir_re_merge);
+
+       return clean;
+}
+
+static void final_cleanup_rename(struct string_list *rename)
+{
+       const struct rename *re;
+       int i;
+
+       if (rename == NULL)
+               return;
+
+       for (i = 0; i < rename->nr; i++) {
+               re = rename->items[i].util;
+               diff_free_filepair(re->pair);
+       }
+       string_list_clear(rename, 1);
+       free(rename);
+}
+
+static void final_cleanup_renames(struct rename_info *re_info)
+{
+       final_cleanup_rename(re_info->head_renames);
+       final_cleanup_rename(re_info->merge_renames);
+}
+
 static struct object_id *stage_oid(const struct object_id *oid, unsigned mode)
 {
        return (is_null_oid(oid) || mode == 0) ? NULL: (struct object_id *)oid;
@@ -1735,6 +2988,7 @@ static int handle_modify_delete(struct merge_options *o,
 
 static int merge_content(struct merge_options *o,
                         const char *path,
+                        int is_dirty,
                         struct object_id *o_oid, int o_mode,
                         struct object_id *a_oid, int a_mode,
                         struct object_id *b_oid, int b_mode,
@@ -1775,29 +3029,26 @@ static int merge_content(struct merge_options *o,
                               S_ISGITLINK(pair1->two->mode)))
                        df_conflict_remains = 1;
        }
-       if (merge_file_special_markers(o, &one, &a, &b,
+       if (merge_file_special_markers(o, &one, &a, &b, path,
                                       o->branch1, path1,
                                       o->branch2, path2, &mfi))
                return -1;
 
-       if (mfi.clean && !df_conflict_remains &&
-           oid_eq(&mfi.oid, a_oid) && mfi.mode == a_mode) {
-               int path_renamed_outside_HEAD;
+       /*
+        * We can skip updating the working tree file iff:
+        *   a) The merge is clean
+        *   b) The merge matches what was in HEAD (content, mode, pathname)
+        *   c) The target path is usable (i.e. not involved in D/F conflict)
+        */
+       if (mfi.clean &&
+           was_tracked_and_matches(o, path, &mfi.oid, mfi.mode) &&
+           !df_conflict_remains) {
                output(o, 3, _("Skipped %s (merged same as existing)"), path);
-               /*
-                * The content merge resulted in the same file contents we
-                * already had.  We can return early if those file contents
-                * are recorded at the correct path (which may not be true
-                * if the merge involves a rename).
-                */
-               path_renamed_outside_HEAD = !path2 || !strcmp(path, path2);
-               if (!path_renamed_outside_HEAD) {
-                       add_cacheinfo(o, mfi.mode, &mfi.oid, path,
-                                     0, (!o->call_depth), 0);
-                       return mfi.clean;
-               }
-       } else
-               output(o, 2, _("Auto-merging %s"), path);
+               if (add_cacheinfo(o, mfi.mode, &mfi.oid, path,
+                                 0, (!o->call_depth && !is_dirty), 0))
+                       return -1;
+               return mfi.clean;
+       }
 
        if (!mfi.clean) {
                if (S_ISGITLINK(mfi.mode))
@@ -1809,7 +3060,7 @@ static int merge_content(struct merge_options *o,
                                return -1;
        }
 
-       if (df_conflict_remains) {
+       if (df_conflict_remains || is_dirty) {
                char *new_path;
                if (o->call_depth) {
                        remove_file_from_cache(path);
@@ -1818,7 +3069,7 @@ static int merge_content(struct merge_options *o,
                                if (update_stages(o, path, &one, &a, &b))
                                        return -1;
                        } else {
-                               int file_from_stage2 = was_tracked(path);
+                               int file_from_stage2 = was_tracked(o, path);
                                struct diff_filespec merged;
                                oidcpy(&merged.oid, &mfi.oid);
                                merged.mode = mfi.mode;
@@ -1831,6 +3082,10 @@ static int merge_content(struct merge_options *o,
 
                }
                new_path = unique_path(o, path, rename_conflict_info->branch1);
+               if (is_dirty) {
+                       output(o, 1, _("Refusing to lose dirty file at %s"),
+                              path);
+               }
                output(o, 1, _("Adding as %s instead"), new_path);
                if (update_file(o, 0, &mfi.oid, mfi.mode, new_path)) {
                        free(new_path);
@@ -1840,7 +3095,20 @@ static int merge_content(struct merge_options *o,
                mfi.clean = 0;
        } else if (update_file(o, mfi.clean, &mfi.oid, mfi.mode, path))
                return -1;
-       return mfi.clean;
+       return !is_dirty && mfi.clean;
+}
+
+static int conflict_rename_normal(struct merge_options *o,
+                                 const char *path,
+                                 struct object_id *o_oid, unsigned int o_mode,
+                                 struct object_id *a_oid, unsigned int a_mode,
+                                 struct object_id *b_oid, unsigned int b_mode,
+                                 struct rename_conflict_info *ci)
+{
+       /* Merge the content and write it out */
+       return merge_content(o, path, was_dirty(o, path),
+                            o_oid, o_mode, a_oid, a_mode, b_oid, b_mode,
+                            ci);
 }
 
 /* Per entry merge function */
@@ -1862,9 +3130,20 @@ static int process_entry(struct merge_options *o,
                switch (conflict_info->rename_type) {
                case RENAME_NORMAL:
                case RENAME_ONE_FILE_TO_ONE:
-                       clean_merge = merge_content(o, path,
-                                                   o_oid, o_mode, a_oid, a_mode, b_oid, b_mode,
-                                                   conflict_info);
+                       clean_merge = conflict_rename_normal(o,
+                                                            path,
+                                                            o_oid, o_mode,
+                                                            a_oid, a_mode,
+                                                            b_oid, b_mode,
+                                                            conflict_info);
+                       break;
+               case RENAME_DIR:
+                       clean_merge = 1;
+                       if (conflict_rename_dir(o,
+                                               conflict_info->pair1,
+                                               conflict_info->branch1,
+                                               conflict_info->branch2))
+                               clean_merge = -1;
                        break;
                case RENAME_DELETE:
                        clean_merge = 0;
@@ -1952,7 +3231,8 @@ static int process_entry(struct merge_options *o,
        } else if (a_oid && b_oid) {
                /* Case C: Added in both (check for same permissions) and */
                /* case D: Modified in both, but differently. */
-               clean_merge = merge_content(o, path,
+               int is_dirty = 0; /* unpack_trees would have bailed if dirty */
+               clean_merge = merge_content(o, path, is_dirty,
                                            o_oid, o_mode, a_oid, a_mode, b_oid, b_mode,
                                            NULL);
        } else if (!o_oid && !a_oid && !b_oid) {
@@ -1962,7 +3242,7 @@ static int process_entry(struct merge_options *o,
                 */
                remove_file(o, 1, path, !a_mode);
        } else
-               die("BUG: fatal merge failure, shouldn't happen.");
+               BUG("fatal merge failure, shouldn't happen.");
 
        return clean_merge;
 }
@@ -1993,18 +3273,20 @@ int merge_trees(struct merge_options *o,
                return 1;
        }
 
-       code = git_merge_trees(o->call_depth, common, head, merge);
+       code = unpack_trees_start(o, common, head, merge);
 
        if (code != 0) {
                if (show(o, 4) || o->call_depth)
                        err(o, _("merging of trees %s and %s failed"),
                            oid_to_hex(&head->object.oid),
                            oid_to_hex(&merge->object.oid));
+               unpack_trees_finish(o);
                return -1;
        }
 
        if (unmerged_cache()) {
-               struct string_list *entries, *re_head, *re_merge;
+               struct string_list *entries;
+               struct rename_info re_info;
                int i;
                /*
                 * Only need the hashmap while processing entries, so
@@ -2018,9 +3300,8 @@ int merge_trees(struct merge_options *o,
                get_files_dirs(o, merge);
 
                entries = get_unmerged();
-               re_head  = get_renames(o, head, common, head, merge, entries);
-               re_merge = get_renames(o, merge, common, head, merge, entries);
-               clean = process_renames(o, re_head, re_merge);
+               clean = handle_renames(o, common, head, merge, entries,
+                                      &re_info);
                record_df_conflict_files(o, entries);
                if (clean < 0)
                        goto cleanup;
@@ -2040,27 +3321,28 @@ int merge_trees(struct merge_options *o,
                for (i = 0; i < entries->nr; i++) {
                        struct stage_data *e = entries->items[i].util;
                        if (!e->processed)
-                               die("BUG: unprocessed path??? %s",
+                               BUG("unprocessed path??? %s",
                                    entries->items[i].string);
                }
 
 cleanup:
-               string_list_clear(re_merge, 0);
-               string_list_clear(re_head, 0);
+               final_cleanup_renames(&re_info);
+
                string_list_clear(entries, 1);
+               free(entries);
 
                hashmap_free(&o->current_file_dir_set, 1);
 
-               free(re_merge);
-               free(re_head);
-               free(entries);
-
-               if (clean < 0)
+               if (clean < 0) {
+                       unpack_trees_finish(o);
                        return clean;
+               }
        }
        else
                clean = 1;
 
+       unpack_trees_finish(o);
+
        if (o->call_depth && !(*result = write_tree_from_memory(o)))
                return -1;
 
@@ -2154,7 +3436,8 @@ int merge_recursive(struct merge_options *o,
                read_cache();
 
        o->ancestor = "merged common ancestors";
-       clean = merge_trees(o, h1->tree, h2->tree, merged_common_ancestors->tree,
+       clean = merge_trees(o, get_commit_tree(h1), get_commit_tree(h2),
+                           get_commit_tree(merged_common_ancestors),
                            &mrtree);
        if (clean < 0) {
                flush_output(o);
@@ -2232,9 +3515,18 @@ int merge_recursive_generic(struct merge_options *o,
 
 static void merge_recursive_config(struct merge_options *o)
 {
+       char *value = NULL;
        git_config_get_int("merge.verbosity", &o->verbosity);
        git_config_get_int("diff.renamelimit", &o->diff_rename_limit);
        git_config_get_int("merge.renamelimit", &o->merge_rename_limit);
+       if (!git_config_get_string("diff.renames", &value)) {
+               o->diff_detect_rename = git_config_rename("diff.renames", value);
+               free(value);
+       }
+       if (!git_config_get_string("merge.renames", &value)) {
+               o->merge_detect_rename = git_config_rename("merge.renames", value);
+               free(value);
+       }
        git_config(git_xmerge_config, NULL);
 }
 
@@ -2247,7 +3539,8 @@ void init_merge_options(struct merge_options *o)
        o->diff_rename_limit = -1;
        o->merge_rename_limit = -1;
        o->renormalize = 0;
-       o->detect_rename = 1;
+       o->diff_detect_rename = -1;
+       o->merge_detect_rename = -1;
        merge_recursive_config(o);
        merge_verbosity = getenv("GIT_MERGE_VERBOSITY");
        if (merge_verbosity)
@@ -2298,16 +3591,16 @@ int parse_merge_opt(struct merge_options *o, const char *s)
        else if (!strcmp(s, "no-renormalize"))
                o->renormalize = 0;
        else if (!strcmp(s, "no-renames"))
-               o->detect_rename = 0;
+               o->merge_detect_rename = 0;
        else if (!strcmp(s, "find-renames")) {
-               o->detect_rename = 1;
+               o->merge_detect_rename = 1;
                o->rename_score = 0;
        }
        else if (skip_prefix(s, "find-renames=", &arg) ||
                 skip_prefix(s, "rename-threshold=", &arg)) {
                if ((o->rename_score = parse_rename_score(&arg)) == -1 || *arg != 0)
                        return -1;
-               o->detect_rename = 1;
+               o->merge_detect_rename = 1;
        }
        else
                return -1;
index 80d69d140195cc3ba1054050569e56bfc0277b56..fa7bc6b6836f99ab9c417e2865cd5a544488d025 100644 (file)
@@ -1,6 +1,7 @@
 #ifndef MERGE_RECURSIVE_H
 #define MERGE_RECURSIVE_H
 
+#include "unpack-trees.h"
 #include "string-list.h"
 
 struct merge_options {
@@ -17,7 +18,8 @@ struct merge_options {
        unsigned renormalize : 1;
        long xdl_opts;
        int verbosity;
-       int detect_rename;
+       int diff_detect_rename;
+       int merge_detect_rename;
        int diff_rename_limit;
        int merge_rename_limit;
        int rename_score;
@@ -27,8 +29,41 @@ struct merge_options {
        struct strbuf obuf;
        struct hashmap current_file_dir_set;
        struct string_list df_conflict_file_set;
+       struct unpack_trees_options unpack_opts;
+       struct index_state orig_index;
 };
 
+/*
+ * For dir_rename_entry, directory names are stored as a full path from the
+ * toplevel of the repository and do not include a trailing '/'.  Also:
+ *
+ *   dir:                original name of directory being renamed
+ *   non_unique_new_dir: if true, could not determine new_dir
+ *   new_dir:            final name of directory being renamed
+ *   possible_new_dirs:  temporary used to help determine new_dir; see comments
+ *                       in get_directory_renames() for details
+ */
+struct dir_rename_entry {
+       struct hashmap_entry ent; /* must be the first member! */
+       char *dir;
+       unsigned non_unique_new_dir:1;
+       struct strbuf new_dir;
+       struct string_list possible_new_dirs;
+};
+
+struct collision_entry {
+       struct hashmap_entry ent; /* must be the first member! */
+       char *target_file;
+       struct string_list source_files;
+       unsigned reported_already:1;
+};
+
+static inline int merge_detect_rename(struct merge_options *o)
+{
+       return o->merge_detect_rename >= 0 ? o->merge_detect_rename :
+               o->diff_detect_rename >= 0 ? o->diff_detect_rename : 1;
+}
+
 /* merge_trees() but with recursive ancestor consolidation */
 int merge_recursive(struct merge_options *o,
                    struct commit *h1,
diff --git a/merge.c b/merge.c
index f06a4773d4f4093d700c652accc79ae17f161a59..0783858739f84028df6eef85d3673c944fabb912 100644 (file)
--- a/merge.c
+++ b/merge.c
 
 static const char *merge_argument(struct commit *commit)
 {
-       if (commit)
-               return oid_to_hex(&commit->object.oid);
-       else
-               return EMPTY_TREE_SHA1_HEX;
+       return oid_to_hex(commit ? &commit->object.oid : the_hash_algo->empty_tree);
 }
 
 int index_has_changes(struct strbuf *sb)
@@ -94,8 +91,24 @@ int checkout_fast_forward(const struct object_id *head,
                return -1;
 
        memset(&trees, 0, sizeof(trees));
-       memset(&opts, 0, sizeof(opts));
        memset(&t, 0, sizeof(t));
+
+       trees[nr_trees] = parse_tree_indirect(head);
+       if (!trees[nr_trees++]) {
+               rollback_lock_file(&lock_file);
+               return -1;
+       }
+       trees[nr_trees] = parse_tree_indirect(remote);
+       if (!trees[nr_trees++]) {
+               rollback_lock_file(&lock_file);
+               return -1;
+       }
+       for (i = 0; i < nr_trees; i++) {
+               parse_tree(trees[i]);
+               init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+       }
+
+       memset(&opts, 0, sizeof(opts));
        if (overwrite_ignore) {
                memset(&dir, 0, sizeof(dir));
                dir.flags |= DIR_SHOW_IGNORED;
@@ -112,24 +125,13 @@ int checkout_fast_forward(const struct object_id *head,
        opts.fn = twoway_merge;
        setup_unpack_trees_porcelain(&opts, "merge");
 
-       trees[nr_trees] = parse_tree_indirect(head);
-       if (!trees[nr_trees++]) {
-               rollback_lock_file(&lock_file);
-               return -1;
-       }
-       trees[nr_trees] = parse_tree_indirect(remote);
-       if (!trees[nr_trees++]) {
-               rollback_lock_file(&lock_file);
-               return -1;
-       }
-       for (i = 0; i < nr_trees; i++) {
-               parse_tree(trees[i]);
-               init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
-       }
        if (unpack_trees(nr_trees, t, &opts)) {
                rollback_lock_file(&lock_file);
+               clear_unpack_trees_porcelain(&opts);
                return -1;
        }
+       clear_unpack_trees_porcelain(&opts);
+
        if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
                return error(_("unable to write new index file"));
        return 0;
index 8e0726a9418e3b24bc8e665057f607e18e7d4206..d613e06574aec31e0ecaf083b489b0465e5c27f1 100644 (file)
@@ -442,7 +442,7 @@ static int merge_one_change(struct notes_merge_options *o,
                        printf("Using remote notes for %s\n",
                                                oid_to_hex(&p->obj));
                if (add_note(t, &p->obj, &p->remote, combine_notes_overwrite))
-                       die("BUG: combine_notes_overwrite failed");
+                       BUG("combine_notes_overwrite failed");
                return 0;
        case NOTES_MERGE_RESOLVE_UNION:
                if (o->verbosity >= 2)
@@ -490,7 +490,7 @@ static int merge_changes(struct notes_merge_options *o,
                        trace_printf("\t\t\tno local change, adopted remote\n");
                        if (add_note(t, &p->obj, &p->remote,
                                     combine_notes_overwrite))
-                               die("BUG: combine_notes_overwrite failed");
+                               BUG("combine_notes_overwrite failed");
                } else {
                        /* need file-level merge between local and remote */
                        trace_printf("\t\t\tneed content-level merge\n");
@@ -600,14 +600,14 @@ int notes_merge(struct notes_merge_options *o,
                        printf("No merge base found; doing history-less merge\n");
        } else if (!bases->next) {
                base_oid = &bases->item->object.oid;
-               base_tree_oid = &bases->item->tree->object.oid;
+               base_tree_oid = get_commit_tree_oid(bases->item);
                if (o->verbosity >= 4)
                        printf("One merge base found (%.7s)\n",
                               oid_to_hex(base_oid));
        } else {
                /* TODO: How to handle multiple merge-bases? */
                base_oid = &bases->item->object.oid;
-               base_tree_oid = &bases->item->tree->object.oid;
+               base_tree_oid = get_commit_tree_oid(bases->item);
                if (o->verbosity >= 3)
                        printf("Multiple merge bases found. Using the first "
                                "(%.7s)\n", oid_to_hex(base_oid));
@@ -634,8 +634,9 @@ int notes_merge(struct notes_merge_options *o,
                goto found_result;
        }
 
-       result = merge_from_diffs(o, base_tree_oid, &local->tree->object.oid,
-                                 &remote->tree->object.oid, local_tree);
+       result = merge_from_diffs(o, base_tree_oid,
+                                 get_commit_tree_oid(local),
+                                 get_commit_tree_oid(remote), local_tree);
 
        if (result != 0) { /* non-trivial merge (with or without conflicts) */
                /* Commit (partial) result */
index 1ff862c7f93fbe793af21acc963ea0f7ea8f1623..d683112fd7bbab5df7e8cd5656b497cb8fd2bda1 100644 (file)
@@ -71,8 +71,10 @@ struct packed_git {
        int index_version;
        time_t mtime;
        int pack_fd;
+       int index;              /* for builtin/pack-objects.c */
        unsigned pack_local:1,
                 pack_keep:1,
+                pack_keep_in_core:1,
                 freshened:1,
                 do_not_close:1,
                 pack_promisor:1;
index 66cffaf6e516f181fa391ddab125fdc16cc71cb5..f7f4de3aaf6d42a6dde5811b37aa8239ac183754 100644 (file)
--- a/object.c
+++ b/object.c
@@ -257,7 +257,7 @@ struct object *parse_object(const struct object_id *oid)
 
        if ((obj && obj->type == OBJ_BLOB && has_object_file(oid)) ||
            (!obj && has_object_file(oid) &&
-            oid_object_info(oid, NULL) == OBJ_BLOB)) {
+            oid_object_info(the_repository, oid, NULL) == OBJ_BLOB)) {
                if (check_object_signature(repl, NULL, 0, NULL) < 0) {
                        error("sha1 mismatch %s", oid_to_hex(oid));
                        return NULL;
@@ -481,6 +481,9 @@ void raw_object_store_clear(struct raw_object_store *o)
        FREE_AND_NULL(o->objectdir);
        FREE_AND_NULL(o->alternate_db);
 
+       oidmap_free(o->replace_map, 1);
+       FREE_AND_NULL(o->replace_map);
+
        free_alt_odbs(o);
        o->alt_odb_tail = NULL;
 
index b8e70e55190bb918a523af0e45fac626b0290fdc..5c13955000cdaec252f29a3b93599560c35deb8a 100644 (file)
--- a/object.h
+++ b/object.h
@@ -25,7 +25,6 @@ struct object_array {
 
 #define OBJECT_ARRAY_INIT { 0, 0, NULL }
 
-#define TYPE_BITS   3
 /*
  * object flag allocation:
  * revision.h:               0---------10                                26
index 9d1bb054bbbee6631bc2c9acae529da813548367..7b2dc3e7dc768a2d2405a9ae3b1d13f86069a93d 100644 (file)
@@ -48,7 +48,8 @@ void bitmap_writer_show_progress(int show)
 /**
  * Build the initial type index for the packfile
  */
-void bitmap_writer_build_type_index(struct pack_idx_entry **index,
+void bitmap_writer_build_type_index(struct packing_data *to_pack,
+                                   struct pack_idx_entry **index,
                                    uint32_t index_nr)
 {
        uint32_t i;
@@ -57,23 +58,25 @@ void bitmap_writer_build_type_index(struct pack_idx_entry **index,
        writer.trees = ewah_new();
        writer.blobs = ewah_new();
        writer.tags = ewah_new();
+       ALLOC_ARRAY(to_pack->in_pack_pos, to_pack->nr_objects);
 
        for (i = 0; i < index_nr; ++i) {
                struct object_entry *entry = (struct object_entry *)index[i];
                enum object_type real_type;
 
-               entry->in_pack_pos = i;
+               oe_set_in_pack_pos(to_pack, entry, i);
 
-               switch (entry->type) {
+               switch (oe_type(entry)) {
                case OBJ_COMMIT:
                case OBJ_TREE:
                case OBJ_BLOB:
                case OBJ_TAG:
-                       real_type = entry->type;
+                       real_type = oe_type(entry);
                        break;
 
                default:
-                       real_type = oid_object_info(&entry->idx.oid, NULL);
+                       real_type = oid_object_info(the_repository,
+                                                   &entry->idx.oid, NULL);
                        break;
                }
 
@@ -97,7 +100,7 @@ void bitmap_writer_build_type_index(struct pack_idx_entry **index,
                default:
                        die("Missing type information for %s (%d/%d)",
                            oid_to_hex(&entry->idx.oid), real_type,
-                           entry->type);
+                           oe_type(entry));
                }
        }
 }
@@ -146,7 +149,7 @@ static uint32_t find_object_pos(const unsigned char *sha1)
                        "(object %s is missing)", sha1_to_hex(sha1));
        }
 
-       return entry->in_pack_pos;
+       return oe_in_pack_pos(writer.to_pack, entry);
 }
 
 static void show_object(struct object *object, const char *name, void *data)
@@ -474,7 +477,7 @@ static void write_selected_commits_v1(struct hashfile *f,
                        sha1_pos(stored->commit->object.oid.hash, index, index_nr, sha1_access);
 
                if (commit_pos < 0)
-                       die("BUG: trying to write commit not in index");
+                       BUG("trying to write commit not in index");
 
                hashwrite_be32(f, commit_pos);
                hashwrite_u8(f, stored->xor_offset);
index 3f2dab340f6cc05cde659f97dacbd825d8266bc9..06771113fb311e4a75fa7a1e325221e4b60c850c 100644 (file)
@@ -255,7 +255,7 @@ static char *pack_bitmap_filename(struct packed_git *p)
        size_t len;
 
        if (!strip_suffix(p->pack_name, ".pack", &len))
-               die("BUG: pack_name does not end in .pack");
+               BUG("pack_name does not end in .pack");
        return xstrfmt("%.*s.bitmap", (int)len, p->pack_name);
 }
 
@@ -723,13 +723,13 @@ int prepare_bitmap_walk(struct rev_info *revs)
                revs->ignore_missing_links = 0;
 
                if (haves_bitmap == NULL)
-                       die("BUG: failed to perform bitmap walk");
+                       BUG("failed to perform bitmap walk");
        }
 
        wants_bitmap = find_objects(revs, wants, haves_bitmap);
 
        if (!wants_bitmap)
-               die("BUG: failed to perform bitmap walk");
+               BUG("failed to perform bitmap walk");
 
        if (haves_bitmap)
                bitmap_and_not(wants_bitmap, haves_bitmap);
@@ -1033,7 +1033,7 @@ int rebuild_existing_bitmaps(struct packing_data *mapping,
                oe = packlist_find(mapping, sha1, NULL);
 
                if (oe)
-                       reposition[i] = oe->in_pack_pos + 1;
+                       reposition[i] = oe_in_pack_pos(mapping, oe) + 1;
        }
 
        rebuild = bitmap_new();
index 3742a00e14a0d4da335253b2a76f978edb499d35..5ded2f139a6ccdab725ed5e568b1100906f4b736 100644 (file)
@@ -44,7 +44,9 @@ int rebuild_existing_bitmaps(struct packing_data *mapping, khash_sha1 *reused_bi
 
 void bitmap_writer_show_progress(int show);
 void bitmap_writer_set_checksum(unsigned char *sha1);
-void bitmap_writer_build_type_index(struct pack_idx_entry **index, uint32_t index_nr);
+void bitmap_writer_build_type_index(struct packing_data *to_pack,
+                                   struct pack_idx_entry **index,
+                                   uint32_t index_nr);
 void bitmap_writer_reuse_bitmaps(struct packing_data *to_pack);
 void bitmap_writer_select_commits(struct commit **indexed_commits,
                unsigned int indexed_commits_nr, int max_bitmaps);
index 385d964bdd1691b962250ec5a49abf2253ca75c1..d3a57df34f2d2bf0ef9935a51f171f24d8ed6a72 100644 (file)
@@ -1,4 +1,5 @@
 #include "cache.h"
+#include "repository.h"
 #include "pack.h"
 #include "pack-revindex.h"
 #include "progress.h"
@@ -134,7 +135,7 @@ static int verify_packfile(struct packed_git *p,
                        data = NULL;
                        data_valid = 0;
                } else {
-                       data = unpack_entry(p, entries[i].offset, &type, &size);
+                       data = unpack_entry(the_repository, p, entries[i].offset, &type, &size);
                        data_valid = 1;
                }
 
index 9558d13834e2842d32fa4015f2e3c00538d52dcd..92708522e76b4565882177f899612d72a3f6d75a 100644 (file)
@@ -2,6 +2,8 @@
 #include "object.h"
 #include "pack.h"
 #include "pack-objects.h"
+#include "packfile.h"
+#include "config.h"
 
 static uint32_t locate_object_entry_hash(struct packing_data *pdata,
                                         const unsigned char *sha1,
@@ -58,7 +60,7 @@ static void rehash_objects(struct packing_data *pdata)
                                                       &found);
 
                if (found)
-                       die("BUG: Duplicate object in hash");
+                       BUG("Duplicate object in hash");
 
                pdata->index[ix] = i + 1;
                entry++;
@@ -86,6 +88,66 @@ struct object_entry *packlist_find(struct packing_data *pdata,
        return &pdata->objects[pdata->index[i] - 1];
 }
 
+static void prepare_in_pack_by_idx(struct packing_data *pdata)
+{
+       struct packed_git **mapping, *p;
+       int cnt = 0, nr = 1U << OE_IN_PACK_BITS;
+
+       ALLOC_ARRAY(mapping, nr);
+       /*
+        * oe_in_pack() on an all-zero'd object_entry
+        * (i.e. in_pack_idx also zero) should return NULL.
+        */
+       mapping[cnt++] = NULL;
+       for (p = get_packed_git(the_repository); p; p = p->next, cnt++) {
+               if (cnt == nr) {
+                       free(mapping);
+                       return;
+               }
+               p->index = cnt;
+               mapping[cnt] = p;
+       }
+       pdata->in_pack_by_idx = mapping;
+}
+
+/*
+ * A new pack appears after prepare_in_pack_by_idx() has been
+ * run. This is likely a race.
+ *
+ * We could map this new pack to in_pack_by_idx[] array, but then we
+ * have to deal with full array anyway. And since it's hard to test
+ * this fall back code, just stay simple and fall back to using
+ * in_pack[] array.
+ */
+void oe_map_new_pack(struct packing_data *pack,
+                    struct packed_git *p)
+{
+       uint32_t i;
+
+       REALLOC_ARRAY(pack->in_pack, pack->nr_alloc);
+
+       for (i = 0; i < pack->nr_objects; i++)
+               pack->in_pack[i] = oe_in_pack(pack, pack->objects + i);
+
+       FREE_AND_NULL(pack->in_pack_by_idx);
+}
+
+/* assume pdata is already zero'd by caller */
+void prepare_packing_data(struct packing_data *pdata)
+{
+       if (git_env_bool("GIT_TEST_FULL_IN_PACK_ARRAY", 0)) {
+               /*
+                * do not initialize in_pack_by_idx[] to force the
+                * slow path in oe_in_pack()
+                */
+       } else {
+               prepare_in_pack_by_idx(pdata);
+       }
+
+       pdata->oe_size_limit = git_env_ulong("GIT_TEST_OE_SIZE",
+                                            1U << OE_SIZE_BITS);
+}
+
 struct object_entry *packlist_alloc(struct packing_data *pdata,
                                    const unsigned char *sha1,
                                    uint32_t index_pos)
@@ -95,6 +157,9 @@ struct object_entry *packlist_alloc(struct packing_data *pdata,
        if (pdata->nr_objects >= pdata->nr_alloc) {
                pdata->nr_alloc = (pdata->nr_alloc  + 1024) * 3 / 2;
                REALLOC_ARRAY(pdata->objects, pdata->nr_alloc);
+
+               if (!pdata->in_pack_by_idx)
+                       REALLOC_ARRAY(pdata->in_pack, pdata->nr_alloc);
        }
 
        new_entry = pdata->objects + pdata->nr_objects++;
@@ -107,5 +172,8 @@ struct object_entry *packlist_alloc(struct packing_data *pdata,
        else
                pdata->index[index_pos] = pdata->nr_objects;
 
+       if (pdata->in_pack)
+               pdata->in_pack[pdata->nr_objects - 1] = NULL;
+
        return new_entry;
 }
index 03f1191659dab55b2c4c440c347101a3cdbd4650..edf74dabddfdb2b67bad803d1c898e93a3af4d8b 100644 (file)
 #ifndef PACK_OBJECTS_H
 #define PACK_OBJECTS_H
 
+#include "object-store.h"
+
+#define DEFAULT_DELTA_CACHE_SIZE (256 * 1024 * 1024)
+
+#define OE_DFS_STATE_BITS      2
+#define OE_DEPTH_BITS          12
+#define OE_IN_PACK_BITS                10
+#define OE_Z_DELTA_BITS                20
+/*
+ * Note that oe_set_size() becomes expensive when the given size is
+ * above this limit. Don't lower it too much.
+ */
+#define OE_SIZE_BITS           31
+#define OE_DELTA_SIZE_BITS     20
+
+/*
+ * State flags for depth-first search used for analyzing delta cycles.
+ *
+ * The depth is measured in delta-links to the base (so if A is a delta
+ * against B, then A has a depth of 1, and B a depth of 0).
+ */
+enum dfs_state {
+       DFS_NONE = 0,
+       DFS_ACTIVE,
+       DFS_DONE,
+       DFS_NUM_STATES
+};
+
+/*
+ * The size of struct nearly determines pack-objects's memory
+ * consumption. This struct is packed tight for that reason. When you
+ * add or reorder something in this struct, think a bit about this.
+ *
+ * basic object info
+ * -----------------
+ * idx.oid is filled up before delta searching starts. idx.crc32 is
+ * only valid after the object is written out and will be used for
+ * generating the index. idx.offset will be both gradually set and
+ * used in writing phase (base objects get offset first, then deltas
+ * refer to them)
+ *
+ * "size" is the uncompressed object size. Compressed size of the raw
+ * data for an object in a pack is not stored anywhere but is computed
+ * and made available when reverse .idx is made. Note that when a
+ * delta is reused, "size" is the uncompressed _delta_ size, not the
+ * canonical one after the delta has been applied.
+ *
+ * "hash" contains a path name hash which is used for sorting the
+ * delta list and also during delta searching. Once prepare_pack()
+ * returns it's no longer needed.
+ *
+ * source pack info
+ * ----------------
+ * The (in_pack, in_pack_offset) tuple contains the location of the
+ * object in the source pack. in_pack_header_size allows quickly
+ * skipping the header and going straight to the zlib stream.
+ *
+ * "type" and "in_pack_type" both describe object type. in_pack_type
+ * may contain a delta type, while type is always the canonical type.
+ *
+ * deltas
+ * ------
+ * Delta links (delta, delta_child and delta_sibling) are created to
+ * reflect that delta graph from the source pack then updated or added
+ * during delta searching phase when we find better deltas.
+ *
+ * delta_child and delta_sibling are last needed in
+ * compute_write_order(). "delta" and "delta_size" must remain valid
+ * at object writing phase in case the delta is not cached.
+ *
+ * If a delta is cached in memory and is compressed, delta_data points
+ * to the data and z_delta_size contains the compressed size. If it's
+ * uncompressed [1], z_delta_size must be zero. delta_size is always
+ * the uncompressed size and must be valid even if the delta is not
+ * cached.
+ *
+ * [1] during try_delta phase we don't bother with compressing because
+ * the delta could be quickly replaced with a better one.
+ */
 struct object_entry {
        struct pack_idx_entry idx;
-       unsigned long size;     /* uncompressed size */
-       struct packed_git *in_pack;     /* already in pack */
-       off_t in_pack_offset;
-       struct object_entry *delta;     /* delta base object */
-       struct object_entry *delta_child; /* deltified objects who bases me */
-       struct object_entry *delta_sibling; /* other deltified objects who
-                                            * uses the same base as me
-                                            */
        void *delta_data;       /* cached delta (uncompressed) */
-       unsigned long delta_size;       /* delta data size (uncompressed) */
-       unsigned long z_delta_size;     /* delta data size (compressed) */
-       enum object_type type;
-       enum object_type in_pack_type;  /* could be delta */
+       off_t in_pack_offset;
        uint32_t hash;                  /* name hint hash */
-       unsigned int in_pack_pos;
-       unsigned char in_pack_header_size;
+       unsigned size_:OE_SIZE_BITS;
+       unsigned size_valid:1;
+       uint32_t delta_idx;     /* delta base object */
+       uint32_t delta_child_idx; /* deltified objects who bases me */
+       uint32_t delta_sibling_idx; /* other deltified objects who
+                                    * uses the same base as me
+                                    */
+       unsigned delta_size_:OE_DELTA_SIZE_BITS; /* delta data size (uncompressed) */
+       unsigned delta_size_valid:1;
+       unsigned in_pack_idx:OE_IN_PACK_BITS;   /* already in pack */
+       unsigned z_delta_size:OE_Z_DELTA_BITS;
+       unsigned type_valid:1;
+       unsigned type_:TYPE_BITS;
+       unsigned no_try_delta:1;
+       unsigned in_pack_type:TYPE_BITS; /* could be delta */
        unsigned preferred_base:1; /*
                                    * we do not pack this, but is available
                                    * to be used as the base object to delta
                                    * objects against.
                                    */
-       unsigned no_try_delta:1;
        unsigned tagged:1; /* near the very tip of refs */
        unsigned filled:1; /* assigned write-order */
+       unsigned dfs_state:OE_DFS_STATE_BITS;
+       unsigned char in_pack_header_size;
+       unsigned depth:OE_DEPTH_BITS;
 
        /*
-        * State flags for depth-first search used for analyzing delta cycles.
+        * pahole results on 64-bit linux (gcc and clang)
+        *
+        *   size: 80, bit_padding: 20 bits, holes: 8 bits
+        *
+        * and on 32-bit (gcc)
         *
-        * The depth is measured in delta-links to the base (so if A is a delta
-        * against B, then A has a depth of 1, and B a depth of 0).
+        *   size: 76, bit_padding: 20 bits, holes: 8 bits
         */
-       enum {
-               DFS_NONE = 0,
-               DFS_ACTIVE,
-               DFS_DONE
-       } dfs_state;
-       int depth;
 };
 
 struct packing_data {
@@ -48,8 +128,22 @@ struct packing_data {
 
        int32_t *index;
        uint32_t index_size;
+
+       unsigned int *in_pack_pos;
+
+       /*
+        * Only one of these can be non-NULL and they have different
+        * sizes. if in_pack_by_idx is allocated, oe_in_pack() returns
+        * the pack of an object using in_pack_idx field. If not,
+        * in_pack[] array is used the same way as in_pack_pos[]
+        */
+       struct packed_git **in_pack_by_idx;
+       struct packed_git **in_pack;
+
+       uintmax_t oe_size_limit;
 };
 
+void prepare_packing_data(struct packing_data *pdata);
 struct object_entry *packlist_alloc(struct packing_data *pdata,
                                    const unsigned char *sha1,
                                    uint32_t index_pos);
@@ -78,4 +172,178 @@ static inline uint32_t pack_name_hash(const char *name)
        return hash;
 }
 
+static inline enum object_type oe_type(const struct object_entry *e)
+{
+       return e->type_valid ? e->type_ : OBJ_BAD;
+}
+
+static inline void oe_set_type(struct object_entry *e,
+                              enum object_type type)
+{
+       if (type >= OBJ_ANY)
+               BUG("OBJ_ANY cannot be set in pack-objects code");
+
+       e->type_valid = type >= OBJ_NONE;
+       e->type_ = (unsigned)type;
+}
+
+static inline unsigned int oe_in_pack_pos(const struct packing_data *pack,
+                                         const struct object_entry *e)
+{
+       return pack->in_pack_pos[e - pack->objects];
+}
+
+static inline void oe_set_in_pack_pos(const struct packing_data *pack,
+                                     const struct object_entry *e,
+                                     unsigned int pos)
+{
+       pack->in_pack_pos[e - pack->objects] = pos;
+}
+
+static inline struct packed_git *oe_in_pack(const struct packing_data *pack,
+                                           const struct object_entry *e)
+{
+       if (pack->in_pack_by_idx)
+               return pack->in_pack_by_idx[e->in_pack_idx];
+       else
+               return pack->in_pack[e - pack->objects];
+}
+
+void oe_map_new_pack(struct packing_data *pack,
+                    struct packed_git *p);
+static inline void oe_set_in_pack(struct packing_data *pack,
+                                 struct object_entry *e,
+                                 struct packed_git *p)
+{
+       if (!p->index)
+               oe_map_new_pack(pack, p);
+       if (pack->in_pack_by_idx)
+               e->in_pack_idx = p->index;
+       else
+               pack->in_pack[e - pack->objects] = p;
+}
+
+static inline struct object_entry *oe_delta(
+               const struct packing_data *pack,
+               const struct object_entry *e)
+{
+       if (e->delta_idx)
+               return &pack->objects[e->delta_idx - 1];
+       return NULL;
+}
+
+static inline void oe_set_delta(struct packing_data *pack,
+                               struct object_entry *e,
+                               struct object_entry *delta)
+{
+       if (delta)
+               e->delta_idx = (delta - pack->objects) + 1;
+       else
+               e->delta_idx = 0;
+}
+
+static inline struct object_entry *oe_delta_child(
+               const struct packing_data *pack,
+               const struct object_entry *e)
+{
+       if (e->delta_child_idx)
+               return &pack->objects[e->delta_child_idx - 1];
+       return NULL;
+}
+
+static inline void oe_set_delta_child(struct packing_data *pack,
+                                     struct object_entry *e,
+                                     struct object_entry *delta)
+{
+       if (delta)
+               e->delta_child_idx = (delta - pack->objects) + 1;
+       else
+               e->delta_child_idx = 0;
+}
+
+static inline struct object_entry *oe_delta_sibling(
+               const struct packing_data *pack,
+               const struct object_entry *e)
+{
+       if (e->delta_sibling_idx)
+               return &pack->objects[e->delta_sibling_idx - 1];
+       return NULL;
+}
+
+static inline void oe_set_delta_sibling(struct packing_data *pack,
+                                       struct object_entry *e,
+                                       struct object_entry *delta)
+{
+       if (delta)
+               e->delta_sibling_idx = (delta - pack->objects) + 1;
+       else
+               e->delta_sibling_idx = 0;
+}
+
+unsigned long oe_get_size_slow(struct packing_data *pack,
+                              const struct object_entry *e);
+static inline unsigned long oe_size(struct packing_data *pack,
+                                   const struct object_entry *e)
+{
+       if (e->size_valid)
+               return e->size_;
+
+       return oe_get_size_slow(pack, e);
+}
+
+static inline int oe_size_less_than(struct packing_data *pack,
+                                   const struct object_entry *lhs,
+                                   unsigned long rhs)
+{
+       if (lhs->size_valid)
+               return lhs->size_ < rhs;
+       if (rhs < pack->oe_size_limit) /* rhs < 2^x <= lhs ? */
+               return 0;
+       return oe_get_size_slow(pack, lhs) < rhs;
+}
+
+static inline int oe_size_greater_than(struct packing_data *pack,
+                                      const struct object_entry *lhs,
+                                      unsigned long rhs)
+{
+       if (lhs->size_valid)
+               return lhs->size_ > rhs;
+       if (rhs < pack->oe_size_limit) /* rhs < 2^x <= lhs ? */
+               return 1;
+       return oe_get_size_slow(pack, lhs) > rhs;
+}
+
+static inline void oe_set_size(struct packing_data *pack,
+                              struct object_entry *e,
+                              unsigned long size)
+{
+       if (size < pack->oe_size_limit) {
+               e->size_ = size;
+               e->size_valid = 1;
+       } else {
+               e->size_valid = 0;
+               if (oe_get_size_slow(pack, e) != size)
+                       BUG("'size' is supposed to be the object size!");
+       }
+}
+
+static inline unsigned long oe_delta_size(struct packing_data *pack,
+                                         const struct object_entry *e)
+{
+       if (e->delta_size_valid)
+               return e->delta_size_;
+       return oe_size(pack, e);
+}
+
+static inline void oe_set_delta_size(struct packing_data *pack,
+                                    struct object_entry *e,
+                                    unsigned long size)
+{
+       e->delta_size_ = size;
+       e->delta_size_valid = e->delta_size_ == size;
+       if (!e->delta_size_valid && size != oe_size(pack, e))
+               BUG("this can only happen in check_object() "
+                   "where delta size is the same as entry size");
+}
+
 #endif
index 6c3ddc3c31d008eb3817f21512815d89e37c4e1f..1a714fbde994f8b4a7bde68f1fcad1725970fe9b 100644 (file)
@@ -84,6 +84,7 @@ static int check_packed_git_idx(const char *path, struct packed_git *p)
        uint32_t version, nr, i, *index;
        int fd = git_open(path);
        struct stat st;
+       const unsigned int hashsz = the_hash_algo->rawsz;
 
        if (fd < 0)
                return -1;
@@ -92,7 +93,7 @@ static int check_packed_git_idx(const char *path, struct packed_git *p)
                return -1;
        }
        idx_size = xsize_t(st.st_size);
-       if (idx_size < 4 * 256 + 20 + 20) {
+       if (idx_size < 4 * 256 + hashsz + hashsz) {
                close(fd);
                return error("index file %s is too small", path);
        }
@@ -129,11 +130,11 @@ static int check_packed_git_idx(const char *path, struct packed_git *p)
                /*
                 * Total size:
                 *  - 256 index entries 4 bytes each
-                *  - 24-byte entries * nr (20-byte sha1 + 4-byte offset)
-                *  - 20-byte SHA1 of the packfile
-                *  - 20-byte SHA1 file checksum
+                *  - 24-byte entries * nr (object ID + 4-byte offset)
+                *  - hash of the packfile
+                *  - file checksum
                 */
-               if (idx_size != 4*256 + nr * 24 + 20 + 20) {
+               if (idx_size != 4*256 + nr * (hashsz + 4) + hashsz + hashsz) {
                        munmap(idx_map, idx_size);
                        return error("wrong index v1 file size in %s", path);
                }
@@ -142,16 +143,16 @@ static int check_packed_git_idx(const char *path, struct packed_git *p)
                 * Minimum size:
                 *  - 8 bytes of header
                 *  - 256 index entries 4 bytes each
-                *  - 20-byte sha1 entry * nr
+                *  - object ID entry * nr
                 *  - 4-byte crc entry * nr
                 *  - 4-byte offset entry * nr
-                *  - 20-byte SHA1 of the packfile
-                *  - 20-byte SHA1 file checksum
+                *  - hash of the packfile
+                *  - file checksum
                 * And after the 4-byte offset table might be a
                 * variable sized table containing 8-byte entries
                 * for offsets larger than 2^31.
                 */
-               unsigned long min_size = 8 + 4*256 + nr*(20 + 4 + 4) + 20 + 20;
+               unsigned long min_size = 8 + 4*256 + nr*(hashsz + 4 + 4) + hashsz + hashsz;
                unsigned long max_size = min_size;
                if (nr)
                        max_size += (nr - 1)*8;
@@ -188,7 +189,7 @@ int open_pack_index(struct packed_git *p)
                return 0;
 
        if (!strip_suffix(p->pack_name, ".pack", &len))
-               die("BUG: pack_name does not end in .pack");
+               BUG("pack_name does not end in .pack");
        idx_name = xstrfmt("%.*s.idx", (int)len, p->pack_name);
        ret = check_packed_git_idx(idx_name, p);
        free(idx_name);
@@ -317,7 +318,7 @@ void close_all_packs(struct raw_object_store *o)
 
        for (p = o->packed_git; p; p = p->next)
                if (p->do_not_close)
-                       die("BUG: want to close pack marked 'do-not-close'");
+                       BUG("want to close pack marked 'do-not-close'");
                else
                        close_pack(p);
 }
@@ -444,10 +445,11 @@ static int open_packed_git_1(struct packed_git *p)
 {
        struct stat st;
        struct pack_header hdr;
-       unsigned char sha1[20];
-       unsigned char *idx_sha1;
+       unsigned char hash[GIT_MAX_RAWSZ];
+       unsigned char *idx_hash;
        long fd_flag;
        ssize_t read_result;
+       const unsigned hashsz = the_hash_algo->rawsz;
 
        if (!p->index_data && open_pack_index(p))
                return error("packfile %s index unavailable", p->pack_name);
@@ -507,15 +509,15 @@ static int open_packed_git_1(struct packed_git *p)
                             " while index indicates %"PRIu32" objects",
                             p->pack_name, ntohl(hdr.hdr_entries),
                             p->num_objects);
-       if (lseek(p->pack_fd, p->pack_size - sizeof(sha1), SEEK_SET) == -1)
+       if (lseek(p->pack_fd, p->pack_size - hashsz, SEEK_SET) == -1)
                return error("end of packfile %s is unavailable", p->pack_name);
-       read_result = read_in_full(p->pack_fd, sha1, sizeof(sha1));
+       read_result = read_in_full(p->pack_fd, hash, hashsz);
        if (read_result < 0)
                return error_errno("error reading from %s", p->pack_name);
-       if (read_result != sizeof(sha1))
+       if (read_result != hashsz)
                return error("packfile %s signature is unavailable", p->pack_name);
-       idx_sha1 = ((unsigned char *)p->index_data) + p->index_size - 40;
-       if (hashcmp(sha1, idx_sha1))
+       idx_hash = ((unsigned char *)p->index_data) + p->index_size - hashsz * 2;
+       if (hashcmp(hash, idx_hash))
                return error("packfile %s does not match index", p->pack_name);
        return 0;
 }
@@ -530,7 +532,7 @@ static int open_packed_git(struct packed_git *p)
 
 static int in_window(struct pack_window *win, off_t offset)
 {
-       /* We must promise at least 20 bytes (one hash) after the
+       /* We must promise at least one full hash after the
         * offset is available from this window, otherwise the offset
         * is not actually in this window and a different window (which
         * has that one hash excess) must be used.  This is to support
@@ -538,7 +540,7 @@ static int in_window(struct pack_window *win, off_t offset)
         */
        off_t win_off = win->offset;
        return win_off <= offset
-               && (offset + 20) <= (win_off + win->len);
+               && (offset + the_hash_algo->rawsz) <= (win_off + win->len);
 }
 
 unsigned char *use_pack(struct packed_git *p,
@@ -555,7 +557,7 @@ unsigned char *use_pack(struct packed_git *p,
         */
        if (!p->pack_size && p->pack_fd == -1 && open_packed_git(p))
                die("packfile %s cannot be accessed", p->pack_name);
-       if (offset > (p->pack_size - 20))
+       if (offset > (p->pack_size - the_hash_algo->rawsz))
                die("offset beyond end of packfile (truncated pack?)");
        if (offset < 0)
                die(_("offset before end of packfile (broken .idx?)"));
@@ -675,7 +677,8 @@ struct packed_git *add_packed_git(const char *path, size_t path_len, int local)
        p->pack_size = st.st_size;
        p->pack_local = local;
        p->mtime = st.st_mtime;
-       if (path_len < 40 || get_sha1_hex(path + path_len - 40, p->sha1))
+       if (path_len < the_hash_algo->hexsz ||
+           get_sha1_hex(path + path_len - the_hash_algo->hexsz, p->sha1))
                hashclr(p->sha1);
        return p;
 }
@@ -1028,7 +1031,8 @@ const struct packed_git *has_packed_and_bad(const unsigned char *sha1)
 
        for (p = the_repository->objects->packed_git; p; p = p->next)
                for (i = 0; i < p->num_bad_objects; i++)
-                       if (!hashcmp(sha1, p->bad_object_sha1 + 20 * i))
+                       if (!hashcmp(sha1,
+                                    p->bad_object_sha1 + the_hash_algo->rawsz * i))
                                return p;
        return NULL;
 }
@@ -1066,7 +1070,7 @@ static off_t get_delta_base(struct packed_git *p,
        } else if (type == OBJ_REF_DELTA) {
                /* The base entry _must_ be in the same pack */
                base_offset = find_pack_entry_one(base_info, p);
-               *curpos += 20;
+               *curpos += the_hash_algo->rawsz;
        } else
                die("I am totally screwed");
        return base_offset;
@@ -1104,7 +1108,9 @@ static const unsigned char *get_delta_base_sha1(struct packed_git *p,
                return NULL;
 }
 
-static int retry_bad_packed_offset(struct packed_git *p, off_t obj_offset)
+static int retry_bad_packed_offset(struct repository *r,
+                                  struct packed_git *p,
+                                  off_t obj_offset)
 {
        int type;
        struct revindex_entry *revidx;
@@ -1114,7 +1120,7 @@ static int retry_bad_packed_offset(struct packed_git *p, off_t obj_offset)
                return OBJ_BAD;
        nth_packed_object_oid(&oid, p, revidx->nr);
        mark_bad_packed_object(p, oid.hash);
-       type = oid_object_info(&oid, NULL);
+       type = oid_object_info(r, &oid, NULL);
        if (type <= OBJ_NONE)
                return OBJ_BAD;
        return type;
@@ -1122,7 +1128,8 @@ static int retry_bad_packed_offset(struct packed_git *p, off_t obj_offset)
 
 #define POI_STACK_PREALLOC 64
 
-static enum object_type packed_to_object_type(struct packed_git *p,
+static enum object_type packed_to_object_type(struct repository *r,
+                                             struct packed_git *p,
                                              off_t obj_offset,
                                              enum object_type type,
                                              struct pack_window **w_curs,
@@ -1153,7 +1160,7 @@ static enum object_type packed_to_object_type(struct packed_git *p,
                if (type <= OBJ_NONE) {
                        /* If getting the base itself fails, we first
                         * retry the base, otherwise unwind */
-                       type = retry_bad_packed_offset(p, base_offset);
+                       type = retry_bad_packed_offset(r, p, base_offset);
                        if (type > OBJ_NONE)
                                goto out;
                        goto unwind;
@@ -1181,7 +1188,7 @@ static enum object_type packed_to_object_type(struct packed_git *p,
 unwind:
        while (poi_stack_nr) {
                obj_offset = poi_stack[--poi_stack_nr];
-               type = retry_bad_packed_offset(p, obj_offset);
+               type = retry_bad_packed_offset(r, p, obj_offset);
                if (type > OBJ_NONE)
                        goto out;
        }
@@ -1268,14 +1275,15 @@ static void detach_delta_base_cache_entry(struct delta_base_cache_entry *ent)
        free(ent);
 }
 
-static void *cache_or_unpack_entry(struct packed_git *p, off_t base_offset,
-       unsigned long *base_size, enum object_type *type)
+static void *cache_or_unpack_entry(struct repository *r, struct packed_git *p,
+                                  off_t base_offset, unsigned long *base_size,
+                                  enum object_type *type)
 {
        struct delta_base_cache_entry *ent;
 
        ent = get_delta_base_cache_entry(p, base_offset);
        if (!ent)
-               return unpack_entry(p, base_offset, type, base_size);
+               return unpack_entry(r, p, base_offset, type, base_size);
 
        if (type)
                *type = ent->type;
@@ -1329,8 +1337,8 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset,
        hashmap_add(&delta_base_cache, ent);
 }
 
-int packed_object_info(struct packed_git *p, off_t obj_offset,
-                      struct object_info *oi)
+int packed_object_info(struct repository *r, struct packed_git *p,
+                      off_t obj_offset, struct object_info *oi)
 {
        struct pack_window *w_curs = NULL;
        unsigned long size;
@@ -1342,7 +1350,7 @@ int packed_object_info(struct packed_git *p, off_t obj_offset,
         * a "real" type later if the caller is interested.
         */
        if (oi->contentp) {
-               *oi->contentp = cache_or_unpack_entry(p, obj_offset, oi->sizep,
+               *oi->contentp = cache_or_unpack_entry(r, p, obj_offset, oi->sizep,
                                                      &type);
                if (!*oi->contentp)
                        type = OBJ_BAD;
@@ -1376,8 +1384,8 @@ int packed_object_info(struct packed_git *p, off_t obj_offset,
 
        if (oi->typep || oi->type_name) {
                enum object_type ptot;
-               ptot = packed_to_object_type(p, obj_offset, type, &w_curs,
-                                            curpos);
+               ptot = packed_to_object_type(r, p, obj_offset,
+                                            type, &w_curs, curpos);
                if (oi->typep)
                        *oi->typep = ptot;
                if (oi->type_name) {
@@ -1465,7 +1473,9 @@ struct unpack_entry_stack_ent {
        unsigned long size;
 };
 
-static void *read_object(const struct object_id *oid, enum object_type *type,
+static void *read_object(struct repository *r,
+                        const struct object_id *oid,
+                        enum object_type *type,
                         unsigned long *size)
 {
        struct object_info oi = OBJECT_INFO_INIT;
@@ -1474,12 +1484,12 @@ static void *read_object(const struct object_id *oid, enum object_type *type,
        oi.sizep = size;
        oi.contentp = &content;
 
-       if (oid_object_info_extended(oid, &oi, 0) < 0)
+       if (oid_object_info_extended(r, oid, &oi, 0) < 0)
                return NULL;
        return content;
 }
 
-void *unpack_entry(struct packed_git *p, off_t obj_offset,
+void *unpack_entry(struct repository *r, struct packed_git *p, off_t obj_offset,
                   enum object_type *final_type, unsigned long *final_size)
 {
        struct pack_window *w_curs = NULL;
@@ -1561,7 +1571,7 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset,
        case OBJ_OFS_DELTA:
        case OBJ_REF_DELTA:
                if (data)
-                       die("BUG: unpack_entry: left loop at a valid delta");
+                       BUG("unpack_entry: left loop at a valid delta");
                break;
        case OBJ_COMMIT:
        case OBJ_TREE:
@@ -1610,7 +1620,7 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset,
                                      oid_to_hex(&base_oid), (uintmax_t)obj_offset,
                                      p->pack_name);
                                mark_bad_packed_object(p, base_oid.hash);
-                               base = read_object(&base_oid, &type, &base_size);
+                               base = read_object(r, &base_oid, &type, &base_size);
                                external_base = base;
                        }
                }
@@ -1671,6 +1681,7 @@ int bsearch_pack(const struct object_id *oid, const struct packed_git *p, uint32
 {
        const unsigned char *index_fanout = p->index_data;
        const unsigned char *index_lookup;
+       const unsigned int hashsz = the_hash_algo->rawsz;
        int index_lookup_width;
 
        if (!index_fanout)
@@ -1678,10 +1689,10 @@ int bsearch_pack(const struct object_id *oid, const struct packed_git *p, uint32
 
        index_lookup = index_fanout + 4 * 256;
        if (p->index_version == 1) {
-               index_lookup_width = 24;
+               index_lookup_width = hashsz + 4;
                index_lookup += 4;
        } else {
-               index_lookup_width = 20;
+               index_lookup_width = hashsz;
                index_fanout += 8;
                index_lookup += 8;
        }
@@ -1694,6 +1705,7 @@ const unsigned char *nth_packed_object_sha1(struct packed_git *p,
                                            uint32_t n)
 {
        const unsigned char *index = p->index_data;
+       const unsigned int hashsz = the_hash_algo->rawsz;
        if (!index) {
                if (open_pack_index(p))
                        return NULL;
@@ -1703,10 +1715,10 @@ const unsigned char *nth_packed_object_sha1(struct packed_git *p,
                return NULL;
        index += 4 * 256;
        if (p->index_version == 1) {
-               return index + 24 * n + 4;
+               return index + (hashsz + 4) * n + 4;
        } else {
                index += 8;
-               return index + 20 * n;
+               return index + hashsz * n;
        }
 }
 
@@ -1738,12 +1750,13 @@ void check_pack_index_ptr(const struct packed_git *p, const void *vptr)
 off_t nth_packed_object_offset(const struct packed_git *p, uint32_t n)
 {
        const unsigned char *index = p->index_data;
+       const unsigned int hashsz = the_hash_algo->rawsz;
        index += 4 * 256;
        if (p->index_version == 1) {
-               return ntohl(*((uint32_t *)(index + 24 * n)));
+               return ntohl(*((uint32_t *)(index + (hashsz + 4) * n)));
        } else {
                uint32_t off;
-               index += 8 + p->num_objects * (20 + 4);
+               index += 8 + p->num_objects * (hashsz + 4);
                off = ntohl(*((uint32_t *)(index + 4 * n)));
                if (!(off & 0x80000000))
                        return off;
@@ -1805,7 +1818,7 @@ struct packed_git *find_sha1_pack(const unsigned char *sha1,
 
 }
 
-static int fill_pack_entry(const unsigned char *sha1,
+static int fill_pack_entry(const struct object_id *oid,
                           struct pack_entry *e,
                           struct packed_git *p)
 {
@@ -1814,11 +1827,12 @@ static int fill_pack_entry(const unsigned char *sha1,
        if (p->num_bad_objects) {
                unsigned i;
                for (i = 0; i < p->num_bad_objects; i++)
-                       if (!hashcmp(sha1, p->bad_object_sha1 + 20 * i))
+                       if (!hashcmp(oid->hash,
+                                    p->bad_object_sha1 + the_hash_algo->rawsz * i))
                                return 0;
        }
 
-       offset = find_pack_entry_one(sha1, p);
+       offset = find_pack_entry_one(oid->hash, p);
        if (!offset)
                return 0;
 
@@ -1833,11 +1847,10 @@ static int fill_pack_entry(const unsigned char *sha1,
                return 0;
        e->offset = offset;
        e->p = p;
-       hashcpy(e->sha1, sha1);
        return 1;
 }
 
-int find_pack_entry(struct repository *r, const unsigned char *sha1, struct pack_entry *e)
+int find_pack_entry(struct repository *r, const struct object_id *oid, struct pack_entry *e)
 {
        struct list_head *pos;
 
@@ -1847,7 +1860,7 @@ int find_pack_entry(struct repository *r, const unsigned char *sha1, struct pack
 
        list_for_each(pos, &r->objects->packed_git_mru) {
                struct packed_git *p = list_entry(pos, struct packed_git, mru);
-               if (fill_pack_entry(sha1, e, p)) {
+               if (fill_pack_entry(oid, e, p)) {
                        list_move(&p->mru, &r->objects->packed_git_mru);
                        return 1;
                }
@@ -1855,10 +1868,10 @@ int find_pack_entry(struct repository *r, const unsigned char *sha1, struct pack
        return 0;
 }
 
-int has_sha1_pack(const unsigned char *sha1)
+int has_object_pack(const struct object_id *oid)
 {
        struct pack_entry e;
-       return find_pack_entry(the_repository, sha1, &e);
+       return find_pack_entry(the_repository, oid, &e);
 }
 
 int has_pack_index(const unsigned char *sha1)
@@ -1944,7 +1957,7 @@ static int add_promisor_object(const struct object_id *oid,
                struct commit *commit = (struct commit *) obj;
                struct commit_list *parents = commit->parents;
 
-               oidset_insert(set, &commit->tree->object.oid);
+               oidset_insert(set, get_commit_tree_oid(commit));
                for (; parents; parents = parents->next)
                        oidset_insert(set, &parents->item->object.oid);
        } else if (obj->type == OBJ_TAG) {
index 9c2f885994546df4271e6a57d253cdae820b025c..e0a38aba9321deae1d64aea7cd87067fde240b10 100644 (file)
@@ -116,7 +116,7 @@ extern off_t nth_packed_object_offset(const struct packed_git *, uint32_t n);
 extern off_t find_pack_entry_one(const unsigned char *sha1, struct packed_git *);
 
 extern int is_pack_valid(struct packed_git *);
-extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsigned long *);
+extern void *unpack_entry(struct repository *r, struct packed_git *, off_t, enum object_type *, unsigned long *);
 extern unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep);
 extern unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t);
 extern int unpack_object_header(struct packed_git *, struct pack_window **, off_t *, unsigned long *);
@@ -126,7 +126,9 @@ extern void release_pack_memory(size_t);
 /* global flag to enable extra checks when accessing packed objects */
 extern int do_check_packed_object_crc;
 
-extern int packed_object_info(struct packed_git *pack, off_t offset, struct object_info *);
+extern int packed_object_info(struct repository *r,
+                             struct packed_git *pack,
+                             off_t offset, struct object_info *);
 
 extern void mark_bad_packed_object(struct packed_git *p, const unsigned char *sha1);
 extern const struct packed_git *has_packed_and_bad(const unsigned char *sha1);
@@ -135,9 +137,9 @@ extern const struct packed_git *has_packed_and_bad(const unsigned char *sha1);
  * Iff a pack file in the given repository contains the object named by sha1,
  * return true and store its location to e.
  */
-extern int find_pack_entry(struct repository *r, const unsigned char *sha1, struct pack_entry *e);
+extern int find_pack_entry(struct repository *r, const struct object_id *oid, struct pack_entry *e);
 
-extern int has_sha1_pack(const unsigned char *sha1);
+extern int has_object_pack(const struct object_id *oid);
 
 extern int has_pack_index(const unsigned char *sha1);
 
diff --git a/pager.c b/pager.c
index 92b23e6cd1d44a26c86afeeb748ddc0aee3f9154..a768797fcfcc44de4dbe4d983a45ade8c81b2e1c 100644 (file)
--- a/pager.c
+++ b/pager.c
@@ -2,6 +2,7 @@
 #include "config.h"
 #include "run-command.h"
 #include "sigchain.h"
+#include "alias.h"
 
 #ifndef DEFAULT_PAGER
 #define DEFAULT_PAGER "less"
@@ -109,10 +110,15 @@ void setup_pager(void)
                return;
 
        /*
-        * force computing the width of the terminal before we redirect
-        * the standard output to the pager.
+        * After we redirect standard output, we won't be able to use an ioctl
+        * to get the terminal size. Let's grab it now, and then set $COLUMNS
+        * to communicate it to any sub-processes.
         */
-       (void) term_columns();
+       {
+               char buf[64];
+               xsnprintf(buf, sizeof(buf), "%d", term_columns());
+               setenv("COLUMNS", buf, 0);
+       }
 
        setenv("GIT_PAGER_IN_USE", "true", 1);
 
diff --git a/path.c b/path.c
index 3308b7b95828821fdda6806d9d19228cbf8e61ed..7f109f661816039768e4d8050bdb3c12384f9f85 100644 (file)
--- a/path.c
+++ b/path.c
@@ -1306,7 +1306,7 @@ static int only_spaces_and_periods(const char *path, size_t len, size_t skip)
 
 int is_ntfs_dotgit(const char *name)
 {
-       int len;
+       size_t len;
 
        for (len = 0; ; len++)
                if (!name[len] || name[len] == '\\' || is_dir_sep(name[len])) {
@@ -1323,6 +1323,90 @@ int is_ntfs_dotgit(const char *name)
                }
 }
 
+static int is_ntfs_dot_generic(const char *name,
+                              const char *dotgit_name,
+                              size_t len,
+                              const char *dotgit_ntfs_shortname_prefix)
+{
+       int saw_tilde;
+       size_t i;
+
+       if ((name[0] == '.' && !strncasecmp(name + 1, dotgit_name, len))) {
+               i = len + 1;
+only_spaces_and_periods:
+               for (;;) {
+                       char c = name[i++];
+                       if (!c)
+                               return 1;
+                       if (c != ' ' && c != '.')
+                               return 0;
+               }
+       }
+
+       /*
+        * Is it a regular NTFS short name, i.e. shortened to 6 characters,
+        * followed by ~1, ... ~4?
+        */
+       if (!strncasecmp(name, dotgit_name, 6) && name[6] == '~' &&
+           name[7] >= '1' && name[7] <= '4') {
+               i = 8;
+               goto only_spaces_and_periods;
+       }
+
+       /*
+        * Is it a fall-back NTFS short name (for details, see
+        * https://en.wikipedia.org/wiki/8.3_filename?
+        */
+       for (i = 0, saw_tilde = 0; i < 8; i++)
+               if (name[i] == '\0')
+                       return 0;
+               else if (saw_tilde) {
+                       if (name[i] < '0' || name[i] > '9')
+                               return 0;
+               } else if (name[i] == '~') {
+                       if (name[++i] < '1' || name[i] > '9')
+                               return 0;
+                       saw_tilde = 1;
+               } else if (i >= 6)
+                       return 0;
+               else if (name[i] < 0) {
+                       /*
+                        * We know our needles contain only ASCII, so we clamp
+                        * here to make the results of tolower() sane.
+                        */
+                       return 0;
+               } else if (tolower(name[i]) != dotgit_ntfs_shortname_prefix[i])
+                       return 0;
+
+       goto only_spaces_and_periods;
+}
+
+/*
+ * Inline helper to make sure compiler resolves strlen() on literals at
+ * compile time.
+ */
+static inline int is_ntfs_dot_str(const char *name, const char *dotgit_name,
+                                 const char *dotgit_ntfs_shortname_prefix)
+{
+       return is_ntfs_dot_generic(name, dotgit_name, strlen(dotgit_name),
+                                  dotgit_ntfs_shortname_prefix);
+}
+
+int is_ntfs_dotgitmodules(const char *name)
+{
+       return is_ntfs_dot_str(name, "gitmodules", "gi7eba");
+}
+
+int is_ntfs_dotgitignore(const char *name)
+{
+       return is_ntfs_dot_str(name, "gitignore", "gi250a");
+}
+
+int is_ntfs_dotgitattributes(const char *name)
+{
+       return is_ntfs_dot_str(name, "gitattributes", "gi7d29");
+}
+
 int looks_like_command_line_option(const char *str)
 {
        return str && str[0] == '-';
index 82eb39cd679ffbce82abac691c6f4343335b9924..27cd6067860d4fdb0b48b6bcf7c84d5903efd71b 100644 (file)
@@ -198,7 +198,7 @@ static void parse_pathspec_attr_match(struct pathspec_item *item, const char *va
        }
 
        if (item->attr_check->nr != item->attr_match_nr)
-               die("BUG: should have same number of entries");
+               BUG("should have same number of entries");
 
        string_list_clear(&list, 0);
 }
@@ -422,7 +422,7 @@ static void init_pathspec_item(struct pathspec_item *item, unsigned flags,
 
        if (pathspec_prefix >= 0 &&
            (prefixlen || (prefix && *prefix)))
-               die("BUG: 'prefix' magic is supposed to be used at worktree's root");
+               BUG("'prefix' magic is supposed to be used at worktree's root");
 
        if ((magic & PATHSPEC_LITERAL) && (magic & PATHSPEC_GLOB))
                die(_("%s: 'literal' and 'glob' are incompatible"), elt);
@@ -486,7 +486,7 @@ static void init_pathspec_item(struct pathspec_item *item, unsigned flags,
        /* sanity checks, pathspec matchers assume these are sane */
        if (item->nowildcard_len > item->len ||
            item->prefix         > item->len) {
-               die ("BUG: error initializing pathspec_item");
+               BUG("error initializing pathspec_item");
        }
 }
 
@@ -545,7 +545,7 @@ void parse_pathspec(struct pathspec *pathspec,
 
        if ((flags & PATHSPEC_PREFER_CWD) &&
            (flags & PATHSPEC_PREFER_FULL))
-               die("BUG: PATHSPEC_PREFER_CWD and PATHSPEC_PREFER_FULL are incompatible");
+               BUG("PATHSPEC_PREFER_CWD and PATHSPEC_PREFER_FULL are incompatible");
 
        /* No arguments with prefix -> prefix pathspec */
        if (!entry) {
@@ -553,7 +553,7 @@ void parse_pathspec(struct pathspec *pathspec,
                        return;
 
                if (!(flags & PATHSPEC_PREFER_CWD))
-                       die("BUG: PATHSPEC_PREFER_CWD requires arguments");
+                       BUG("PATHSPEC_PREFER_CWD requires arguments");
 
                pathspec->items = item = xcalloc(1, sizeof(*item));
                item->match = xstrdup(prefix);
@@ -609,7 +609,7 @@ void parse_pathspec(struct pathspec *pathspec,
 
        if (pathspec->magic & PATHSPEC_MAXDEPTH) {
                if (flags & PATHSPEC_KEEP_ORDER)
-                       die("BUG: PATHSPEC_MAXDEPTH_VALID and PATHSPEC_KEEP_ORDER are incompatible");
+                       BUG("PATHSPEC_MAXDEPTH_VALID and PATHSPEC_KEEP_ORDER are incompatible");
                QSORT(pathspec->items, pathspec->nr, pathspec_item_cmp);
        }
 }
index 555eb2a50746bb8f129a8c14c9b6d61169093436..a593c08aad814ca015198936158219f44cbd0226 100644 (file)
@@ -277,7 +277,7 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,
        ssize_t ret;
 
        if (fd >= 0 && src_buf && *src_buf)
-               die("BUG: multiple sources given to packet_read");
+               BUG("multiple sources given to packet_read");
 
        /* Read up to "size" bytes from our source, whatever it is. */
        if (src_buf && *src_buf) {
index 34fe891fc03672fa042257e4d32630882868eadf..703fa6ff7bf297e9d0dd91586f951f715032e06a 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -1161,10 +1161,11 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
                strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET));
                return 1;
        case 'T':               /* tree hash */
-               strbuf_addstr(sb, oid_to_hex(&commit->tree->object.oid));
+               strbuf_addstr(sb, oid_to_hex(get_commit_tree_oid(commit)));
                return 1;
        case 't':               /* abbreviated tree hash */
-               strbuf_add_unique_abbrev(sb, &commit->tree->object.oid,
+               strbuf_add_unique_abbrev(sb,
+                                        get_commit_tree_oid(commit),
                                         c->pretty_ctx->abbrev);
                return 1;
        case 'P':               /* parent hashes */
index 126d09672738533b6ecc6b94b7405dff888bbaf6..a07845187233e429e00138cce7ade42e97a40d4b 100644 (file)
@@ -20,7 +20,7 @@ void prio_queue_reverse(struct prio_queue *queue)
        int i, j;
 
        if (queue->compare != NULL)
-               die("BUG: prio_queue_reverse() on non-LIFO queue");
+               BUG("prio_queue_reverse() on non-LIFO queue");
        for (i = 0; i < (j = (queue->nr - 1) - i); i++)
                swap(queue, i, j);
 }
index a6ea33a5db8b616420572cda4b857ac6d7d43cb1..ffb976c33c6936a7b178c7c478bcf4cd2840d472 100644 (file)
@@ -78,7 +78,7 @@ static void add_recent_object(const struct object_id *oid,
         * later processing, and the revision machinery expects
         * commits and tags to have been parsed.
         */
-       type = oid_object_info(oid, NULL);
+       type = oid_object_info(the_repository, oid, NULL);
        if (type < 0)
                die("unable to get object info for %s", oid_to_hex(oid));
 
index 10f1c6bb8a316e85448445afc3478c832d61709c..372588260ea00f48ea4d031095901a575ac0e620 100644 (file)
@@ -752,7 +752,7 @@ struct cache_entry *make_cache_entry(unsigned int mode,
        int size, len;
        struct cache_entry *ce, *ret;
 
-       if (!verify_path(path)) {
+       if (!verify_path(path, mode)) {
                error("Invalid path '%s'", path);
                return NULL;
        }
@@ -817,7 +817,7 @@ int ce_same_name(const struct cache_entry *a, const struct cache_entry *b)
  * Also, we don't want double slashes or slashes at the
  * end that can make pathnames ambiguous.
  */
-static int verify_dotfile(const char *rest)
+static int verify_dotfile(const char *rest, unsigned mode)
 {
        /*
         * The first character was '.', but that
@@ -831,8 +831,13 @@ static int verify_dotfile(const char *rest)
 
        switch (*rest) {
        /*
-        * ".git" followed by  NUL or slash is bad. This
-        * shares the path end test with the ".." case.
+        * ".git" followed by NUL or slash is bad. Note that we match
+        * case-insensitively here, even if ignore_case is not set.
+        * This outlaws ".GIT" everywhere out of an abundance of caution,
+        * since there's really no good reason to allow it.
+        *
+        * Once we've seen ".git", we can also find ".gitmodules", etc (also
+        * case-insensitively).
         */
        case 'g':
        case 'G':
@@ -840,8 +845,15 @@ static int verify_dotfile(const char *rest)
                        break;
                if (rest[2] != 't' && rest[2] != 'T')
                        break;
-               rest += 2;
-       /* fallthrough */
+               if (rest[3] == '\0' || is_dir_sep(rest[3]))
+                       return 0;
+               if (S_ISLNK(mode)) {
+                       rest += 3;
+                       if (skip_iprefix(rest, "modules", &rest) &&
+                           (*rest == '\0' || is_dir_sep(*rest)))
+                               return 0;
+               }
+               break;
        case '.':
                if (rest[1] == '\0' || is_dir_sep(rest[1]))
                        return 0;
@@ -849,7 +861,7 @@ static int verify_dotfile(const char *rest)
        return 1;
 }
 
-int verify_path(const char *path)
+int verify_path(const char *path, unsigned mode)
 {
        char c;
 
@@ -862,12 +874,25 @@ int verify_path(const char *path)
                        return 1;
                if (is_dir_sep(c)) {
 inside:
-                       if (protect_hfs && is_hfs_dotgit(path))
-                               return 0;
-                       if (protect_ntfs && is_ntfs_dotgit(path))
-                               return 0;
+                       if (protect_hfs) {
+                               if (is_hfs_dotgit(path))
+                                       return 0;
+                               if (S_ISLNK(mode)) {
+                                       if (is_hfs_dotgitmodules(path))
+                                               return 0;
+                               }
+                       }
+                       if (protect_ntfs) {
+                               if (is_ntfs_dotgit(path))
+                                       return 0;
+                               if (S_ISLNK(mode)) {
+                                       if (is_ntfs_dotgitmodules(path))
+                                               return 0;
+                               }
+                       }
+
                        c = *path++;
-                       if ((c == '.' && !verify_dotfile(path)) ||
+                       if ((c == '.' && !verify_dotfile(path, mode)) ||
                            is_dir_sep(c) || c == '\0')
                                return 0;
                }
@@ -1184,7 +1209,7 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
 
        if (!ok_to_add)
                return -1;
-       if (!verify_path(ce->name))
+       if (!verify_path(ce->name, ce->ce_mode))
                return error("Invalid path '%s'", ce->name);
 
        if (!skip_df_check &&
@@ -1806,7 +1831,7 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)
        if (verify_hdr(hdr, mmap_size) < 0)
                goto unmap;
 
-       hashcpy(istate->sha1, (const unsigned char *)hdr + mmap_size - the_hash_algo->rawsz);
+       hashcpy(istate->oid.hash, (const unsigned char *)hdr + mmap_size - the_hash_algo->rawsz);
        istate->version = ntohl(hdr->hdr_version);
        istate->cache_nr = ntohl(hdr->hdr_entries);
        istate->cache_alloc = alloc_nr(istate->cache_nr);
@@ -1878,7 +1903,7 @@ int read_index_from(struct index_state *istate, const char *path,
        uint64_t start = getnanotime();
        struct split_index *split_index;
        int ret;
-       char *base_sha1_hex;
+       char *base_oid_hex;
        char *base_path;
 
        /* istate->initialized covers both .git/index and .git/sharedindex.xxx */
@@ -1889,7 +1914,7 @@ int read_index_from(struct index_state *istate, const char *path,
        trace_performance_since(start, "read cache %s", path);
 
        split_index = istate->split_index;
-       if (!split_index || is_null_sha1(split_index->base_sha1)) {
+       if (!split_index || is_null_oid(&split_index->base_oid)) {
                post_read_index_from(istate);
                return ret;
        }
@@ -1899,13 +1924,13 @@ int read_index_from(struct index_state *istate, const char *path,
        else
                split_index->base = xcalloc(1, sizeof(*split_index->base));
 
-       base_sha1_hex = sha1_to_hex(split_index->base_sha1);
-       base_path = xstrfmt("%s/sharedindex.%s", gitdir, base_sha1_hex);
+       base_oid_hex = oid_to_hex(&split_index->base_oid);
+       base_path = xstrfmt("%s/sharedindex.%s", gitdir, base_oid_hex);
        ret = do_read_index(split_index->base, base_path, 1);
-       if (hashcmp(split_index->base_sha1, split_index->base->sha1))
+       if (oidcmp(&split_index->base_oid, &split_index->base->oid))
                die("broken index, expect %s in %s, got %s",
-                   base_sha1_hex, base_path,
-                   sha1_to_hex(split_index->base->sha1));
+                   base_oid_hex, base_path,
+                   oid_to_hex(&split_index->base->oid));
 
        freshen_shared_index(base_path, 0);
        merge_base_index(istate);
@@ -2194,7 +2219,7 @@ static int verify_index_from(const struct index_state *istate, const char *path)
        if (n != the_hash_algo->rawsz)
                goto out;
 
-       if (hashcmp(istate->sha1, hash))
+       if (hashcmp(istate->oid.hash, hash))
                goto out;
 
        close(fd);
@@ -2268,7 +2293,7 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
 
        if (!istate->version) {
                istate->version = get_index_format_default();
-               if (getenv("GIT_TEST_SPLIT_INDEX"))
+               if (git_env_bool("GIT_TEST_SPLIT_INDEX", 0))
                        init_split_index(istate);
        }
 
@@ -2373,7 +2398,7 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
                        return -1;
        }
 
-       if (ce_flush(&c, newfd, istate->sha1))
+       if (ce_flush(&c, newfd, istate->oid.hash))
                return -1;
        if (close_tempfile_gently(tempfile)) {
                error(_("could not close '%s'"), tempfile->filename.buf);
@@ -2497,10 +2522,10 @@ static int write_shared_index(struct index_state *istate,
                return ret;
        }
        ret = rename_tempfile(temp,
-                             git_path("sharedindex.%s", sha1_to_hex(si->base->sha1)));
+                             git_path("sharedindex.%s", oid_to_hex(&si->base->oid)));
        if (!ret) {
-               hashcpy(si->base_sha1, si->base->sha1);
-               clean_shared_index_files(sha1_to_hex(si->base->sha1));
+               oidcpy(&si->base_oid, &si->base->oid);
+               clean_shared_index_files(oid_to_hex(&si->base->oid));
        }
 
        return ret;
@@ -2554,13 +2579,13 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock,
        if (!si || alternate_index_output ||
            (istate->cache_changed & ~EXTMASK)) {
                if (si)
-                       hashclr(si->base_sha1);
+                       oidclr(&si->base_oid);
                ret = do_write_locked_index(istate, lock, flags);
                goto out;
        }
 
-       if (getenv("GIT_TEST_SPLIT_INDEX")) {
-               int v = si->base_sha1[0];
+       if (git_env_bool("GIT_TEST_SPLIT_INDEX", 0)) {
+               int v = si->base_oid.hash[0];
                if ((v & 15) < 6)
                        istate->cache_changed |= SPLIT_INDEX_ORDERED;
        }
@@ -2575,7 +2600,7 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock,
 
                temp = mks_tempfile(git_path("sharedindex_XXXXXX"));
                if (!temp) {
-                       hashclr(si->base_sha1);
+                       oidclr(&si->base_oid);
                        ret = do_write_locked_index(istate, lock, flags);
                        goto out;
                }
@@ -2595,7 +2620,7 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock,
        /* Freshen the shared index only if the split-index was written */
        if (!ret && !new_shared_index) {
                const char *shared_index = git_path("sharedindex.%s",
-                                                   sha1_to_hex(si->base_sha1));
+                                                   oid_to_hex(&si->base_oid));
                freshen_shared_index(shared_index, 1);
        }
 
index 39e2744c949bb7c4ffbcb7c7e08d3757a6b732de..01c1a82075eca737c2e3749e295ca24bbab870b6 100644 (file)
@@ -824,7 +824,7 @@ static int grab_objectname(const char *name, const struct object_id *oid,
                        v->s = xstrdup(find_unique_abbrev(oid, atom->u.objectname.length));
                        return 1;
                } else
-                       die("BUG: unknown %%(objectname) option");
+                       BUG("unknown %%(objectname) option");
        }
        return 0;
 }
@@ -888,7 +888,7 @@ static void grab_commit_values(struct atom_value *val, int deref, struct object
                if (deref)
                        name++;
                if (!strcmp(name, "tree")) {
-                       v->s = xstrdup(oid_to_hex(&commit->tree->object.oid));
+                       v->s = xstrdup(oid_to_hex(get_commit_tree_oid(commit)));
                }
                else if (!strcmp(name, "numparent")) {
                        v->value = commit_list_count(commit->parents);
@@ -1372,7 +1372,7 @@ static void fill_remote_ref_details(struct used_atom *atom, const char *refname,
                else
                        *s = "";
        } else
-               die("BUG: unhandled RR_* enum");
+               BUG("unhandled RR_* enum");
 }
 
 char *get_head_description(void)
diff --git a/refs.c b/refs.c
index 64aadd14c95274316fae2022741df91d2ec64c78..0eb379f9312fd9f167fea2e0f148c85c47cd2ff0 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -303,7 +303,7 @@ enum peel_status peel_object(const struct object_id *name, struct object_id *oid
        struct object *o = lookup_unknown_object(name->hash);
 
        if (o->type == OBJ_NONE) {
-               int type = oid_object_info(name, NULL);
+               int type = oid_object_info(the_repository, name, NULL);
                if (type < 0 || !object_as_type(o, type, 0))
                        return PEEL_INVALID;
        }
@@ -615,7 +615,8 @@ int dwim_log(const char *str, int len, struct object_id *oid, char **log)
 static int is_per_worktree_ref(const char *refname)
 {
        return !strcmp(refname, "HEAD") ||
-               starts_with(refname, "refs/bisect/");
+               starts_with(refname, "refs/bisect/") ||
+               starts_with(refname, "refs/rewritten/");
 }
 
 static int is_pseudoref_syntax(const char *refname)
@@ -659,7 +660,7 @@ static int write_pseudoref(const char *pseudoref, const struct object_id *oid,
 {
        const char *filename;
        int fd;
-       static struct lock_file lock;
+       struct lock_file lock = LOCK_INIT;
        struct strbuf buf = STRBUF_INIT;
        int ret = -1;
 
@@ -669,8 +670,7 @@ static int write_pseudoref(const char *pseudoref, const struct object_id *oid,
        strbuf_addf(&buf, "%s\n", oid_to_hex(oid));
 
        filename = git_path("%s", pseudoref);
-       fd = hold_lock_file_for_update_timeout(&lock, filename,
-                                              LOCK_DIE_ON_ERROR,
+       fd = hold_lock_file_for_update_timeout(&lock, filename, 0,
                                               get_files_ref_lock_timeout_ms());
        if (fd < 0) {
                strbuf_addf(err, "could not open '%s' for writing: %s",
@@ -681,10 +681,21 @@ static int write_pseudoref(const char *pseudoref, const struct object_id *oid,
        if (old_oid) {
                struct object_id actual_old_oid;
 
-               if (read_ref(pseudoref, &actual_old_oid))
-                       die("could not read ref '%s'", pseudoref);
-               if (oidcmp(&actual_old_oid, old_oid)) {
-                       strbuf_addf(err, "unexpected sha1 when writing '%s'", pseudoref);
+               if (read_ref(pseudoref, &actual_old_oid)) {
+                       if (!is_null_oid(old_oid)) {
+                               strbuf_addf(err, "could not read ref '%s'",
+                                           pseudoref);
+                               rollback_lock_file(&lock);
+                               goto done;
+                       }
+               } else if (is_null_oid(old_oid)) {
+                       strbuf_addf(err, "ref '%s' already exists",
+                                   pseudoref);
+                       rollback_lock_file(&lock);
+                       goto done;
+               } else if (oidcmp(&actual_old_oid, old_oid)) {
+                       strbuf_addf(err, "unexpected object ID when writing '%s'",
+                                   pseudoref);
                        rollback_lock_file(&lock);
                        goto done;
                }
@@ -705,24 +716,28 @@ static int write_pseudoref(const char *pseudoref, const struct object_id *oid,
 
 static int delete_pseudoref(const char *pseudoref, const struct object_id *old_oid)
 {
-       static struct lock_file lock;
        const char *filename;
 
        filename = git_path("%s", pseudoref);
 
        if (old_oid && !is_null_oid(old_oid)) {
+               struct lock_file lock = LOCK_INIT;
                int fd;
                struct object_id actual_old_oid;
 
                fd = hold_lock_file_for_update_timeout(
-                               &lock, filename, LOCK_DIE_ON_ERROR,
+                               &lock, filename, 0,
                                get_files_ref_lock_timeout_ms());
-               if (fd < 0)
-                       die_errno(_("Could not open '%s' for writing"), filename);
+               if (fd < 0) {
+                       error_errno(_("could not open '%s' for writing"),
+                                   filename);
+                       return -1;
+               }
                if (read_ref(pseudoref, &actual_old_oid))
                        die("could not read ref '%s'", pseudoref);
                if (oidcmp(&actual_old_oid, old_oid)) {
-                       warning("Unexpected sha1 when deleting %s", pseudoref);
+                       error("unexpected object ID when deleting '%s'",
+                             pseudoref);
                        rollback_lock_file(&lock);
                        return -1;
                }
@@ -959,10 +974,10 @@ void ref_transaction_free(struct ref_transaction *transaction)
                /* OK */
                break;
        case REF_TRANSACTION_PREPARED:
-               die("BUG: free called on a prepared reference transaction");
+               BUG("free called on a prepared reference transaction");
                break;
        default:
-               die("BUG: unexpected reference transaction state");
+               BUG("unexpected reference transaction state");
                break;
        }
 
@@ -984,7 +999,7 @@ struct ref_update *ref_transaction_add_update(
        struct ref_update *update;
 
        if (transaction->state != REF_TRANSACTION_OPEN)
-               die("BUG: update called for transaction that is not open");
+               BUG("update called for transaction that is not open");
 
        FLEX_ALLOC_STR(update, refname, refname);
        ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
@@ -1034,7 +1049,7 @@ int ref_transaction_create(struct ref_transaction *transaction,
                           struct strbuf *err)
 {
        if (!new_oid || is_null_oid(new_oid))
-               die("BUG: create called without valid new_oid");
+               BUG("create called without valid new_oid");
        return ref_transaction_update(transaction, refname, new_oid,
                                      &null_oid, flags, msg, err);
 }
@@ -1046,7 +1061,7 @@ int ref_transaction_delete(struct ref_transaction *transaction,
                           struct strbuf *err)
 {
        if (old_oid && is_null_oid(old_oid))
-               die("BUG: delete called with old_oid set to zeros");
+               BUG("delete called with old_oid set to zeros");
        return ref_transaction_update(transaction, refname,
                                      &null_oid, old_oid,
                                      flags, msg, err);
@@ -1059,7 +1074,7 @@ int ref_transaction_verify(struct ref_transaction *transaction,
                           struct strbuf *err)
 {
        if (!old_oid)
-               die("BUG: verify called with old_oid set to NULL");
+               BUG("verify called with old_oid set to NULL");
        return ref_transaction_update(transaction, refname,
                                      NULL, old_oid,
                                      flags, NULL, err);
@@ -1147,8 +1162,8 @@ char *shorten_unambiguous_ref(const char *refname, int strict)
                for (i = 0; i < nr_rules; i++) {
                        assert(offset < total_len);
                        scanf_fmts[i] = (char *)&scanf_fmts[nr_rules] + offset;
-                       offset += snprintf(scanf_fmts[i], total_len - offset,
-                                          ref_rev_parse_rules[i], 2, "%s") + 1;
+                       offset += xsnprintf(scanf_fmts[i], total_len - offset,
+                                           ref_rev_parse_rules[i], 2, "%s") + 1;
                }
        }
 
@@ -1657,7 +1672,7 @@ static struct ref_store *ref_store_init(const char *gitdir,
        struct ref_store *refs;
 
        if (!be)
-               die("BUG: reference backend %s is unknown", be_name);
+               BUG("reference backend %s is unknown", be_name);
 
        refs = be->init(gitdir, flags);
        return refs;
@@ -1668,6 +1683,9 @@ struct ref_store *get_main_ref_store(struct repository *r)
        if (r->refs)
                return r->refs;
 
+       if (!r->gitdir)
+               BUG("attempting to get main_ref_store outside of repository");
+
        r->refs = ref_store_init(r->gitdir, REF_STORE_ALL_CAPS);
        return r->refs;
 }
@@ -1685,7 +1703,7 @@ static void register_ref_store_map(struct hashmap *map,
                hashmap_init(map, ref_store_hash_cmp, NULL, 0);
 
        if (hashmap_put(map, alloc_ref_store_hash_entry(name, refs)))
-               die("BUG: %s ref_store '%s' initialized twice", type, name);
+               BUG("%s ref_store '%s' initialized twice", type, name);
 }
 
 struct ref_store *get_submodule_ref_store(const char *submodule)
@@ -1831,7 +1849,7 @@ int ref_update_reject_duplicates(struct string_list *refnames,
                                    refnames->items[i].string);
                        return 1;
                } else if (cmp > 0) {
-                       die("BUG: ref_update_reject_duplicates() received unsorted list");
+                       BUG("ref_update_reject_duplicates() received unsorted list");
                }
        }
        return 0;
@@ -1847,13 +1865,13 @@ int ref_transaction_prepare(struct ref_transaction *transaction,
                /* Good. */
                break;
        case REF_TRANSACTION_PREPARED:
-               die("BUG: prepare called twice on reference transaction");
+               BUG("prepare called twice on reference transaction");
                break;
        case REF_TRANSACTION_CLOSED:
-               die("BUG: prepare called on a closed reference transaction");
+               BUG("prepare called on a closed reference transaction");
                break;
        default:
-               die("BUG: unexpected reference transaction state");
+               BUG("unexpected reference transaction state");
                break;
        }
 
@@ -1880,10 +1898,10 @@ int ref_transaction_abort(struct ref_transaction *transaction,
                ret = refs->be->transaction_abort(refs, transaction, err);
                break;
        case REF_TRANSACTION_CLOSED:
-               die("BUG: abort called on a closed reference transaction");
+               BUG("abort called on a closed reference transaction");
                break;
        default:
-               die("BUG: unexpected reference transaction state");
+               BUG("unexpected reference transaction state");
                break;
        }
 
@@ -1908,10 +1926,10 @@ int ref_transaction_commit(struct ref_transaction *transaction,
                /* Fall through to finish. */
                break;
        case REF_TRANSACTION_CLOSED:
-               die("BUG: commit called on a closed reference transaction");
+               BUG("commit called on a closed reference transaction");
                break;
        default:
-               die("BUG: unexpected reference transaction state");
+               BUG("unexpected reference transaction state");
                break;
        }
 
@@ -1992,7 +2010,7 @@ int refs_verify_refname_available(struct ref_store *refs,
        }
 
        if (ok != ITER_DONE)
-               die("BUG: error while iterating over references");
+               BUG("error while iterating over references");
 
        extra_refname = find_descendant_ref(dirname.buf, extras, skip);
        if (extra_refname)
index 49d8f67bf132c2357fddcca4b449e28a54ce8f7c..a9a066dcfb60d83a10e27d1c0d1131faf2ae97a4 100644 (file)
@@ -121,7 +121,7 @@ static void files_assert_main_repository(struct files_ref_store *refs,
        if (refs->store_flags & REF_STORE_MAIN)
                return;
 
-       die("BUG: operation %s only allowed for main ref store", caller);
+       BUG("operation %s only allowed for main ref store", caller);
 }
 
 /*
@@ -137,13 +137,13 @@ static struct files_ref_store *files_downcast(struct ref_store *ref_store,
        struct files_ref_store *refs;
 
        if (ref_store->be != &refs_be_files)
-               die("BUG: ref_store is type \"%s\" not \"files\" in %s",
+               BUG("ref_store is type \"%s\" not \"files\" in %s",
                    ref_store->be->name, caller);
 
        refs = (struct files_ref_store *)ref_store;
 
        if ((refs->store_flags & required_flags) != required_flags)
-               die("BUG: operation %s requires abilities 0x%x, but only have 0x%x",
+               BUG("operation %s requires abilities 0x%x, but only have 0x%x",
                    caller, required_flags, refs->store_flags);
 
        return refs;
@@ -162,7 +162,7 @@ static void files_reflog_path(struct files_ref_store *refs,
                strbuf_addf(sb, "%s/logs/%s", refs->gitcommondir, refname);
                break;
        default:
-               die("BUG: unknown ref type %d of ref %s",
+               BUG("unknown ref type %d of ref %s",
                    ref_type(refname), refname);
        }
 }
@@ -180,7 +180,7 @@ static void files_ref_path(struct files_ref_store *refs,
                strbuf_addf(sb, "%s/%s", refs->gitcommondir, refname);
                break;
        default:
-               die("BUG: unknown ref type %d of ref %s",
+               BUG("unknown ref type %d of ref %s",
                    ref_type(refname), refname);
        }
 }
@@ -2006,7 +2006,7 @@ static int files_for_each_reflog_ent_reverse(struct ref_store *ref_store,
 
        }
        if (!ret && sb.len)
-               die("BUG: reverse reflog parser had leftover data");
+               BUG("reverse reflog parser had leftover data");
 
        fclose(logfp);
        strbuf_release(&sb);
@@ -2084,7 +2084,7 @@ static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator)
 static int files_reflog_iterator_peel(struct ref_iterator *ref_iterator,
                                   struct object_id *peeled)
 {
-       die("BUG: ref_iterator_peel() called for reflog_iterator");
+       BUG("ref_iterator_peel() called for reflog_iterator");
 }
 
 static int files_reflog_iterator_abort(struct ref_iterator *ref_iterator)
@@ -2869,7 +2869,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,
        assert(err);
 
        if (transaction->state != REF_TRANSACTION_OPEN)
-               die("BUG: commit called for transaction that is not open");
+               BUG("commit called for transaction that is not open");
 
        /* Fail if a refname appears more than once in the transaction: */
        for (i = 0; i < transaction->nr; i++)
@@ -2895,7 +2895,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,
         */
        if (refs_for_each_rawref(&refs->base, ref_present,
                                 &affected_refnames))
-               die("BUG: initial ref transaction called with existing refs");
+               BUG("initial ref transaction called with existing refs");
 
        packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, err);
        if (!packed_transaction) {
@@ -2908,7 +2908,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,
 
                if ((update->flags & REF_HAVE_OLD) &&
                    !is_null_oid(&update->old_oid))
-                       die("BUG: initial ref transaction with old_sha1 set");
+                       BUG("initial ref transaction with old_sha1 set");
                if (refs_verify_refname_available(&refs->base, update->refname,
                                                  &affected_refnames, NULL,
                                                  err)) {
@@ -2991,7 +2991,7 @@ static int files_reflog_expire(struct ref_store *ref_store,
 {
        struct files_ref_store *refs =
                files_downcast(ref_store, REF_STORE_WRITE, "reflog_expire");
-       static struct lock_file reflog_lock;
+       struct lock_file reflog_lock = LOCK_INIT;
        struct expire_reflog_cb cb;
        struct ref_lock *lock;
        struct strbuf log_file_sb = STRBUF_INIT;
index bd35da4e622ca4016194177ce97594b46c5aafe7..2ac91ac3401c87108b9cdb4983acb7c165df82d9 100644 (file)
@@ -54,7 +54,7 @@ static int empty_ref_iterator_advance(struct ref_iterator *ref_iterator)
 static int empty_ref_iterator_peel(struct ref_iterator *ref_iterator,
                                   struct object_id *peeled)
 {
-       die("BUG: peel called for empty iterator");
+       BUG("peel called for empty iterator");
 }
 
 static int empty_ref_iterator_abort(struct ref_iterator *ref_iterator)
@@ -177,7 +177,7 @@ static int merge_ref_iterator_peel(struct ref_iterator *ref_iterator,
                (struct merge_ref_iterator *)ref_iterator;
 
        if (!iter->current) {
-               die("BUG: peel called before advance for merge iterator");
+               BUG("peel called before advance for merge iterator");
        }
        return ref_iterator_peel(*iter->current, peeled);
 }
@@ -338,7 +338,7 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator)
                         * trimming, report it as a bug:
                         */
                        if (strlen(iter->iter0->refname) <= iter->trim)
-                               die("BUG: attempt to trim too many characters");
+                               BUG("attempt to trim too many characters");
                        iter->base.refname = iter->iter0->refname + iter->trim;
                } else {
                        iter->base.refname = iter->iter0->refname;
index 369c34f886fc5532de2afbd1590823eed0758a33..cec3fb9e00f78c1e0be9f24bd07aa756d56afc00 100644 (file)
@@ -221,13 +221,13 @@ static struct packed_ref_store *packed_downcast(struct ref_store *ref_store,
        struct packed_ref_store *refs;
 
        if (ref_store->be != &refs_be_packed)
-               die("BUG: ref_store is type \"%s\" not \"packed\" in %s",
+               BUG("ref_store is type \"%s\" not \"packed\" in %s",
                    ref_store->be->name, caller);
 
        refs = (struct packed_ref_store *)ref_store;
 
        if ((refs->store_flags & required_flags) != required_flags)
-               die("BUG: unallowed operation (%s), requires %x, has %x\n",
+               BUG("unallowed operation (%s), requires %x, has %x\n",
                    caller, required_flags, refs->store_flags);
 
        return refs;
@@ -1036,7 +1036,7 @@ void packed_refs_unlock(struct ref_store *ref_store)
                        "packed_refs_unlock");
 
        if (!is_lock_file_locked(&refs->lock))
-               die("BUG: packed_refs_unlock() called when not locked");
+               BUG("packed_refs_unlock() called when not locked");
        rollback_lock_file(&refs->lock);
 }
 
@@ -1089,7 +1089,7 @@ static int write_with_updates(struct packed_ref_store *refs,
        char *packed_refs_path;
 
        if (!is_lock_file_locked(&refs->lock))
-               die("BUG: write_with_updates() called while unlocked");
+               BUG("write_with_updates() called while unlocked");
 
        /*
         * If packed-refs is a symlink, we want to overwrite the
@@ -1563,21 +1563,21 @@ static int packed_create_symref(struct ref_store *ref_store,
                               const char *refname, const char *target,
                               const char *logmsg)
 {
-       die("BUG: packed reference store does not support symrefs");
+       BUG("packed reference store does not support symrefs");
 }
 
 static int packed_rename_ref(struct ref_store *ref_store,
                            const char *oldrefname, const char *newrefname,
                            const char *logmsg)
 {
-       die("BUG: packed reference store does not support renaming references");
+       BUG("packed reference store does not support renaming references");
 }
 
 static int packed_copy_ref(struct ref_store *ref_store,
                           const char *oldrefname, const char *newrefname,
                           const char *logmsg)
 {
-       die("BUG: packed reference store does not support copying references");
+       BUG("packed reference store does not support copying references");
 }
 
 static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_store)
@@ -1610,7 +1610,7 @@ static int packed_create_reflog(struct ref_store *ref_store,
                               const char *refname, int force_create,
                               struct strbuf *err)
 {
-       die("BUG: packed reference store does not support reflogs");
+       BUG("packed reference store does not support reflogs");
 }
 
 static int packed_delete_reflog(struct ref_store *ref_store,
index e90bd3e727fd0fcd5f6a99dde4fbac65b479f411..9b110c8494ff802fea730570f8c8f3b522292dbf 100644 (file)
@@ -23,7 +23,7 @@ struct ref_dir *get_ref_dir(struct ref_entry *entry)
        dir = &entry->u.subdir;
        if (entry->flag & REF_INCOMPLETE) {
                if (!dir->cache->fill_ref_dir)
-                       die("BUG: incomplete ref_store without fill_ref_dir function");
+                       BUG("incomplete ref_store without fill_ref_dir function");
 
                dir->cache->fill_ref_dir(dir->cache->ref_store, dir, entry->name);
                entry->flag &= ~REF_INCOMPLETE;
index ceb05347bd18c3c0ea4e7cfd7ffe09a072b722ce..99b0bedc6ddc670cad6e74083dc33dc0aa4b595a 100644 (file)
@@ -684,7 +684,7 @@ static int post_rpc(struct rpc_state *rpc)
        curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
        curl_easy_setopt(slot->curl, CURLOPT_POST, 1);
        curl_easy_setopt(slot->curl, CURLOPT_URL, rpc->service_url);
-       curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "gzip");
+       curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "");
 
        if (large_request) {
                /* The request body is large and the size cannot be predicted.
@@ -1259,6 +1259,7 @@ static int proxy_request(struct proxy_state *p)
 
        slot = get_active_slot();
 
+       curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "");
        curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
        curl_easy_setopt(slot->curl, CURLOPT_POST, 1);
        curl_easy_setopt(slot->curl, CURLOPT_URL, p->service_url);
index 0d1a3d07f8b8f31240da5ffc8f22b16db4855699..abe80c13972c718bd5d738063a2f526f2d12ee87 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -1188,7 +1188,7 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds
                        continue; /* not a tag */
                if (string_list_has_string(&dst_tag, ref->name))
                        continue; /* they already have it */
-               if (oid_object_info(&ref->new_oid, NULL) != OBJ_TAG)
+               if (oid_object_info(the_repository, &ref->new_oid, NULL) != OBJ_TAG)
                        continue; /* be conservative */
                item = string_list_append(&src_tag, ref->name);
                item->util = ref;
@@ -1626,7 +1626,7 @@ static const char *branch_get_push_1(struct branch *branch, struct strbuf *err)
                }
        }
 
-       die("BUG: unhandled push situation");
+       BUG("unhandled push situation");
 }
 
 const char *branch_get_push(struct branch *branch, struct strbuf *err)
index 62a65665946f061cf28914d15611d89044cc879f..45ecc6cefafdea435d0fee39691f7458b940b877 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -136,6 +136,7 @@ void free_refs(struct ref *ref);
 struct oid_array;
 struct packet_reader;
 struct argv_array;
+struct string_list;
 extern struct ref **get_remote_heads(struct packet_reader *reader,
                                     struct ref **list, unsigned int flags,
                                     struct oid_array *extra_have,
@@ -144,7 +145,8 @@ extern struct ref **get_remote_heads(struct packet_reader *reader,
 /* Used for protocol v2 in order to retrieve refs from a remote */
 extern struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
                                    struct ref **list, int for_push,
-                                   const struct argv_array *ref_prefixes);
+                                   const struct argv_array *ref_prefixes,
+                                   const struct string_list *server_options);
 
 int resolve_remote_symref(struct ref *ref, struct ref *list);
 int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid);
index 246b98cd4f1e4d229109bbeb8ffe90731df43df0..801b5c16789f5ac7d87a4409d8218311a52db0aa 100644 (file)
@@ -37,7 +37,7 @@ static void prepare_replace_object(struct repository *r)
                return;
 
        r->objects->replace_map =
-               xmalloc(sizeof(*the_repository->objects->replace_map));
+               xmalloc(sizeof(*r->objects->replace_map));
        oidmap_init(r->objects->replace_map, 0);
 
        for_each_replace_ref(r, register_replace_ref, NULL);
index beff3caa9e24a902e560372cdab3c337769c7c89..02fe884603df56ea0996121152b7640afb56a48f 100644 (file)
@@ -238,7 +238,8 @@ void repo_clear(struct repository *repo)
 
        if (repo->index) {
                discard_index(repo->index);
-               FREE_AND_NULL(repo->index);
+               if (repo->index != &the_index)
+                       FREE_AND_NULL(repo->index);
        }
 }
 
index 18cae2d11c9a86aae0ed352a8f7606b142c5c183..e0862e27786244b1a98723d09475223cc001dd62 100644 (file)
--- a/rerere.c
+++ b/rerere.c
@@ -703,10 +703,9 @@ static int merge(const struct rerere_id *id, const char *path)
        return ret;
 }
 
-static struct lock_file index_lock;
-
 static void update_paths(struct string_list *update)
 {
+       struct lock_file index_lock = LOCK_INIT;
        int i;
 
        hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR);
index aed95b4b35fbb187bb96242afcf6b4d8e3d2008b..fc5b3b83d9a08e9deed55ee15520387f147da626 100644 (file)
@@ -90,7 +90,7 @@ struct string_list *resolve_undo_read(const char *data, unsigned long size)
                                continue;
                        if (size < rawsz)
                                goto error;
-                       memcpy(ui->oid[i].hash, (const unsigned char *)data, rawsz);
+                       oidread(&ui->oid[i], (const unsigned char *)data);
                        size -= rawsz;
                        data += rawsz;
                }
index 1cff11833e7e2ce64114e4d18339d6ae21ec80e8..40fd91ff2b16bffc725f8602f45117279e27ba19 100644 (file)
@@ -52,12 +52,9 @@ static void mark_tree_contents_uninteresting(struct tree *tree)
 {
        struct tree_desc desc;
        struct name_entry entry;
-       struct object *obj = &tree->object;
 
-       if (!has_object_file(&obj->oid))
+       if (parse_tree_gently(tree, 1) < 0)
                return;
-       if (parse_tree(tree) < 0)
-               die("bad tree %s", oid_to_hex(&obj->oid));
 
        init_tree_desc(&desc, tree->buffer, tree->size);
        while (tree_entry(&desc, &entry)) {
@@ -95,50 +92,63 @@ void mark_tree_uninteresting(struct tree *tree)
        mark_tree_contents_uninteresting(tree);
 }
 
-void mark_parents_uninteresting(struct commit *commit)
+struct commit_stack {
+       struct commit **items;
+       size_t nr, alloc;
+};
+#define COMMIT_STACK_INIT { NULL, 0, 0 }
+
+static void commit_stack_push(struct commit_stack *stack, struct commit *commit)
 {
-       struct commit_list *parents = NULL, *l;
+       ALLOC_GROW(stack->items, stack->nr + 1, stack->alloc);
+       stack->items[stack->nr++] = commit;
+}
 
-       for (l = commit->parents; l; l = l->next)
-               commit_list_insert(l->item, &parents);
+static struct commit *commit_stack_pop(struct commit_stack *stack)
+{
+       return stack->nr ? stack->items[--stack->nr] : NULL;
+}
 
-       while (parents) {
-               struct commit *commit = pop_commit(&parents);
+static void commit_stack_clear(struct commit_stack *stack)
+{
+       FREE_AND_NULL(stack->items);
+       stack->nr = stack->alloc = 0;
+}
 
-               while (commit) {
-                       /*
-                        * A missing commit is ok iff its parent is marked
-                        * uninteresting.
-                        *
-                        * We just mark such a thing parsed, so that when
-                        * it is popped next time around, we won't be trying
-                        * to parse it and get an error.
-                        */
-                       if (!commit->object.parsed &&
-                           !has_object_file(&commit->object.oid))
-                               commit->object.parsed = 1;
+static void mark_one_parent_uninteresting(struct commit *commit,
+                                         struct commit_stack *pending)
+{
+       struct commit_list *l;
 
-                       if (commit->object.flags & UNINTERESTING)
-                               break;
+       if (commit->object.flags & UNINTERESTING)
+               return;
+       commit->object.flags |= UNINTERESTING;
+
+       /*
+        * Normally we haven't parsed the parent
+        * yet, so we won't have a parent of a parent
+        * here. However, it may turn out that we've
+        * reached this commit some other way (where it
+        * wasn't uninteresting), in which case we need
+        * to mark its parents recursively too..
+        */
+       for (l = commit->parents; l; l = l->next)
+               commit_stack_push(pending, l->item);
+}
 
-                       commit->object.flags |= UNINTERESTING;
+void mark_parents_uninteresting(struct commit *commit)
+{
+       struct commit_stack pending = COMMIT_STACK_INIT;
+       struct commit_list *l;
 
-                       /*
-                        * Normally we haven't parsed the parent
-                        * yet, so we won't have a parent of a parent
-                        * here. However, it may turn out that we've
-                        * reached this commit some other way (where it
-                        * wasn't uninteresting), in which case we need
-                        * to mark its parents recursively too..
-                        */
-                       if (!commit->parents)
-                               break;
+       for (l = commit->parents; l; l = l->next)
+               mark_one_parent_uninteresting(l->item, &pending);
 
-                       for (l = commit->parents->next; l; l = l->next)
-                               commit_list_insert(l->item, &parents);
-                       commit = commit->parents->item;
-               }
-       }
+       while (pending.nr > 0)
+               mark_one_parent_uninteresting(commit_stack_pop(&pending),
+                                             &pending);
+
+       commit_stack_clear(&pending);
 }
 
 static void add_pending_object_with_path(struct rev_info *revs,
@@ -441,8 +451,8 @@ static void file_change(struct diff_options *options,
 static int rev_compare_tree(struct rev_info *revs,
                            struct commit *parent, struct commit *commit)
 {
-       struct tree *t1 = parent->tree;
-       struct tree *t2 = commit->tree;
+       struct tree *t1 = get_commit_tree(parent);
+       struct tree *t2 = get_commit_tree(commit);
 
        if (!t1)
                return REV_TREE_NEW;
@@ -478,7 +488,7 @@ static int rev_compare_tree(struct rev_info *revs,
 static int rev_same_tree_as_empty(struct rev_info *revs, struct commit *commit)
 {
        int retval;
-       struct tree *t1 = commit->tree;
+       struct tree *t1 = get_commit_tree(commit);
 
        if (!t1)
                return 0;
@@ -616,7 +626,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
        if (!revs->prune)
                return;
 
-       if (!commit->tree)
+       if (!get_commit_tree(commit))
                return;
 
        if (!commit->parents) {
@@ -1752,6 +1762,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
        const char *arg = argv[0];
        const char *optarg;
        int argcount;
+       const unsigned hexsz = the_hash_algo->hexsz;
 
        /* pseudo revision arguments */
        if (!strcmp(arg, "--all") || !strcmp(arg, "--branches") ||
@@ -2039,8 +2050,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->abbrev = strtoul(optarg, NULL, 10);
                if (revs->abbrev < MINIMUM_ABBREV)
                        revs->abbrev = MINIMUM_ABBREV;
-               else if (revs->abbrev > 40)
-                       revs->abbrev = 40;
+               else if (revs->abbrev > hexsz)
+                       revs->abbrev = hexsz;
        } else if (!strcmp(arg, "--abbrev-commit")) {
                revs->abbrev_commit = 1;
                revs->abbrev_commit_given = 1;
@@ -2108,7 +2119,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->ignore_missing = 1;
        } else if (!strcmp(arg, "--exclude-promisor-objects")) {
                if (fetch_if_missing)
-                       die("BUG: exclude_promisor_objects can only be used when fetch_if_missing is 0");
+                       BUG("exclude_promisor_objects can only be used when fetch_if_missing is 0");
                revs->exclude_promisor_objects = 1;
        } else {
                int opts = diff_opt_parse(&revs->diffopt, argv, argc, revs->prefix);
@@ -2174,7 +2185,7 @@ static int handle_revision_pseudo_opt(const char *submodule,
                 * supported right now, so stick to single worktree.
                 */
                if (!revs->single_worktree)
-                       die("BUG: --single-worktree cannot be used together with submodule");
+                       BUG("--single-worktree cannot be used together with submodule");
                refs = get_submodule_ref_store(submodule);
        } else
                refs = get_main_ref_store(the_repository);
@@ -3087,7 +3098,7 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi
 {
        if (commit->object.flags & SHOWN)
                return commit_ignore;
-       if (revs->unpacked && has_sha1_pack(commit->object.oid.hash))
+       if (revs->unpacked && has_object_pack(&commit->object.oid))
                return commit_ignore;
        if (commit->object.flags & UNINTERESTING)
                return commit_ignore;
index 12c94c1dbe5a720a7581af81516b6884fc25d8d0..84b883c2132bb07bc7fa21434368d64664587b8b 100644 (file)
@@ -245,7 +245,7 @@ int sane_execvp(const char *file, char * const argv[])
 static const char **prepare_shell_cmd(struct argv_array *out, const char **argv)
 {
        if (!argv[0])
-               die("BUG: shell command is empty");
+               BUG("shell command is empty");
 
        if (strcspn(argv[0], "|&;<>()$`\\\"' \t\n*?[#~=%") != strlen(argv[0])) {
 #ifndef GIT_WINDOWS_NATIVE
@@ -383,7 +383,7 @@ static void child_err_spew(struct child_process *cmd, struct child_err *cerr)
 static void prepare_cmd(struct argv_array *out, const struct child_process *cmd)
 {
        if (!cmd->argv[0])
-               die("BUG: command is empty");
+               BUG("command is empty");
 
        /*
         * Add SHELL_PATH so in the event exec fails with ENOEXEC we can
@@ -471,15 +471,12 @@ struct atfork_state {
        sigset_t old;
 };
 
-#ifndef NO_PTHREADS
-static void bug_die(int err, const char *msg)
-{
-       if (err) {
-               errno = err;
-               die_errno("BUG: %s", msg);
-       }
-}
-#endif
+#define CHECK_BUG(err, msg) \
+       do { \
+               int e = (err); \
+               if (e) \
+                       BUG("%s: %s", msg, strerror(e)); \
+       } while(0)
 
 static void atfork_prepare(struct atfork_state *as)
 {
@@ -491,9 +488,9 @@ static void atfork_prepare(struct atfork_state *as)
        if (sigprocmask(SIG_SETMASK, &all, &as->old))
                die_errno("sigprocmask");
 #else
-       bug_die(pthread_sigmask(SIG_SETMASK, &all, &as->old),
+       CHECK_BUG(pthread_sigmask(SIG_SETMASK, &all, &as->old),
                "blocking all signals");
-       bug_die(pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &as->cs),
+       CHECK_BUG(pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &as->cs),
                "disabling cancellation");
 #endif
 }
@@ -504,9 +501,9 @@ static void atfork_parent(struct atfork_state *as)
        if (sigprocmask(SIG_SETMASK, &as->old, NULL))
                die_errno("sigprocmask");
 #else
-       bug_die(pthread_setcancelstate(as->cs, NULL),
+       CHECK_BUG(pthread_setcancelstate(as->cs, NULL),
                "re-enabling cancellation");
-       bug_die(pthread_sigmask(SIG_SETMASK, &as->old, NULL),
+       CHECK_BUG(pthread_sigmask(SIG_SETMASK, &as->old, NULL),
                "restoring signal mask");
 #endif
 }
@@ -967,7 +964,7 @@ int run_command(struct child_process *cmd)
        int code;
 
        if (cmd->out < 0 || cmd->err < 0)
-               die("BUG: run_command with a pipe can cause deadlock");
+               BUG("run_command with a pipe can cause deadlock");
 
        code = start_command(cmd);
        if (code)
@@ -1557,7 +1554,7 @@ static void pp_init(struct parallel_processes *pp,
 
        pp->data = data;
        if (!get_next_task)
-               die("BUG: you need to specify a get_next_task function");
+               BUG("you need to specify a get_next_task function");
        pp->get_next_task = get_next_task;
 
        pp->start_failure = start_failure ? start_failure : default_start_failure;
@@ -1619,7 +1616,7 @@ static int pp_start_one(struct parallel_processes *pp)
                if (pp->children[i].state == GIT_CP_FREE)
                        break;
        if (i == pp->max_processes)
-               die("BUG: bookkeeping is hard");
+               BUG("bookkeeping is hard");
 
        code = pp->get_next_task(&pp->children[i].process,
                                 &pp->children[i].err,
index 4ce5120e777085227dd0f677dfed70a003cffe96..cca968043ea86c4515e6af1b4fe9251a0140c0a3 100644 (file)
 #include "hashmap.h"
 #include "notes-utils.h"
 #include "sigchain.h"
+#include "unpack-trees.h"
+#include "worktree.h"
+#include "oidmap.h"
+#include "oidset.h"
+#include "alias.h"
 
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
 
@@ -74,13 +79,6 @@ static GIT_PATH_FUNC(rebase_path_message, "rebase-merge/message")
  * previous commit and from the first squash/fixup commit are written
  * to it. The commit message for each subsequent squash/fixup commit
  * is appended to the file as it is processed.
- *
- * The first line of the file is of the form
- *     # This is a combination of $count commits.
- * where $count is the number of commits whose messages have been
- * written to the file so far (including the initial "pick" commit).
- * Each time that a commit message is processed, this line is read and
- * updated. It is deleted just before the combined commit is made.
  */
 static GIT_PATH_FUNC(rebase_path_squash_msg, "rebase-merge/message-squash")
 /*
@@ -91,6 +89,11 @@ static GIT_PATH_FUNC(rebase_path_squash_msg, "rebase-merge/message-squash")
  * commit without opening the editor.)
  */
 static GIT_PATH_FUNC(rebase_path_fixup_msg, "rebase-merge/message-fixup")
+/*
+ * This file contains the list fixup/squash commands that have been
+ * accumulated into message-fixup or message-squash so far.
+ */
+static GIT_PATH_FUNC(rebase_path_current_fixups, "rebase-merge/current-fixups")
 /*
  * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
  * GIT_AUTHOR_DATE that will be used for the commit that is currently
@@ -120,6 +123,19 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha")
 static GIT_PATH_FUNC(rebase_path_rewritten_list, "rebase-merge/rewritten-list")
 static GIT_PATH_FUNC(rebase_path_rewritten_pending,
        "rebase-merge/rewritten-pending")
+
+/*
+ * The path of the file containig the OID of the "squash onto" commit, i.e.
+ * the dummy commit used for `reset [new root]`.
+ */
+static GIT_PATH_FUNC(rebase_path_squash_onto, "rebase-merge/squash-onto")
+
+/*
+ * The path of the file listing refs that need to be deleted after the rebase
+ * finishes. This is used by the `label` command to record the need for cleanup.
+ */
+static GIT_PATH_FUNC(rebase_path_refs_to_delete, "rebase-merge/refs-to-delete")
+
 /*
  * The following files are written by git-rebase just after parsing the
  * command-line (and are only consumed, not modified, by the sequencer).
@@ -245,18 +261,35 @@ static const char *gpg_sign_opt_quoted(struct replay_opts *opts)
 
 int sequencer_remove_state(struct replay_opts *opts)
 {
-       struct strbuf dir = STRBUF_INIT;
+       struct strbuf buf = STRBUF_INIT;
        int i;
 
+       if (is_rebase_i(opts) &&
+           strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {
+               char *p = buf.buf;
+               while (*p) {
+                       char *eol = strchr(p, '\n');
+                       if (eol)
+                               *eol = '\0';
+                       if (delete_ref("(rebase -i) cleanup", p, NULL, 0) < 0)
+                               warning(_("could not delete '%s'"), p);
+                       if (!eol)
+                               break;
+                       p = eol + 1;
+               }
+       }
+
        free(opts->gpg_sign);
        free(opts->strategy);
        for (i = 0; i < opts->xopts_nr; i++)
                free(opts->xopts[i]);
        free(opts->xopts);
+       strbuf_release(&opts->current_fixups);
 
-       strbuf_addstr(&dir, get_dir(opts));
-       remove_dir_recursively(&dir, 0);
-       strbuf_release(&dir);
+       strbuf_reset(&buf);
+       strbuf_addstr(&buf, get_dir(opts));
+       remove_dir_recursively(&buf, 0);
+       strbuf_release(&buf);
 
        return 0;
 }
@@ -346,12 +379,14 @@ static int write_message(const void *buf, size_t len, const char *filename,
        if (msg_fd < 0)
                return error_errno(_("could not lock '%s'"), filename);
        if (write_in_full(msg_fd, buf, len) < 0) {
+               error_errno(_("could not write to '%s'"), filename);
                rollback_lock_file(&msg_file);
-               return error_errno(_("could not write to '%s'"), filename);
+               return -1;
        }
        if (append_eol && write(msg_fd, "\n", 1) < 0) {
+               error_errno(_("could not write eol to '%s'"), filename);
                rollback_lock_file(&msg_file);
-               return error_errno(_("could not write eol to '%s'"), filename);
+               return -1;
        }
        if (commit_lock_file(&msg_file) < 0)
                return error(_("failed to finalize '%s'"), filename);
@@ -441,7 +476,8 @@ static int fast_forward_to(const struct object_id *to, const struct object_id *f
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
            ref_transaction_update(transaction, "HEAD",
-                                  to, unborn ? &null_oid : from,
+                                  to, unborn && !is_rebase_i(opts) ?
+                                  &null_oid : from,
                                   0, sb.buf, &err) ||
            ref_transaction_commit(transaction, &err)) {
                ref_transaction_free(transaction);
@@ -500,8 +536,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
        o.show_rename_progress = 1;
 
        head_tree = parse_tree_indirect(head);
-       next_tree = next ? next->tree : empty_tree();
-       base_tree = base ? base->tree : empty_tree();
+       next_tree = next ? get_commit_tree(next) : empty_tree();
+       base_tree = base ? get_commit_tree(base) : empty_tree();
 
        for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++)
                parse_merge_opt(&o, *xopt);
@@ -533,9 +569,23 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
        return !clean;
 }
 
+static struct object_id *get_cache_tree_oid(void)
+{
+       if (!active_cache_tree)
+               active_cache_tree = cache_tree();
+
+       if (!cache_tree_fully_valid(active_cache_tree))
+               if (cache_tree_update(&the_index, 0)) {
+                       error(_("unable to update cache tree"));
+                       return NULL;
+               }
+
+       return &active_cache_tree->oid;
+}
+
 static int is_index_unchanged(void)
 {
-       struct object_id head_oid;
+       struct object_id head_oid, *cache_tree_oid;
        struct commit *head_commit;
 
        if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
@@ -554,15 +604,10 @@ static int is_index_unchanged(void)
        if (parse_commit(head_commit))
                return -1;
 
-       if (!active_cache_tree)
-               active_cache_tree = cache_tree();
-
-       if (!cache_tree_fully_valid(active_cache_tree))
-               if (cache_tree_update(&the_index, 0))
-                       return error(_("unable to update cache tree"));
+       if (!(cache_tree_oid = get_cache_tree_oid()))
+               return -1;
 
-       return !oidcmp(&active_cache_tree->oid,
-                      &head_commit->tree->object.oid);
+       return !oidcmp(cache_tree_oid, get_commit_tree_oid(head_commit));
 }
 
 static int write_author_script(const char *message)
@@ -654,6 +699,52 @@ static char *get_author(const char *message)
        return NULL;
 }
 
+/* Read author-script and return an ident line (author <email> timestamp) */
+static const char *read_author_ident(struct strbuf *buf)
+{
+       const char *keys[] = {
+               "GIT_AUTHOR_NAME=", "GIT_AUTHOR_EMAIL=", "GIT_AUTHOR_DATE="
+       };
+       char *in, *out, *eol;
+       int i = 0, len;
+
+       if (strbuf_read_file(buf, rebase_path_author_script(), 256) <= 0)
+               return NULL;
+
+       /* dequote values and construct ident line in-place */
+       for (in = out = buf->buf; i < 3 && in - buf->buf < buf->len; i++) {
+               if (!skip_prefix(in, keys[i], (const char **)&in)) {
+                       warning("could not parse '%s' (looking for '%s'",
+                               rebase_path_author_script(), keys[i]);
+                       return NULL;
+               }
+
+               eol = strchrnul(in, '\n');
+               *eol = '\0';
+               sq_dequote(in);
+               len = strlen(in);
+
+               if (i > 0) /* separate values by spaces */
+                       *(out++) = ' ';
+               if (i == 1) /* email needs to be surrounded by <...> */
+                       *(out++) = '<';
+               memmove(out, in, len);
+               out += len;
+               if (i == 1) /* email needs to be surrounded by <...> */
+                       *(out++) = '>';
+               in = eol + 1;
+       }
+
+       if (i < 3) {
+               warning("could not parse '%s' (looking for '%s')",
+                       rebase_path_author_script(), keys[i]);
+               return NULL;
+       }
+
+       buf->len = out - buf->buf;
+       return buf->buf;
+}
+
 static const char staged_changes_advice[] =
 N_("you have staged changes in your working tree\n"
 "If these changes are meant to be squashed into the previous commit, run:\n"
@@ -673,6 +764,7 @@ N_("you have staged changes in your working tree\n"
 #define AMEND_MSG   (1<<2)
 #define CLEANUP_MSG (1<<3)
 #define VERIFY_MSG  (1<<4)
+#define CREATE_ROOT_COMMIT (1<<5)
 
 /*
  * If we are cherry-pick, and if the merge did not result in
@@ -692,6 +784,40 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
        struct child_process cmd = CHILD_PROCESS_INIT;
        const char *value;
 
+       if (flags & CREATE_ROOT_COMMIT) {
+               struct strbuf msg = STRBUF_INIT, script = STRBUF_INIT;
+               const char *author = is_rebase_i(opts) ?
+                       read_author_ident(&script) : NULL;
+               struct object_id root_commit, *cache_tree_oid;
+               int res = 0;
+
+               if (!defmsg)
+                       BUG("root commit without message");
+
+               if (!(cache_tree_oid = get_cache_tree_oid()))
+                       res = -1;
+
+               if (!res)
+                       res = strbuf_read_file(&msg, defmsg, 0);
+
+               if (res <= 0)
+                       res = error_errno(_("could not read '%s'"), defmsg);
+               else
+                       res = commit_tree(msg.buf, msg.len, cache_tree_oid,
+                                         NULL, &root_commit, author,
+                                         opts->gpg_sign);
+
+               strbuf_release(&msg);
+               strbuf_release(&script);
+               if (!res) {
+                       update_ref(NULL, "CHERRY_PICK_HEAD", &root_commit, NULL,
+                                  REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR);
+                       res = update_ref(NULL, "HEAD", &root_commit, NULL, 0,
+                                        UPDATE_REFS_MSG_ON_ERR);
+               }
+               return res < 0 ? error(_("writing root commit")) : 0;
+       }
+
        cmd.git_cmd = 1;
 
        if (is_rebase_i(opts)) {
@@ -718,6 +844,8 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
                argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign);
        if (defmsg)
                argv_array_pushl(&cmd.args, "-F", defmsg, NULL);
+       else if (!(flags & EDIT_MSG))
+               argv_array_pushl(&cmd.args, "-C", "HEAD", NULL);
        if ((flags & CLEANUP_MSG))
                argv_array_push(&cmd.args, "--cleanup=strip");
        if ((flags & EDIT_MSG))
@@ -1119,8 +1247,8 @@ static int try_to_commit(struct strbuf *msg, const char *author,
        }
 
        if (!(flags & ALLOW_EMPTY) && !oidcmp(current_head ?
-                                             &current_head->tree->object.oid :
-                                             &empty_tree_oid, &tree)) {
+                                             get_commit_tree_oid(current_head) :
+                                             the_hash_algo->empty_tree, &tree)) {
                res = 1; /* run 'git commit' to display error message */
                goto out;
        }
@@ -1180,7 +1308,8 @@ static int do_commit(const char *msg_file, const char *author,
 {
        int res = 1;
 
-       if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG)) {
+       if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG) &&
+           !(flags & CREATE_ROOT_COMMIT)) {
                struct object_id oid;
                struct strbuf sb = STRBUF_INIT;
 
@@ -1219,12 +1348,12 @@ static int is_original_commit_empty(struct commit *commit)
                if (parse_commit(parent))
                        return error(_("could not parse parent commit %s"),
                                oid_to_hex(&parent->object.oid));
-               ptree_oid = &parent->tree->object.oid;
+               ptree_oid = get_commit_tree_oid(parent);
        } else {
                ptree_oid = the_hash_algo->empty_tree; /* commit is root */
        }
 
-       return !oidcmp(ptree_oid, &commit->tree->object.oid);
+       return !oidcmp(ptree_oid, get_commit_tree_oid(commit));
 }
 
 /*
@@ -1280,6 +1409,9 @@ enum todo_command {
        TODO_SQUASH,
        /* commands that do something else than handling a single commit */
        TODO_EXEC,
+       TODO_LABEL,
+       TODO_RESET,
+       TODO_MERGE,
        /* commands that do nothing but are counted for reporting progress */
        TODO_NOOP,
        TODO_DROP,
@@ -1298,6 +1430,9 @@ static struct {
        { 'f', "fixup" },
        { 's', "squash" },
        { 'x', "exec" },
+       { 'l', "label" },
+       { 't', "reset" },
+       { 'm', "merge" },
        { 0,   "noop" },
        { 'd', "drop" },
        { 0,   NULL }
@@ -1327,38 +1462,43 @@ static int is_fixup(enum todo_command command)
        return command == TODO_FIXUP || command == TODO_SQUASH;
 }
 
+/* Does this command create a (non-merge) commit? */
+static int is_pick_or_similar(enum todo_command command)
+{
+       switch (command) {
+       case TODO_PICK:
+       case TODO_REVERT:
+       case TODO_EDIT:
+       case TODO_REWORD:
+       case TODO_FIXUP:
+       case TODO_SQUASH:
+               return 1;
+       default:
+               return 0;
+       }
+}
+
 static int update_squash_messages(enum todo_command command,
                struct commit *commit, struct replay_opts *opts)
 {
        struct strbuf buf = STRBUF_INIT;
-       int count, res;
+       int res;
        const char *message, *body;
 
-       if (file_exists(rebase_path_squash_msg())) {
+       if (opts->current_fixup_count > 0) {
                struct strbuf header = STRBUF_INIT;
-               char *eol, *p;
+               char *eol;
 
-               if (strbuf_read_file(&buf, rebase_path_squash_msg(), 2048) <= 0)
+               if (strbuf_read_file(&buf, rebase_path_squash_msg(), 9) <= 0)
                        return error(_("could not read '%s'"),
                                rebase_path_squash_msg());
 
-               p = buf.buf + 1;
-               eol = strchrnul(buf.buf, '\n');
-               if (buf.buf[0] != comment_line_char ||
-                   (p += strcspn(p, "0123456789\n")) == eol)
-                       return error(_("unexpected 1st line of squash message:"
-                                      "\n\n\t%.*s"),
-                                    (int)(eol - buf.buf), buf.buf);
-               count = strtol(p, NULL, 10);
-
-               if (count < 1)
-                       return error(_("invalid 1st line of squash message:\n"
-                                      "\n\t%.*s"),
-                                    (int)(eol - buf.buf), buf.buf);
+               eol = buf.buf[0] != comment_line_char ?
+                       buf.buf : strchrnul(buf.buf, '\n');
 
                strbuf_addf(&header, "%c ", comment_line_char);
-               strbuf_addf(&header,
-                           _("This is a combination of %d commits."), ++count);
+               strbuf_addf(&header, _("This is a combination of %d commits."),
+                           opts->current_fixup_count + 2);
                strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);
                strbuf_release(&header);
        } else {
@@ -1381,10 +1521,8 @@ static int update_squash_messages(enum todo_command command,
                                     rebase_path_fixup_msg());
                }
 
-               count = 2;
                strbuf_addf(&buf, "%c ", comment_line_char);
-               strbuf_addf(&buf, _("This is a combination of %d commits."),
-                           count);
+               strbuf_addf(&buf, _("This is a combination of %d commits."), 2);
                strbuf_addf(&buf, "\n%c ", comment_line_char);
                strbuf_addstr(&buf, _("This is the 1st commit message:"));
                strbuf_addstr(&buf, "\n\n");
@@ -1401,13 +1539,14 @@ static int update_squash_messages(enum todo_command command,
        if (command == TODO_SQUASH) {
                unlink(rebase_path_fixup_msg());
                strbuf_addf(&buf, "\n%c ", comment_line_char);
-               strbuf_addf(&buf, _("This is the commit message #%d:"), count);
+               strbuf_addf(&buf, _("This is the commit message #%d:"),
+                           ++opts->current_fixup_count);
                strbuf_addstr(&buf, "\n\n");
                strbuf_addstr(&buf, body);
        } else if (command == TODO_FIXUP) {
                strbuf_addf(&buf, "\n%c ", comment_line_char);
                strbuf_addf(&buf, _("The commit message #%d will be skipped:"),
-                           count);
+                           ++opts->current_fixup_count);
                strbuf_addstr(&buf, "\n\n");
                strbuf_add_commented_lines(&buf, body, strlen(body));
        } else
@@ -1416,6 +1555,17 @@ static int update_squash_messages(enum todo_command command,
 
        res = write_message(buf.buf, buf.len, rebase_path_squash_msg(), 0);
        strbuf_release(&buf);
+
+       if (!res) {
+               strbuf_addf(&opts->current_fixups, "%s%s %s",
+                           opts->current_fixups.len ? "\n" : "",
+                           command_to_string(command),
+                           oid_to_hex(&commit->object.oid));
+               res = write_message(opts->current_fixups.buf,
+                                   opts->current_fixups.len,
+                                   rebase_path_current_fixups(), 0);
+       }
+
        return res;
 }
 
@@ -1481,9 +1631,16 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
                        return error(_("your index file is unmerged."));
        } else {
                unborn = get_oid("HEAD", &head);
-               if (unborn)
+               /* Do we want to generate a root commit? */
+               if (is_pick_or_similar(command) && opts->have_squash_onto &&
+                   !oidcmp(&head, &opts->squash_onto)) {
+                       if (is_fixup(command))
+                               return error(_("cannot fixup root commit"));
+                       flags |= CREATE_ROOT_COMMIT;
+                       unborn = 1;
+               } else if (unborn)
                        oidcpy(&head, the_hash_algo->empty_tree);
-               if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD",
+               if (index_differs_from(unborn ? empty_tree_oid_hex() : "HEAD",
                                       NULL, 0))
                        return error_dirty_index(opts);
        }
@@ -1678,6 +1835,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
        if (!res && final_fixup) {
                unlink(rebase_path_fixup_msg());
                unlink(rebase_path_squash_msg());
+               unlink(rebase_path_current_fixups());
+               strbuf_reset(&opts->current_fixups);
+               opts->current_fixup_count = 0;
        }
 
 leave:
@@ -1725,9 +1885,14 @@ static int read_and_refresh_cache(struct replay_opts *opts)
        return 0;
 }
 
+enum todo_item_flags {
+       TODO_EDIT_MERGE_MSG = 1
+};
+
 struct todo_item {
        enum todo_command command;
        struct commit *commit;
+       unsigned int flags;
        const char *arg;
        int arg_len;
        size_t offset_in_buf;
@@ -1762,6 +1927,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)
        char *end_of_object_name;
        int i, saved, status, padding;
 
+       item->flags = 0;
+
        /* left-trim */
        bol += strspn(bol, " \t");
 
@@ -1803,13 +1970,29 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)
                return error(_("missing arguments for %s"),
                             command_to_string(item->command));
 
-       if (item->command == TODO_EXEC) {
+       if (item->command == TODO_EXEC || item->command == TODO_LABEL ||
+           item->command == TODO_RESET) {
                item->commit = NULL;
                item->arg = bol;
                item->arg_len = (int)(eol - bol);
                return 0;
        }
 
+       if (item->command == TODO_MERGE) {
+               if (skip_prefix(bol, "-C", &bol))
+                       bol += strspn(bol, " \t");
+               else if (skip_prefix(bol, "-c", &bol)) {
+                       bol += strspn(bol, " \t");
+                       item->flags |= TODO_EDIT_MERGE_MSG;
+               } else {
+                       item->flags |= TODO_EDIT_MERGE_MSG;
+                       item->commit = NULL;
+                       item->arg = bol;
+                       item->arg_len = (int)(eol - bol);
+                       return 0;
+               }
+       }
+
        end_of_object_name = (char *) bol + strcspn(bol, " \t\n");
        saved = *end_of_object_name;
        *end_of_object_name = '\0';
@@ -1871,6 +2054,23 @@ static int count_commands(struct todo_list *todo_list)
        return count;
 }
 
+static int get_item_line_offset(struct todo_list *todo_list, int index)
+{
+       return index < todo_list->nr ?
+               todo_list->items[index].offset_in_buf : todo_list->buf.len;
+}
+
+static const char *get_item_line(struct todo_list *todo_list, int index)
+{
+       return todo_list->buf.buf + get_item_line_offset(todo_list, index);
+}
+
+static int get_item_line_length(struct todo_list *todo_list, int index)
+{
+       return get_item_line_offset(todo_list, index + 1)
+               -  get_item_line_offset(todo_list, index);
+}
+
 static ssize_t strbuf_read_file_or_whine(struct strbuf *sb, const char *path)
 {
        int fd;
@@ -2054,6 +2254,22 @@ static int read_populate_opts(struct replay_opts *opts)
                read_strategy_opts(opts, &buf);
                strbuf_release(&buf);
 
+               if (read_oneliner(&opts->current_fixups,
+                                 rebase_path_current_fixups(), 1)) {
+                       const char *p = opts->current_fixups.buf;
+                       opts->current_fixup_count = 1;
+                       while ((p = strchr(p, '\n'))) {
+                               opts->current_fixup_count++;
+                               p++;
+                       }
+               }
+
+               if (read_oneliner(&buf, rebase_path_squash_onto(), 0)) {
+                       if (get_oid_hex(buf.buf, &opts->squash_onto) < 0)
+                               return error(_("unusable squash-onto"));
+                       opts->have_squash_onto = 1;
+               }
+
                return 0;
        }
 
@@ -2127,9 +2343,9 @@ static int save_head(const char *head)
        written = write_in_full(fd, buf.buf, buf.len);
        strbuf_release(&buf);
        if (written < 0) {
+               error_errno(_("could not write to '%s'"), git_path_head_file());
                rollback_lock_file(&head_lock);
-               return error_errno(_("could not write to '%s'"),
-                                  git_path_head_file());
+               return -1;
        }
        if (commit_lock_file(&head_lock) < 0)
                return error(_("failed to finalize '%s'"), git_path_head_file());
@@ -2250,29 +2466,27 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
        fd = hold_lock_file_for_update(&todo_lock, todo_path, 0);
        if (fd < 0)
                return error_errno(_("could not lock '%s'"), todo_path);
-       offset = next < todo_list->nr ?
-               todo_list->items[next].offset_in_buf : todo_list->buf.len;
+       offset = get_item_line_offset(todo_list, next);
        if (write_in_full(fd, todo_list->buf.buf + offset,
                        todo_list->buf.len - offset) < 0)
                return error_errno(_("could not write to '%s'"), todo_path);
        if (commit_lock_file(&todo_lock) < 0)
                return error(_("failed to finalize '%s'"), todo_path);
 
-       if (is_rebase_i(opts)) {
-               const char *done_path = rebase_path_done();
-               int fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666);
-               int prev_offset = !next ? 0 :
-                       todo_list->items[next - 1].offset_in_buf;
+       if (is_rebase_i(opts) && next > 0) {
+               const char *done = rebase_path_done();
+               int fd = open(done, O_CREAT | O_WRONLY | O_APPEND, 0666);
+               int ret = 0;
 
-               if (fd >= 0 && offset > prev_offset &&
-                   write_in_full(fd, todo_list->buf.buf + prev_offset,
-                                 offset - prev_offset) < 0) {
-                       close(fd);
-                       return error_errno(_("could not write to '%s'"),
-                                          done_path);
-               }
-               if (fd >= 0)
-                       close(fd);
+               if (fd < 0)
+                       return 0;
+               if (write_in_full(fd, get_item_line(todo_list, next - 1),
+                                 get_item_line_length(todo_list, next - 1))
+                   < 0)
+                       ret = error_errno(_("could not write to '%s'"), done);
+               if (close(fd) < 0)
+                       ret = error_errno(_("failed to finalize '%s'"), done);
+               return ret;
        }
        return 0;
 }
@@ -2400,10 +2614,9 @@ static int error_with_patch(struct commit *commit,
 static int error_failed_squash(struct commit *commit,
        struct replay_opts *opts, int subject_len, const char *subject)
 {
-       if (rename(rebase_path_squash_msg(), rebase_path_message()))
-               return error(_("could not rename '%s' to '%s'"),
+       if (copy_file(rebase_path_message(), rebase_path_squash_msg(), 0666))
+               return error(_("could not copy '%s' to '%s'"),
                        rebase_path_squash_msg(), rebase_path_message());
-       unlink(rebase_path_fixup_msg());
        unlink(git_path_merge_msg());
        if (copy_file(git_path_merge_msg(), rebase_path_message(), 0666))
                return error(_("could not copy '%s' to '%s'"),
@@ -2456,6 +2669,377 @@ static int do_exec(const char *command_line)
        return status;
 }
 
+static int safe_append(const char *filename, const char *fmt, ...)
+{
+       va_list ap;
+       struct lock_file lock = LOCK_INIT;
+       int fd = hold_lock_file_for_update(&lock, filename,
+                                          LOCK_REPORT_ON_ERROR);
+       struct strbuf buf = STRBUF_INIT;
+
+       if (fd < 0)
+               return -1;
+
+       if (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT) {
+               error_errno(_("could not read '%s'"), filename);
+               rollback_lock_file(&lock);
+               return -1;
+       }
+       strbuf_complete(&buf, '\n');
+       va_start(ap, fmt);
+       strbuf_vaddf(&buf, fmt, ap);
+       va_end(ap);
+
+       if (write_in_full(fd, buf.buf, buf.len) < 0) {
+               error_errno(_("could not write to '%s'"), filename);
+               strbuf_release(&buf);
+               rollback_lock_file(&lock);
+               return -1;
+       }
+       if (commit_lock_file(&lock) < 0) {
+               strbuf_release(&buf);
+               rollback_lock_file(&lock);
+               return error(_("failed to finalize '%s'"), filename);
+       }
+
+       strbuf_release(&buf);
+       return 0;
+}
+
+static int do_label(const char *name, int len)
+{
+       struct ref_store *refs = get_main_ref_store(the_repository);
+       struct ref_transaction *transaction;
+       struct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;
+       struct strbuf msg = STRBUF_INIT;
+       int ret = 0;
+       struct object_id head_oid;
+
+       if (len == 1 && *name == '#')
+               return error("Illegal label name: '%.*s'", len, name);
+
+       strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
+       strbuf_addf(&msg, "rebase -i (label) '%.*s'", len, name);
+
+       transaction = ref_store_transaction_begin(refs, &err);
+       if (!transaction) {
+               error("%s", err.buf);
+               ret = -1;
+       } else if (get_oid("HEAD", &head_oid)) {
+               error(_("could not read HEAD"));
+               ret = -1;
+       } else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,
+                                         NULL, 0, msg.buf, &err) < 0 ||
+                  ref_transaction_commit(transaction, &err)) {
+               error("%s", err.buf);
+               ret = -1;
+       }
+       ref_transaction_free(transaction);
+       strbuf_release(&err);
+       strbuf_release(&msg);
+
+       if (!ret)
+               ret = safe_append(rebase_path_refs_to_delete(),
+                                 "%s\n", ref_name.buf);
+       strbuf_release(&ref_name);
+
+       return ret;
+}
+
+static const char *reflog_message(struct replay_opts *opts,
+       const char *sub_action, const char *fmt, ...);
+
+static int do_reset(const char *name, int len, struct replay_opts *opts)
+{
+       struct strbuf ref_name = STRBUF_INIT;
+       struct object_id oid;
+       struct lock_file lock = LOCK_INIT;
+       struct tree_desc desc;
+       struct tree *tree;
+       struct unpack_trees_options unpack_tree_opts;
+       int ret = 0, i;
+
+       if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)
+               return -1;
+
+       if (len == 10 && !strncmp("[new root]", name, len)) {
+               if (!opts->have_squash_onto) {
+                       const char *hex;
+                       if (commit_tree("", 0, the_hash_algo->empty_tree,
+                                       NULL, &opts->squash_onto,
+                                       NULL, NULL))
+                               return error(_("writing fake root commit"));
+                       opts->have_squash_onto = 1;
+                       hex = oid_to_hex(&opts->squash_onto);
+                       if (write_message(hex, strlen(hex),
+                                         rebase_path_squash_onto(), 0))
+                               return error(_("writing squash-onto"));
+               }
+               oidcpy(&oid, &opts->squash_onto);
+       } else {
+               /* Determine the length of the label */
+               for (i = 0; i < len; i++)
+                       if (isspace(name[i]))
+                               len = i;
+
+               strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
+               if (get_oid(ref_name.buf, &oid) &&
+                   get_oid(ref_name.buf + strlen("refs/rewritten/"), &oid)) {
+                       error(_("could not read '%s'"), ref_name.buf);
+                       rollback_lock_file(&lock);
+                       strbuf_release(&ref_name);
+                       return -1;
+               }
+       }
+
+       memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));
+       setup_unpack_trees_porcelain(&unpack_tree_opts, "reset");
+       unpack_tree_opts.head_idx = 1;
+       unpack_tree_opts.src_index = &the_index;
+       unpack_tree_opts.dst_index = &the_index;
+       unpack_tree_opts.fn = oneway_merge;
+       unpack_tree_opts.merge = 1;
+       unpack_tree_opts.update = 1;
+
+       if (read_cache_unmerged()) {
+               rollback_lock_file(&lock);
+               strbuf_release(&ref_name);
+               return error_resolve_conflict(_(action_name(opts)));
+       }
+
+       if (!fill_tree_descriptor(&desc, &oid)) {
+               error(_("failed to find tree of %s"), oid_to_hex(&oid));
+               rollback_lock_file(&lock);
+               free((void *)desc.buffer);
+               strbuf_release(&ref_name);
+               return -1;
+       }
+
+       if (unpack_trees(1, &desc, &unpack_tree_opts)) {
+               rollback_lock_file(&lock);
+               free((void *)desc.buffer);
+               strbuf_release(&ref_name);
+               return -1;
+       }
+
+       tree = parse_tree_indirect(&oid);
+       prime_cache_tree(&the_index, tree);
+
+       if (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)
+               ret = error(_("could not write index"));
+       free((void *)desc.buffer);
+
+       if (!ret)
+               ret = update_ref(reflog_message(opts, "reset", "'%.*s'",
+                                               len, name), "HEAD", &oid,
+                                NULL, 0, UPDATE_REFS_MSG_ON_ERR);
+
+       strbuf_release(&ref_name);
+       return ret;
+}
+
+static int do_merge(struct commit *commit, const char *arg, int arg_len,
+                   int flags, struct replay_opts *opts)
+{
+       int run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ?
+               EDIT_MSG | VERIFY_MSG : 0;
+       struct strbuf ref_name = STRBUF_INIT;
+       struct commit *head_commit, *merge_commit, *i;
+       struct commit_list *bases, *j, *reversed = NULL;
+       struct merge_options o;
+       int merge_arg_len, oneline_offset, can_fast_forward, ret;
+       static struct lock_file lock;
+       const char *p;
+
+       if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) {
+               ret = -1;
+               goto leave_merge;
+       }
+
+       head_commit = lookup_commit_reference_by_name("HEAD");
+       if (!head_commit) {
+               ret = error(_("cannot merge without a current revision"));
+               goto leave_merge;
+       }
+
+       oneline_offset = arg_len;
+       merge_arg_len = strcspn(arg, " \t\n");
+       p = arg + merge_arg_len;
+       p += strspn(p, " \t\n");
+       if (*p == '#' && (!p[1] || isspace(p[1]))) {
+               p += 1 + strspn(p + 1, " \t\n");
+               oneline_offset = p - arg;
+       } else if (p - arg < arg_len)
+               BUG("octopus merges are not supported yet: '%s'", p);
+
+       strbuf_addf(&ref_name, "refs/rewritten/%.*s", merge_arg_len, arg);
+       merge_commit = lookup_commit_reference_by_name(ref_name.buf);
+       if (!merge_commit) {
+               /* fall back to non-rewritten ref or commit */
+               strbuf_splice(&ref_name, 0, strlen("refs/rewritten/"), "", 0);
+               merge_commit = lookup_commit_reference_by_name(ref_name.buf);
+       }
+
+       if (!merge_commit) {
+               ret = error(_("could not resolve '%s'"), ref_name.buf);
+               goto leave_merge;
+       }
+
+       if (opts->have_squash_onto &&
+           !oidcmp(&head_commit->object.oid, &opts->squash_onto)) {
+               /*
+                * When the user tells us to "merge" something into a
+                * "[new root]", let's simply fast-forward to the merge head.
+                */
+               rollback_lock_file(&lock);
+               ret = fast_forward_to(&merge_commit->object.oid,
+                                      &head_commit->object.oid, 0, opts);
+               goto leave_merge;
+       }
+
+       if (commit) {
+               const char *message = get_commit_buffer(commit, NULL);
+               const char *body;
+               int len;
+
+               if (!message) {
+                       ret = error(_("could not get commit message of '%s'"),
+                                   oid_to_hex(&commit->object.oid));
+                       goto leave_merge;
+               }
+               write_author_script(message);
+               find_commit_subject(message, &body);
+               len = strlen(body);
+               ret = write_message(body, len, git_path_merge_msg(), 0);
+               unuse_commit_buffer(commit, message);
+               if (ret) {
+                       error_errno(_("could not write '%s'"),
+                                   git_path_merge_msg());
+                       goto leave_merge;
+               }
+       } else {
+               struct strbuf buf = STRBUF_INIT;
+               int len;
+
+               strbuf_addf(&buf, "author %s", git_author_info(0));
+               write_author_script(buf.buf);
+               strbuf_reset(&buf);
+
+               if (oneline_offset < arg_len) {
+                       p = arg + oneline_offset;
+                       len = arg_len - oneline_offset;
+               } else {
+                       strbuf_addf(&buf, "Merge branch '%.*s'",
+                                   merge_arg_len, arg);
+                       p = buf.buf;
+                       len = buf.len;
+               }
+
+               ret = write_message(p, len, git_path_merge_msg(), 0);
+               strbuf_release(&buf);
+               if (ret) {
+                       error_errno(_("could not write '%s'"),
+                                   git_path_merge_msg());
+                       goto leave_merge;
+               }
+       }
+
+       /*
+        * If HEAD is not identical to the first parent of the original merge
+        * commit, we cannot fast-forward.
+        */
+       can_fast_forward = opts->allow_ff && commit && commit->parents &&
+               !oidcmp(&commit->parents->item->object.oid,
+                       &head_commit->object.oid);
+
+       /*
+        * If the merge head is different from the original one, we cannot
+        * fast-forward.
+        */
+       if (can_fast_forward) {
+               struct commit_list *second_parent = commit->parents->next;
+
+               if (second_parent && !second_parent->next &&
+                   oidcmp(&merge_commit->object.oid,
+                          &second_parent->item->object.oid))
+                       can_fast_forward = 0;
+       }
+
+       if (can_fast_forward && commit->parents->next &&
+           !commit->parents->next->next &&
+           !oidcmp(&commit->parents->next->item->object.oid,
+                   &merge_commit->object.oid)) {
+               rollback_lock_file(&lock);
+               ret = fast_forward_to(&commit->object.oid,
+                                     &head_commit->object.oid, 0, opts);
+               goto leave_merge;
+       }
+
+       write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,
+                     git_path_merge_head(), 0);
+       write_message("no-ff", 5, git_path_merge_mode(), 0);
+
+       bases = get_merge_bases(head_commit, merge_commit);
+       if (bases && !oidcmp(&merge_commit->object.oid,
+                            &bases->item->object.oid)) {
+               ret = 0;
+               /* skip merging an ancestor of HEAD */
+               goto leave_merge;
+       }
+
+       for (j = bases; j; j = j->next)
+               commit_list_insert(j->item, &reversed);
+       free_commit_list(bases);
+
+       read_cache();
+       init_merge_options(&o);
+       o.branch1 = "HEAD";
+       o.branch2 = ref_name.buf;
+       o.buffer_output = 2;
+
+       ret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);
+       if (ret <= 0)
+               fputs(o.obuf.buf, stdout);
+       strbuf_release(&o.obuf);
+       if (ret < 0) {
+               error(_("could not even attempt to merge '%.*s'"),
+                     merge_arg_len, arg);
+               goto leave_merge;
+       }
+       /*
+        * The return value of merge_recursive() is 1 on clean, and 0 on
+        * unclean merge.
+        *
+        * Let's reverse that, so that do_merge() returns 0 upon success and
+        * 1 upon failed merge (keeping the return value -1 for the cases where
+        * we will want to reschedule the `merge` command).
+        */
+       ret = !ret;
+
+       if (active_cache_changed &&
+           write_locked_index(&the_index, &lock, COMMIT_LOCK)) {
+               ret = error(_("merge: Unable to write new index file"));
+               goto leave_merge;
+       }
+
+       rollback_lock_file(&lock);
+       if (ret)
+               rerere(opts->allow_rerere_auto);
+       else
+               /*
+                * In case of problems, we now want to return a positive
+                * value (a negative one would indicate that the `merge`
+                * command needs to be rescheduled).
+                */
+               ret = !!run_git_commit(git_path_merge_msg(), opts,
+                                    run_commit_flags);
+
+leave_merge:
+       strbuf_release(&ref_name);
+       rollback_lock_file(&lock);
+       return ret;
+}
+
 static int is_final_fixup(struct todo_list *todo_list)
 {
        int i = todo_list->current;
@@ -2546,9 +3130,20 @@ static const char *reflog_message(struct replay_opts *opts,
        return buf.buf;
 }
 
+static const char rescheduled_advice[] =
+N_("Could not execute the todo command\n"
+"\n"
+"    %.*s"
+"\n"
+"It has been rescheduled; To edit the command before continuing, please\n"
+"edit the todo list first:\n"
+"\n"
+"    git rebase --edit-todo\n"
+"    git rebase --continue\n");
+
 static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
 {
-       int res = 0;
+       int res = 0, reschedule = 0;
 
        setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
        if (opts->allow_ff)
@@ -2591,6 +3186,11 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
                                        opts, is_final_fixup(todo_list));
                        if (is_rebase_i(opts) && res < 0) {
                                /* Reschedule */
+                               advise(_(rescheduled_advice),
+                                      get_item_line_length(todo_list,
+                                                           todo_list->current),
+                                      get_item_line(todo_list,
+                                                    todo_list->current));
                                todo_list->current--;
                                if (save_todo(todo_list, opts))
                                        return -1;
@@ -2614,7 +3214,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
                                        intend_to_amend();
                                return error_failed_squash(item->commit, opts,
                                        item->arg_len, item->arg);
-                       } else if (res && is_rebase_i(opts))
+                       } else if (res && is_rebase_i(opts) && item->commit)
                                return res | error_with_patch(item->commit,
                                        item->arg, item->arg_len, opts, res,
                                        item->command == TODO_REWORD);
@@ -2640,9 +3240,44 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
                                /* `current` will be incremented below */
                                todo_list->current = -1;
                        }
+               } else if (item->command == TODO_LABEL) {
+                       if ((res = do_label(item->arg, item->arg_len)))
+                               reschedule = 1;
+               } else if (item->command == TODO_RESET) {
+                       if ((res = do_reset(item->arg, item->arg_len, opts)))
+                               reschedule = 1;
+               } else if (item->command == TODO_MERGE) {
+                       if ((res = do_merge(item->commit,
+                                           item->arg, item->arg_len,
+                                           item->flags, opts)) < 0)
+                               reschedule = 1;
+                       else if (item->commit)
+                               record_in_rewritten(&item->commit->object.oid,
+                                                   peek_command(todo_list, 1));
+                       if (res > 0)
+                               /* failed with merge conflicts */
+                               return error_with_patch(item->commit,
+                                                       item->arg,
+                                                       item->arg_len, opts,
+                                                       res, 0);
                } else if (!is_noop(item->command))
                        return error(_("unknown command %d"), item->command);
 
+               if (reschedule) {
+                       advise(_(rescheduled_advice),
+                              get_item_line_length(todo_list,
+                                                   todo_list->current),
+                              get_item_line(todo_list, todo_list->current));
+                       todo_list->current--;
+                       if (save_todo(todo_list, opts))
+                               return -1;
+                       if (item->commit)
+                               return error_with_patch(item->commit,
+                                                       item->arg,
+                                                       item->arg_len, opts,
+                                                       res, 0);
+               }
+
                todo_list->current++;
                if (res)
                        return res;
@@ -2769,19 +3404,16 @@ static int continue_single_pick(void)
        return run_command_v_opt(argv, RUN_GIT_CMD);
 }
 
-static int commit_staged_changes(struct replay_opts *opts)
+static int commit_staged_changes(struct replay_opts *opts,
+                                struct todo_list *todo_list)
 {
        unsigned int flags = ALLOW_EMPTY | EDIT_MSG;
+       unsigned int final_fixup = 0, is_clean;
 
        if (has_unstaged_changes(1))
                return error(_("cannot rebase: You have unstaged changes."));
-       if (!has_uncommitted_changes(0)) {
-               const char *cherry_pick_head = git_path_cherry_pick_head();
 
-               if (file_exists(cherry_pick_head) && unlink(cherry_pick_head))
-                       return error(_("could not remove CHERRY_PICK_HEAD"));
-               return 0;
-       }
+       is_clean = !has_uncommitted_changes(0);
 
        if (file_exists(rebase_path_amend())) {
                struct strbuf rev = STRBUF_INIT;
@@ -2794,19 +3426,107 @@ static int commit_staged_changes(struct replay_opts *opts)
                if (get_oid_hex(rev.buf, &to_amend))
                        return error(_("invalid contents: '%s'"),
                                rebase_path_amend());
-               if (oidcmp(&head, &to_amend))
+               if (!is_clean && oidcmp(&head, &to_amend))
                        return error(_("\nYou have uncommitted changes in your "
                                       "working tree. Please, commit them\n"
                                       "first and then run 'git rebase "
                                       "--continue' again."));
+               /*
+                * When skipping a failed fixup/squash, we need to edit the
+                * commit message, the current fixup list and count, and if it
+                * was the last fixup/squash in the chain, we need to clean up
+                * the commit message and if there was a squash, let the user
+                * edit it.
+                */
+               if (is_clean && !oidcmp(&head, &to_amend) &&
+                   opts->current_fixup_count > 0 &&
+                   file_exists(rebase_path_stopped_sha())) {
+                       const char *p = opts->current_fixups.buf;
+                       int len = opts->current_fixups.len;
+
+                       opts->current_fixup_count--;
+                       if (!len)
+                               BUG("Incorrect current_fixups:\n%s", p);
+                       while (len && p[len - 1] != '\n')
+                               len--;
+                       strbuf_setlen(&opts->current_fixups, len);
+                       if (write_message(p, len, rebase_path_current_fixups(),
+                                         0) < 0)
+                               return error(_("could not write file: '%s'"),
+                                            rebase_path_current_fixups());
+
+                       /*
+                        * If a fixup/squash in a fixup/squash chain failed, the
+                        * commit message is already correct, no need to commit
+                        * it again.
+                        *
+                        * Only if it is the final command in the fixup/squash
+                        * chain, and only if the chain is longer than a single
+                        * fixup/squash command (which was just skipped), do we
+                        * actually need to re-commit with a cleaned up commit
+                        * message.
+                        */
+                       if (opts->current_fixup_count > 0 &&
+                           !is_fixup(peek_command(todo_list, 0))) {
+                               final_fixup = 1;
+                               /*
+                                * If there was not a single "squash" in the
+                                * chain, we only need to clean up the commit
+                                * message, no need to bother the user with
+                                * opening the commit message in the editor.
+                                */
+                               if (!starts_with(p, "squash ") &&
+                                   !strstr(p, "\nsquash "))
+                                       flags = (flags & ~EDIT_MSG) | CLEANUP_MSG;
+                       } else if (is_fixup(peek_command(todo_list, 0))) {
+                               /*
+                                * We need to update the squash message to skip
+                                * the latest commit message.
+                                */
+                               struct commit *commit;
+                               const char *path = rebase_path_squash_msg();
+
+                               if (parse_head(&commit) ||
+                                   !(p = get_commit_buffer(commit, NULL)) ||
+                                   write_message(p, strlen(p), path, 0)) {
+                                       unuse_commit_buffer(commit, p);
+                                       return error(_("could not write file: "
+                                                      "'%s'"), path);
+                               }
+                               unuse_commit_buffer(commit, p);
+                       }
+               }
 
                strbuf_release(&rev);
                flags |= AMEND_MSG;
        }
 
-       if (run_git_commit(rebase_path_message(), opts, flags))
+       if (is_clean) {
+               const char *cherry_pick_head = git_path_cherry_pick_head();
+
+               if (file_exists(cherry_pick_head) && unlink(cherry_pick_head))
+                       return error(_("could not remove CHERRY_PICK_HEAD"));
+               if (!final_fixup)
+                       return 0;
+       }
+
+       if (run_git_commit(final_fixup ? NULL : rebase_path_message(),
+                          opts, flags))
                return error(_("could not commit staged changes."));
        unlink(rebase_path_amend());
+       if (final_fixup) {
+               unlink(rebase_path_fixup_msg());
+               unlink(rebase_path_squash_msg());
+       }
+       if (opts->current_fixup_count > 0) {
+               /*
+                * Whether final fixup or not, we just cleaned up the commit
+                * message...
+                */
+               unlink(rebase_path_current_fixups());
+               strbuf_reset(&opts->current_fixups);
+               opts->current_fixup_count = 0;
+       }
        return 0;
 }
 
@@ -2818,14 +3538,16 @@ int sequencer_continue(struct replay_opts *opts)
        if (read_and_refresh_cache(opts))
                return -1;
 
+       if (read_populate_opts(opts))
+               return -1;
        if (is_rebase_i(opts)) {
-               if (commit_staged_changes(opts))
+               if ((res = read_populate_todo(&todo_list, opts)))
+                       goto release_todo_list;
+               if (commit_staged_changes(opts, &todo_list))
                        return -1;
        } else if (!file_exists(get_todo_path(opts)))
                return continue_single_pick();
-       if (read_populate_opts(opts))
-               return -1;
-       if ((res = read_populate_todo(&todo_list, opts)))
+       else if ((res = read_populate_todo(&todo_list, opts)))
                goto release_todo_list;
 
        if (!is_rebase_i(opts)) {
@@ -2884,7 +3606,8 @@ int sequencer_pick_revisions(struct replay_opts *opts)
 
                if (!get_oid(name, &oid)) {
                        if (!lookup_commit_reference_gently(&oid, 1)) {
-                               enum object_type type = oid_object_info(&oid,
+                               enum object_type type = oid_object_info(the_repository,
+                                                                       &oid,
                                                                        NULL);
                                return error(_("%s: can't cherry-pick a %s"),
                                        name, type_name(type));
@@ -2995,6 +3718,348 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)
        strbuf_release(&sob);
 }
 
+struct labels_entry {
+       struct hashmap_entry entry;
+       char label[FLEX_ARRAY];
+};
+
+static int labels_cmp(const void *fndata, const struct labels_entry *a,
+                     const struct labels_entry *b, const void *key)
+{
+       return key ? strcmp(a->label, key) : strcmp(a->label, b->label);
+}
+
+struct string_entry {
+       struct oidmap_entry entry;
+       char string[FLEX_ARRAY];
+};
+
+struct label_state {
+       struct oidmap commit2label;
+       struct hashmap labels;
+       struct strbuf buf;
+};
+
+static const char *label_oid(struct object_id *oid, const char *label,
+                            struct label_state *state)
+{
+       struct labels_entry *labels_entry;
+       struct string_entry *string_entry;
+       struct object_id dummy;
+       size_t len;
+       int i;
+
+       string_entry = oidmap_get(&state->commit2label, oid);
+       if (string_entry)
+               return string_entry->string;
+
+       /*
+        * For "uninteresting" commits, i.e. commits that are not to be
+        * rebased, and which can therefore not be labeled, we use a unique
+        * abbreviation of the commit name. This is slightly more complicated
+        * than calling find_unique_abbrev() because we also need to make
+        * sure that the abbreviation does not conflict with any other
+        * label.
+        *
+        * We disallow "interesting" commits to be labeled by a string that
+        * is a valid full-length hash, to ensure that we always can find an
+        * abbreviation for any uninteresting commit's names that does not
+        * clash with any other label.
+        */
+       if (!label) {
+               char *p;
+
+               strbuf_reset(&state->buf);
+               strbuf_grow(&state->buf, GIT_SHA1_HEXSZ);
+               label = p = state->buf.buf;
+
+               find_unique_abbrev_r(p, oid, default_abbrev);
+
+               /*
+                * We may need to extend the abbreviated hash so that there is
+                * no conflicting label.
+                */
+               if (hashmap_get_from_hash(&state->labels, strihash(p), p)) {
+                       size_t i = strlen(p) + 1;
+
+                       oid_to_hex_r(p, oid);
+                       for (; i < GIT_SHA1_HEXSZ; i++) {
+                               char save = p[i];
+                               p[i] = '\0';
+                               if (!hashmap_get_from_hash(&state->labels,
+                                                          strihash(p), p))
+                                       break;
+                               p[i] = save;
+                       }
+               }
+       } else if (((len = strlen(label)) == the_hash_algo->hexsz &&
+                   !get_oid_hex(label, &dummy)) ||
+                  (len == 1 && *label == '#') ||
+                  hashmap_get_from_hash(&state->labels,
+                                        strihash(label), label)) {
+               /*
+                * If the label already exists, or if the label is a valid full
+                * OID, or the label is a '#' (which we use as a separator
+                * between merge heads and oneline), we append a dash and a
+                * number to make it unique.
+                */
+               struct strbuf *buf = &state->buf;
+
+               strbuf_reset(buf);
+               strbuf_add(buf, label, len);
+
+               for (i = 2; ; i++) {
+                       strbuf_setlen(buf, len);
+                       strbuf_addf(buf, "-%d", i);
+                       if (!hashmap_get_from_hash(&state->labels,
+                                                  strihash(buf->buf),
+                                                  buf->buf))
+                               break;
+               }
+
+               label = buf->buf;
+       }
+
+       FLEX_ALLOC_STR(labels_entry, label, label);
+       hashmap_entry_init(labels_entry, strihash(label));
+       hashmap_add(&state->labels, labels_entry);
+
+       FLEX_ALLOC_STR(string_entry, string, label);
+       oidcpy(&string_entry->entry.oid, oid);
+       oidmap_put(&state->commit2label, string_entry);
+
+       return string_entry->string;
+}
+
+static int make_script_with_merges(struct pretty_print_context *pp,
+                                  struct rev_info *revs, FILE *out,
+                                  unsigned flags)
+{
+       int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
+       int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
+       struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
+       struct strbuf label = STRBUF_INIT;
+       struct commit_list *commits = NULL, **tail = &commits, *iter;
+       struct commit_list *tips = NULL, **tips_tail = &tips;
+       struct commit *commit;
+       struct oidmap commit2todo = OIDMAP_INIT;
+       struct string_entry *entry;
+       struct oidset interesting = OIDSET_INIT, child_seen = OIDSET_INIT,
+               shown = OIDSET_INIT;
+       struct label_state state = { OIDMAP_INIT, { NULL }, STRBUF_INIT };
+
+       int abbr = flags & TODO_LIST_ABBREVIATE_CMDS;
+       const char *cmd_pick = abbr ? "p" : "pick",
+               *cmd_label = abbr ? "l" : "label",
+               *cmd_reset = abbr ? "t" : "reset",
+               *cmd_merge = abbr ? "m" : "merge";
+
+       oidmap_init(&commit2todo, 0);
+       oidmap_init(&state.commit2label, 0);
+       hashmap_init(&state.labels, (hashmap_cmp_fn) labels_cmp, NULL, 0);
+       strbuf_init(&state.buf, 32);
+
+       if (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) {
+               struct object_id *oid = &revs->cmdline.rev[0].item->oid;
+               FLEX_ALLOC_STR(entry, string, "onto");
+               oidcpy(&entry->entry.oid, oid);
+               oidmap_put(&state.commit2label, entry);
+       }
+
+       /*
+        * First phase:
+        * - get onelines for all commits
+        * - gather all branch tips (i.e. 2nd or later parents of merges)
+        * - label all branch tips
+        */
+       while ((commit = get_revision(revs))) {
+               struct commit_list *to_merge;
+               int is_octopus;
+               const char *p1, *p2;
+               struct object_id *oid;
+               int is_empty;
+
+               tail = &commit_list_insert(commit, tail)->next;
+               oidset_insert(&interesting, &commit->object.oid);
+
+               is_empty = is_original_commit_empty(commit);
+               if (!is_empty && (commit->object.flags & PATCHSAME))
+                       continue;
+
+               strbuf_reset(&oneline);
+               pretty_print_commit(pp, commit, &oneline);
+
+               to_merge = commit->parents ? commit->parents->next : NULL;
+               if (!to_merge) {
+                       /* non-merge commit: easy case */
+                       strbuf_reset(&buf);
+                       if (!keep_empty && is_empty)
+                               strbuf_addf(&buf, "%c ", comment_line_char);
+                       strbuf_addf(&buf, "%s %s %s", cmd_pick,
+                                   oid_to_hex(&commit->object.oid),
+                                   oneline.buf);
+
+                       FLEX_ALLOC_STR(entry, string, buf.buf);
+                       oidcpy(&entry->entry.oid, &commit->object.oid);
+                       oidmap_put(&commit2todo, entry);
+
+                       continue;
+               }
+
+               is_octopus = to_merge && to_merge->next;
+
+               if (is_octopus)
+                       BUG("Octopus merges not yet supported");
+
+               /* Create a label */
+               strbuf_reset(&label);
+               if (skip_prefix(oneline.buf, "Merge ", &p1) &&
+                   (p1 = strchr(p1, '\'')) &&
+                   (p2 = strchr(++p1, '\'')))
+                       strbuf_add(&label, p1, p2 - p1);
+               else if (skip_prefix(oneline.buf, "Merge pull request ",
+                                    &p1) &&
+                        (p1 = strstr(p1, " from ")))
+                       strbuf_addstr(&label, p1 + strlen(" from "));
+               else
+                       strbuf_addbuf(&label, &oneline);
+
+               for (p1 = label.buf; *p1; p1++)
+                       if (isspace(*p1))
+                               *(char *)p1 = '-';
+
+               strbuf_reset(&buf);
+               strbuf_addf(&buf, "%s -C %s",
+                           cmd_merge, oid_to_hex(&commit->object.oid));
+
+               /* label the tip of merged branch */
+               oid = &to_merge->item->object.oid;
+               strbuf_addch(&buf, ' ');
+
+               if (!oidset_contains(&interesting, oid))
+                       strbuf_addstr(&buf, label_oid(oid, NULL, &state));
+               else {
+                       tips_tail = &commit_list_insert(to_merge->item,
+                                                       tips_tail)->next;
+
+                       strbuf_addstr(&buf, label_oid(oid, label.buf, &state));
+               }
+               strbuf_addf(&buf, " # %s", oneline.buf);
+
+               FLEX_ALLOC_STR(entry, string, buf.buf);
+               oidcpy(&entry->entry.oid, &commit->object.oid);
+               oidmap_put(&commit2todo, entry);
+       }
+
+       /*
+        * Second phase:
+        * - label branch points
+        * - add HEAD to the branch tips
+        */
+       for (iter = commits; iter; iter = iter->next) {
+               struct commit_list *parent = iter->item->parents;
+               for (; parent; parent = parent->next) {
+                       struct object_id *oid = &parent->item->object.oid;
+                       if (!oidset_contains(&interesting, oid))
+                               continue;
+                       if (!oidset_contains(&child_seen, oid))
+                               oidset_insert(&child_seen, oid);
+                       else
+                               label_oid(oid, "branch-point", &state);
+               }
+
+               /* Add HEAD as implict "tip of branch" */
+               if (!iter->next)
+                       tips_tail = &commit_list_insert(iter->item,
+                                                       tips_tail)->next;
+       }
+
+       /*
+        * Third phase: output the todo list. This is a bit tricky, as we
+        * want to avoid jumping back and forth between revisions. To
+        * accomplish that goal, we walk backwards from the branch tips,
+        * gathering commits not yet shown, reversing the list on the fly,
+        * then outputting that list (labeling revisions as needed).
+        */
+       fprintf(out, "%s onto\n", cmd_label);
+       for (iter = tips; iter; iter = iter->next) {
+               struct commit_list *list = NULL, *iter2;
+
+               commit = iter->item;
+               if (oidset_contains(&shown, &commit->object.oid))
+                       continue;
+               entry = oidmap_get(&state.commit2label, &commit->object.oid);
+
+               if (entry)
+                       fprintf(out, "\n# Branch %s\n", entry->string);
+               else
+                       fprintf(out, "\n");
+
+               while (oidset_contains(&interesting, &commit->object.oid) &&
+                      !oidset_contains(&shown, &commit->object.oid)) {
+                       commit_list_insert(commit, &list);
+                       if (!commit->parents) {
+                               commit = NULL;
+                               break;
+                       }
+                       commit = commit->parents->item;
+               }
+
+               if (!commit)
+                       fprintf(out, "%s %s\n", cmd_reset,
+                               rebase_cousins ? "onto" : "[new root]");
+               else {
+                       const char *to = NULL;
+
+                       entry = oidmap_get(&state.commit2label,
+                                          &commit->object.oid);
+                       if (entry)
+                               to = entry->string;
+                       else if (!rebase_cousins)
+                               to = label_oid(&commit->object.oid, NULL,
+                                              &state);
+
+                       if (!to || !strcmp(to, "onto"))
+                               fprintf(out, "%s onto\n", cmd_reset);
+                       else {
+                               strbuf_reset(&oneline);
+                               pretty_print_commit(pp, commit, &oneline);
+                               fprintf(out, "%s %s # %s\n",
+                                       cmd_reset, to, oneline.buf);
+                       }
+               }
+
+               for (iter2 = list; iter2; iter2 = iter2->next) {
+                       struct object_id *oid = &iter2->item->object.oid;
+                       entry = oidmap_get(&commit2todo, oid);
+                       /* only show if not already upstream */
+                       if (entry)
+                               fprintf(out, "%s\n", entry->string);
+                       entry = oidmap_get(&state.commit2label, oid);
+                       if (entry)
+                               fprintf(out, "%s %s\n",
+                                       cmd_label, entry->string);
+                       oidset_insert(&shown, oid);
+               }
+
+               free_commit_list(list);
+       }
+
+       free_commit_list(commits);
+       free_commit_list(tips);
+
+       strbuf_release(&label);
+       strbuf_release(&oneline);
+       strbuf_release(&buf);
+
+       oidmap_free(&commit2todo, 1);
+       oidmap_free(&state.commit2label, 1);
+       hashmap_free(&state.labels, 1);
+       strbuf_release(&state.buf);
+
+       return 0;
+}
+
 int sequencer_make_script(FILE *out, int argc, const char **argv,
                          unsigned flags)
 {
@@ -3005,10 +4070,12 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,
        struct commit *commit;
        int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
        const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
+       int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
 
        init_revisions(&revs, NULL);
        revs.verbose_header = 1;
-       revs.max_parents = 1;
+       if (!rebase_merges)
+               revs.max_parents = 1;
        revs.cherry_mark = 1;
        revs.limited = 1;
        revs.reverse = 1;
@@ -3033,6 +4100,9 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,
        if (prepare_revision_walk(&revs) < 0)
                return error(_("make_script: error preparing revisions"));
 
+       if (rebase_merges)
+               return make_script_with_merges(&pp, &revs, out, flags);
+
        while ((commit = get_revision(&revs))) {
                int is_empty  = is_original_commit_empty(commit);
 
@@ -3126,8 +4196,16 @@ int transform_todos(unsigned flags)
                                          short_commit_name(item->commit) :
                                          oid_to_hex(&item->commit->object.oid);
 
+                       if (item->command == TODO_MERGE) {
+                               if (item->flags & TODO_EDIT_MERGE_MSG)
+                                       strbuf_addstr(&buf, " -c");
+                               else
+                                       strbuf_addstr(&buf, " -C");
+                       }
+
                        strbuf_addf(&buf, " %s", oid);
                }
+
                /* add all the rest */
                if (!item->arg_len)
                        strbuf_addch(&buf, '\n');
@@ -3307,8 +4385,7 @@ int skip_unnecessary_picks(void)
                oid = &item->commit->object.oid;
        }
        if (i > 0) {
-               int offset = i < todo_list.nr ?
-                       todo_list.items[i].offset_in_buf : todo_list.buf.len;
+               int offset = get_item_line_offset(&todo_list, i);
                const char *done_path = rebase_path_done();
 
                fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666);
@@ -3403,7 +4480,7 @@ int rearrange_squash(void)
                struct subject2item_entry *entry;
 
                next[i] = tail[i] = -1;
-               if (item->command >= TODO_EXEC) {
+               if (!item->commit || item->command == TODO_DROP) {
                        subjects[i] = NULL;
                        continue;
                }
@@ -3488,12 +4565,10 @@ int rearrange_squash(void)
                                continue;
 
                        while (cur >= 0) {
-                               int offset = todo_list.items[cur].offset_in_buf;
-                               int end_offset = cur + 1 < todo_list.nr ?
-                                       todo_list.items[cur + 1].offset_in_buf :
-                                       todo_list.buf.len;
-                               char *bol = todo_list.buf.buf + offset;
-                               char *eol = todo_list.buf.buf + end_offset;
+                               const char *bol =
+                                       get_item_line(&todo_list, cur);
+                               const char *eol =
+                                       get_item_line(&todo_list, cur + 1);
 
                                /* replace 'pick', by 'fixup' or 'squash' */
                                command = todo_list.items[cur].command;
index e45b178dfc41d723bf186f20674c4515d7c7fa00..c5787c6b566bbc89caad1a099f4281fecba01766 100644 (file)
@@ -44,10 +44,18 @@ struct replay_opts {
        char **xopts;
        size_t xopts_nr, xopts_alloc;
 
+       /* Used by fixup/squash */
+       struct strbuf current_fixups;
+       int current_fixup_count;
+
+       /* placeholder commit for -i --root */
+       struct object_id squash_onto;
+       int have_squash_onto;
+
        /* Only used by REPLAY_NONE */
        struct rev_info *revs;
 };
-#define REPLAY_OPTS_INIT { -1 }
+#define REPLAY_OPTS_INIT { .action = -1, .current_fixups = STRBUF_INIT }
 
 /* Call this to setup defaults before parsing command line options */
 void sequencer_init_config(struct replay_opts *opts);
@@ -59,6 +67,13 @@ int sequencer_remove_state(struct replay_opts *opts);
 #define TODO_LIST_KEEP_EMPTY (1U << 0)
 #define TODO_LIST_SHORTEN_IDS (1U << 1)
 #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)
+#define TODO_LIST_REBASE_MERGES (1U << 3)
+/*
+ * When rebasing merges, commits that do have the base commit as ancestor
+ * ("cousins") are *not* rebased onto the new base by default. If those
+ * commits should be rebased onto the new base, this flag needs to be passed.
+ */
+#define TODO_LIST_REBASE_COUSINS (1U << 4)
 int sequencer_make_script(FILE *out, int argc, const char **argv,
                          unsigned flags);
 
diff --git a/serve.c b/serve.c
index a5a7b2f7dd243c9dda3a29eda37aca8f96c3f0a5..bda085f09c8e10314c2c497dbca978fd9241e7b5 100644 (file)
--- a/serve.c
+++ b/serve.c
@@ -56,6 +56,7 @@ static struct protocol_capability capabilities[] = {
        { "agent", agent_advertise, NULL },
        { "ls-refs", always_advertise, ls_refs },
        { "fetch", upload_pack_advertise, upload_pack_v2 },
+       { "server-option", always_advertise, NULL },
 };
 
 static void advertise_capabilities(void)
index 83460ec0d6f10b26719c076639a08a8a0ce803cc..7ce6dcd67b7bef92f79bc24c319b2e46150980a3 100644 (file)
@@ -92,8 +92,6 @@ static struct pack_info {
        int old_num;
        int new_num;
        int nr_alloc;
-       int nr_heads;
-       unsigned char (*head)[20];
 } **info;
 static int num_pack;
 static const char *objdir;
@@ -225,12 +223,9 @@ static void init_pack_info(const char *infofile, int force)
        else
                stale = 1;
 
-       for (i = 0; i < num_pack; i++) {
-               if (stale) {
+       for (i = 0; i < num_pack; i++)
+               if (stale)
                        info[i]->old_num = -1;
-                       info[i]->nr_heads = 0;
-               }
-       }
 
        /* renumber them */
        QSORT(info, num_pack, compare_info);
diff --git a/setup.c b/setup.c
index 3e03d442b6fad10c1b11fb8e8626f3c4fe444298..b24c811c1c3129c8f11fa0fffa4e937705792e36 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -539,7 +539,7 @@ void read_gitfile_error_die(int error_code, const char *path, const char *dir)
        case READ_GITFILE_ERR_NOT_A_REPO:
                die(_("not a git repository: %s"), dir);
        default:
-               die("BUG: unknown error code");
+               BUG("unknown error code");
        }
 }
 
@@ -1086,7 +1086,7 @@ const char *setup_git_directory_gently(int *nongit_ok)
                      "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set)."),
                    dir.buf);
        default:
-               die("BUG: unhandled setup_git_directory_1() result");
+               BUG("unhandled setup_git_directory_1() result");
        }
 
        if (prefix)
index 838b3bf8478cfdfcd78f866e69dc0f8c5cbf3c9e..265941fbf40d4a6c64bb1e77b2aef7a5950493a7 100644 (file)
@@ -41,9 +41,26 @@ void oid_array_clear(struct oid_array *array)
        array->sorted = 0;
 }
 
+
+int oid_array_for_each(struct oid_array *array,
+                      for_each_oid_fn fn,
+                      void *data)
+{
+       int i;
+
+       /* No oid_array_sort() here! See the api-oid-array.txt docs! */
+
+       for (i = 0; i < array->nr; i++) {
+               int ret = fn(array->oid + i, data);
+               if (ret)
+                       return ret;
+       }
+       return 0;
+}
+
 int oid_array_for_each_unique(struct oid_array *array,
-                               for_each_oid_fn fn,
-                               void *data)
+                             for_each_oid_fn fn,
+                             void *data)
 {
        int i;
 
index 04b0756334da7adf1e3eefb3f521f8c95d263046..232bf9501729696846f514e13ce5f23c425d9d27 100644 (file)
@@ -16,8 +16,11 @@ void oid_array_clear(struct oid_array *array);
 
 typedef int (*for_each_oid_fn)(const struct object_id *oid,
                               void *data);
+int oid_array_for_each(struct oid_array *array,
+                      for_each_oid_fn fn,
+                      void *data);
 int oid_array_for_each_unique(struct oid_array *array,
-                              for_each_oid_fn fn,
-                              void *data);
+                             for_each_oid_fn fn,
+                             void *data);
 
 #endif /* SHA1_ARRAY_H */
index 46072602fff00bc08960f08568e8bdb84255c60e..555e780f4b61b8b96b12549de341cb2d827e039e 100644 (file)
 /* The maximum size for an object header. */
 #define MAX_HEADER_LEN 32
 
+
+#define EMPTY_TREE_SHA1_BIN_LITERAL \
+        "\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60" \
+        "\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04"
+
+#define EMPTY_BLOB_SHA1_BIN_LITERAL \
+       "\xe6\x9d\xe2\x9b\xb2\xd1\xd6\x43\x4b\x8b" \
+       "\x29\xae\x77\x5a\xd8\xc2\xe4\x8c\x53\x91"
+
 const unsigned char null_sha1[GIT_MAX_RAWSZ];
 const struct object_id null_oid;
-const struct object_id empty_tree_oid = {
+static const struct object_id empty_tree_oid = {
        EMPTY_TREE_SHA1_BIN_LITERAL
 };
-const struct object_id empty_blob_oid = {
+static const struct object_id empty_blob_oid = {
        EMPTY_BLOB_SHA1_BIN_LITERAL
 };
 
@@ -101,6 +110,18 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
        },
 };
 
+const char *empty_tree_oid_hex(void)
+{
+       static char buf[GIT_MAX_HEXSZ + 1];
+       return oid_to_hex_r(buf, the_hash_algo->empty_tree);
+}
+
+const char *empty_blob_oid_hex(void)
+{
+       static char buf[GIT_MAX_HEXSZ + 1];
+       return oid_to_hex_r(buf, the_hash_algo->empty_blob);
+}
+
 /*
  * This is meant to hold a *small* number of objects that you would
  * want read_sha1_file() to be able to return, but yet you do not want
@@ -108,7 +129,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
  * application).
  */
 static struct cached_object {
-       unsigned char sha1[20];
+       struct object_id oid;
        enum object_type type;
        void *buf;
        unsigned long size;
@@ -116,22 +137,22 @@ static struct cached_object {
 static int cached_object_nr, cached_object_alloc;
 
 static struct cached_object empty_tree = {
-       EMPTY_TREE_SHA1_BIN_LITERAL,
+       { EMPTY_TREE_SHA1_BIN_LITERAL },
        OBJ_TREE,
        "",
        0
 };
 
-static struct cached_object *find_cached_object(const unsigned char *sha1)
+static struct cached_object *find_cached_object(const struct object_id *oid)
 {
        int i;
        struct cached_object *co = cached_objects;
 
        for (i = 0; i < cached_object_nr; i++, co++) {
-               if (!hashcmp(co->sha1, sha1))
+               if (!oidcmp(&co->oid, oid))
                        return co;
        }
-       if (!hashcmp(sha1, empty_tree.sha1))
+       if (!oidcmp(oid, the_hash_algo->empty_tree))
                return &empty_tree;
        return NULL;
 }
@@ -710,42 +731,42 @@ int check_and_freshen_file(const char *fn, int freshen)
        return 1;
 }
 
-static int check_and_freshen_local(const unsigned char *sha1, int freshen)
+static int check_and_freshen_local(const struct object_id *oid, int freshen)
 {
        static struct strbuf buf = STRBUF_INIT;
 
        strbuf_reset(&buf);
-       sha1_file_name(the_repository, &buf, sha1);
+       sha1_file_name(the_repository, &buf, oid->hash);
 
        return check_and_freshen_file(buf.buf, freshen);
 }
 
-static int check_and_freshen_nonlocal(const unsigned char *sha1, int freshen)
+static int check_and_freshen_nonlocal(const struct object_id *oid, int freshen)
 {
        struct alternate_object_database *alt;
        prepare_alt_odb(the_repository);
        for (alt = the_repository->objects->alt_odb_list; alt; alt = alt->next) {
-               const char *path = alt_sha1_path(alt, sha1);
+               const char *path = alt_sha1_path(alt, oid->hash);
                if (check_and_freshen_file(path, freshen))
                        return 1;
        }
        return 0;
 }
 
-static int check_and_freshen(const unsigned char *sha1, int freshen)
+static int check_and_freshen(const struct object_id *oid, int freshen)
 {
-       return check_and_freshen_local(sha1, freshen) ||
-              check_and_freshen_nonlocal(sha1, freshen);
+       return check_and_freshen_local(oid, freshen) ||
+              check_and_freshen_nonlocal(oid, freshen);
 }
 
-int has_loose_object_nonlocal(const unsigned char *sha1)
+int has_loose_object_nonlocal(const struct object_id *oid)
 {
-       return check_and_freshen_nonlocal(sha1, 0);
+       return check_and_freshen_nonlocal(oid, 0);
 }
 
-static int has_loose_object(const unsigned char *sha1)
+static int has_loose_object(const struct object_id *oid)
 {
-       return check_and_freshen(sha1, 0);
+       return check_and_freshen(oid, 0);
 }
 
 static void mmap_limit_check(size_t length)
@@ -1231,7 +1252,8 @@ static int sha1_loose_object_info(struct repository *r,
 
 int fetch_if_missing = 1;
 
-int oid_object_info_extended(const struct object_id *oid, struct object_info *oi, unsigned flags)
+int oid_object_info_extended(struct repository *r, const struct object_id *oid,
+                            struct object_info *oi, unsigned flags)
 {
        static struct object_info blank_oi = OBJECT_INFO_INIT;
        struct pack_entry e;
@@ -1240,7 +1262,7 @@ int oid_object_info_extended(const struct object_id *oid, struct object_info *oi
        int already_retried = 0;
 
        if (flags & OBJECT_INFO_LOOKUP_REPLACE)
-               real = lookup_replace_object(the_repository, oid);
+               real = lookup_replace_object(r, oid);
 
        if (is_null_oid(real))
                return -1;
@@ -1249,7 +1271,7 @@ int oid_object_info_extended(const struct object_id *oid, struct object_info *oi
                oi = &blank_oi;
 
        if (!(flags & OBJECT_INFO_SKIP_CACHED)) {
-               struct cached_object *co = find_cached_object(real->hash);
+               struct cached_object *co = find_cached_object(real);
                if (co) {
                        if (oi->typep)
                                *(oi->typep) = co->type;
@@ -1269,29 +1291,31 @@ int oid_object_info_extended(const struct object_id *oid, struct object_info *oi
        }
 
        while (1) {
-               if (find_pack_entry(the_repository, real->hash, &e))
+               if (find_pack_entry(r, real, &e))
                        break;
 
                if (flags & OBJECT_INFO_IGNORE_LOOSE)
                        return -1;
 
                /* Most likely it's a loose object. */
-               if (!sha1_loose_object_info(the_repository, real->hash, oi, flags))
+               if (!sha1_loose_object_info(r, real->hash, oi, flags))
                        return 0;
 
                /* Not a loose object; someone else may have just packed it. */
                if (!(flags & OBJECT_INFO_QUICK)) {
-                       reprepare_packed_git(the_repository);
-                       if (find_pack_entry(the_repository, real->hash, &e))
+                       reprepare_packed_git(r);
+                       if (find_pack_entry(r, real, &e))
                                break;
                }
 
                /* Check if it is a missing object */
                if (fetch_if_missing && repository_format_partial_clone &&
-                   !already_retried) {
+                   !already_retried && r == the_repository) {
                        /*
-                        * TODO Investigate haveing fetch_object() return
+                        * TODO Investigate having fetch_object() return
                         * TODO error/success and stopping the music here.
+                        * TODO Pass a repository struct through fetch_object,
+                        * such that arbitrary repositories work.
                         */
                        fetch_object(repository_format_partial_clone, real->hash);
                        already_retried = 1;
@@ -1307,10 +1331,10 @@ int oid_object_info_extended(const struct object_id *oid, struct object_info *oi
                 * information below, so return early.
                 */
                return 0;
-       rtype = packed_object_info(e.p, e.offset, oi);
+       rtype = packed_object_info(r, e.p, e.offset, oi);
        if (rtype < 0) {
                mark_bad_packed_object(e.p, real->hash);
-               return oid_object_info_extended(real, oi, 0);
+               return oid_object_info_extended(r, real, oi, 0);
        } else if (oi->whence == OI_PACKED) {
                oi->u.packed.offset = e.offset;
                oi->u.packed.pack = e.p;
@@ -1322,15 +1346,17 @@ int oid_object_info_extended(const struct object_id *oid, struct object_info *oi
 }
 
 /* returns enum object_type or negative */
-int oid_object_info(const struct object_id *oid, unsigned long *sizep)
+int oid_object_info(struct repository *r,
+                   const struct object_id *oid,
+                   unsigned long *sizep)
 {
        enum object_type type;
        struct object_info oi = OBJECT_INFO_INIT;
 
        oi.typep = &type;
        oi.sizep = sizep;
-       if (oid_object_info_extended(oid, &oi,
-                                    OBJECT_INFO_LOOKUP_REPLACE) < 0)
+       if (oid_object_info_extended(r, oid, &oi,
+                                     OBJECT_INFO_LOOKUP_REPLACE) < 0)
                return -1;
        return type;
 }
@@ -1347,7 +1373,7 @@ static void *read_object(const unsigned char *sha1, enum object_type *type,
 
        hashcpy(oid.hash, sha1);
 
-       if (oid_object_info_extended(&oid, &oi, 0) < 0)
+       if (oid_object_info_extended(the_repository, &oid, &oi, 0) < 0)
                return NULL;
        return content;
 }
@@ -1358,7 +1384,7 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,
        struct cached_object *co;
 
        hash_object_file(buf, len, type_name(type), oid);
-       if (has_sha1_file(oid->hash) || find_cached_object(oid->hash))
+       if (has_sha1_file(oid->hash) || find_cached_object(oid))
                return 0;
        ALLOC_GROW(cached_objects, cached_object_nr + 1, cached_object_alloc);
        co = &cached_objects[cached_object_nr++];
@@ -1366,7 +1392,7 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,
        co->type = type;
        co->buf = xmalloc(len);
        memcpy(co->buf, buf, len);
-       hashcpy(co->sha1, oid->hash);
+       oidcpy(&co->oid, oid);
        return 0;
 }
 
@@ -1662,15 +1688,15 @@ static int write_loose_object(const struct object_id *oid, char *hdr,
        return finalize_object_file(tmp_file.buf, filename.buf);
 }
 
-static int freshen_loose_object(const unsigned char *sha1)
+static int freshen_loose_object(const struct object_id *oid)
 {
-       return check_and_freshen(sha1, 1);
+       return check_and_freshen(oid, 1);
 }
 
-static int freshen_packed_object(const unsigned char *sha1)
+static int freshen_packed_object(const struct object_id *oid)
 {
        struct pack_entry e;
-       if (!find_pack_entry(the_repository, sha1, &e))
+       if (!find_pack_entry(the_repository, oid, &e))
                return 0;
        if (e.p->freshened)
                return 1;
@@ -1690,7 +1716,7 @@ int write_object_file(const void *buf, unsigned long len, const char *type,
         * it out into .git/objects/??/?{38} file.
         */
        write_object_file_prepare(buf, len, type, oid, hdr, &hdrlen);
-       if (freshen_packed_object(oid->hash) || freshen_loose_object(oid->hash))
+       if (freshen_packed_object(oid) || freshen_loose_object(oid))
                return 0;
        return write_loose_object(oid, hdr, hdrlen, buf, len, 0);
 }
@@ -1709,7 +1735,7 @@ int hash_object_file_literally(const void *buf, unsigned long len,
 
        if (!(flags & HASH_WRITE_OBJECT))
                goto cleanup;
-       if (freshen_packed_object(oid->hash) || freshen_loose_object(oid->hash))
+       if (freshen_packed_object(oid) || freshen_loose_object(oid))
                goto cleanup;
        status = write_loose_object(oid, header, hdrlen, buf, len, 0);
 
@@ -1727,7 +1753,7 @@ int force_object_loose(const struct object_id *oid, time_t mtime)
        int hdrlen;
        int ret;
 
-       if (has_loose_object(oid->hash))
+       if (has_loose_object(oid))
                return 0;
        buf = read_object(oid->hash, &type, &len);
        if (!buf)
@@ -1745,7 +1771,7 @@ int has_sha1_file_with_flags(const unsigned char *sha1, int flags)
        if (!startup_info->have_repository)
                return 0;
        hashcpy(oid.hash, sha1);
-       return oid_object_info_extended(&oid, NULL,
+       return oid_object_info_extended(the_repository, &oid, NULL,
                                        flags | OBJECT_INFO_SKIP_CACHED) >= 0;
 }
 
@@ -1988,7 +2014,7 @@ int read_pack_header(int fd, struct pack_header *header)
 
 void assert_oid_type(const struct object_id *oid, enum object_type expect)
 {
-       enum object_type type = oid_object_info(oid, NULL);
+       enum object_type type = oid_object_info(the_repository, oid, NULL);
        if (type < 0)
                die("%s is not a valid object", oid_to_hex(oid));
        if (type != expect)
@@ -2227,7 +2253,7 @@ int read_loose_object(const char *path,
                goto out;
        }
 
-       if (*type == OBJ_BLOB) {
+       if (*type == OBJ_BLOB && *size > big_file_threshold) {
                if (check_stream_sha1(&stream, hdr, *size, path, expected_oid->hash) < 0)
                        goto out;
        } else {
index 8d0b1db3e27c99b80faa2867565de237581802f4..796ab68da83c3a36ec87fa45bfa18f5d0508a947 100644 (file)
@@ -81,7 +81,7 @@ int sha1_pos(const unsigned char *sha1, void *table, size_t nr,
                                mi = (nr - 1) * (miv - lov) / (hiv - lov);
                                if (lo <= mi && mi < hi)
                                        break;
-                               die("BUG: assertion failed in binary search");
+                               BUG("assertion failed in binary search");
                        }
                }
        }
index 5b93bf8da36939376b506f96624f568875397969..60d9ef3c7e7108c859647656972c171cce4e7d7f 100644 (file)
@@ -223,7 +223,7 @@ static int finish_object_disambiguation(struct disambiguate_state *ds,
 
 static int disambiguate_commit_only(const struct object_id *oid, void *cb_data_unused)
 {
-       int kind = oid_object_info(oid, NULL);
+       int kind = oid_object_info(the_repository, oid, NULL);
        return kind == OBJ_COMMIT;
 }
 
@@ -232,7 +232,7 @@ static int disambiguate_committish_only(const struct object_id *oid, void *cb_da
        struct object *obj;
        int kind;
 
-       kind = oid_object_info(oid, NULL);
+       kind = oid_object_info(the_repository, oid, NULL);
        if (kind == OBJ_COMMIT)
                return 1;
        if (kind != OBJ_TAG)
@@ -247,7 +247,7 @@ static int disambiguate_committish_only(const struct object_id *oid, void *cb_da
 
 static int disambiguate_tree_only(const struct object_id *oid, void *cb_data_unused)
 {
-       int kind = oid_object_info(oid, NULL);
+       int kind = oid_object_info(the_repository, oid, NULL);
        return kind == OBJ_TREE;
 }
 
@@ -256,7 +256,7 @@ static int disambiguate_treeish_only(const struct object_id *oid, void *cb_data_
        struct object *obj;
        int kind;
 
-       kind = oid_object_info(oid, NULL);
+       kind = oid_object_info(the_repository, oid, NULL);
        if (kind == OBJ_TREE || kind == OBJ_COMMIT)
                return 1;
        if (kind != OBJ_TAG)
@@ -271,7 +271,7 @@ static int disambiguate_treeish_only(const struct object_id *oid, void *cb_data_
 
 static int disambiguate_blob_only(const struct object_id *oid, void *cb_data_unused)
 {
-       int kind = oid_object_info(oid, NULL);
+       int kind = oid_object_info(the_repository, oid, NULL);
        return kind == OBJ_BLOB;
 }
 
@@ -346,11 +346,10 @@ static int show_ambiguous_object(const struct object_id *oid, void *data)
        struct strbuf desc = STRBUF_INIT;
        int type;
 
-
        if (ds->fn && !ds->fn(oid, ds->cb_data))
                return 0;
 
-       type = oid_object_info(oid, NULL);
+       type = oid_object_info(the_repository, oid, NULL);
        if (type == OBJ_COMMIT) {
                struct commit *commit = lookup_commit(oid);
                if (commit) {
@@ -373,6 +372,40 @@ static int show_ambiguous_object(const struct object_id *oid, void *data)
        return 0;
 }
 
+static int collect_ambiguous(const struct object_id *oid, void *data)
+{
+       oid_array_append(data, oid);
+       return 0;
+}
+
+static int sort_ambiguous(const void *a, const void *b)
+{
+       int a_type = oid_object_info(the_repository, a, NULL);
+       int b_type = oid_object_info(the_repository, b, NULL);
+       int a_type_sort;
+       int b_type_sort;
+
+       /*
+        * Sorts by hash within the same object type, just as
+        * oid_array_for_each_unique() would do.
+        */
+       if (a_type == b_type)
+               return oidcmp(a, b);
+
+       /*
+        * Between object types show tags, then commits, and finally
+        * trees and blobs.
+        *
+        * The object_type enum is commit, tree, blob, tag, but we
+        * want tag, commit, tree blob. Cleverly (perhaps too
+        * cleverly) do that with modulus, since the enum assigns 1 to
+        * commit, so tag becomes 0.
+        */
+       a_type_sort = a_type % 4;
+       b_type_sort = b_type % 4;
+       return a_type_sort > b_type_sort ? 1 : -1;
+}
+
 static int get_short_oid(const char *name, int len, struct object_id *oid,
                          unsigned flags)
 {
@@ -384,7 +417,7 @@ static int get_short_oid(const char *name, int len, struct object_id *oid,
                return -1;
 
        if (HAS_MULTI_BITS(flags & GET_OID_DISAMBIGUATORS))
-               die("BUG: multiple get_short_oid disambiguator flags");
+               BUG("multiple get_short_oid disambiguator flags");
 
        if (flags & GET_OID_COMMIT)
                ds.fn = disambiguate_commit_only;
@@ -404,6 +437,8 @@ static int get_short_oid(const char *name, int len, struct object_id *oid,
        status = finish_object_disambiguation(&ds, oid);
 
        if (!quietly && (status == SHORT_NAME_AMBIGUOUS)) {
+               struct oid_array collect = OID_ARRAY_INIT;
+
                error(_("short SHA1 %s is ambiguous"), ds.hex_pfx);
 
                /*
@@ -416,18 +451,17 @@ static int get_short_oid(const char *name, int len, struct object_id *oid,
                        ds.fn = NULL;
 
                advise(_("The candidates are:"));
-               for_each_abbrev(ds.hex_pfx, show_ambiguous_object, &ds);
+               for_each_abbrev(ds.hex_pfx, collect_ambiguous, &collect);
+               QSORT(collect.oid, collect.nr, sort_ambiguous);
+
+               if (oid_array_for_each(&collect, show_ambiguous_object, &ds))
+                       BUG("show_ambiguous_object shouldn't return non-zero");
+               oid_array_clear(&collect);
        }
 
        return status;
 }
 
-static int collect_ambiguous(const struct object_id *oid, void *data)
-{
-       oid_array_append(data, oid);
-       return 0;
-}
-
 int for_each_abbrev(const char *prefix, each_abbrev_fn fn, void *cb_data)
 {
        struct oid_array collect = OID_ARRAY_INIT;
@@ -864,7 +898,7 @@ struct object *peel_to_type(const char *name, int namelen,
                if (o->type == OBJ_TAG)
                        o = ((struct tag*) o)->tagged;
                else if (o->type == OBJ_COMMIT)
-                       o = &(((struct commit *) o)->tree->object);
+                       o = &(get_commit_tree(((struct commit *)o))->object);
                else {
                        if (name)
                                error("%.*s: expected %s type, but the object "
@@ -1685,8 +1719,8 @@ static int get_oid_with_context_1(const char *name,
                        if (new_filename)
                                filename = new_filename;
                        if (flags & GET_OID_FOLLOW_SYMLINKS) {
-                               ret = get_tree_entry_follow_symlinks(tree_oid.hash,
-                                       filename, oid->hash, &oc->symlink_path,
+                               ret = get_tree_entry_follow_symlinks(&tree_oid,
+                                       filename, oid, &oc->symlink_path,
                                        &oc->mode);
                        } else {
                                ret = get_tree_entry(&tree_oid, filename, oid,
@@ -1698,7 +1732,6 @@ static int get_oid_with_context_1(const char *name,
                                                                   name, len);
                                }
                        }
-                       hashcpy(oc->tree, tree_oid.hash);
                        if (flags & GET_OID_RECORD_PATH)
                                oc->path = xstrdup(filename);
 
@@ -1729,6 +1762,6 @@ void maybe_die_on_misspelt_object_name(const char *name, const char *prefix)
 int get_oid_with_context(const char *str, unsigned flags, struct object_id *oid, struct object_context *oc)
 {
        if (flags & GET_OID_FOLLOW_SYMLINKS && flags & GET_OID_ONLY_TO_DIE)
-               die("BUG: incompatible flags for get_sha1_with_context");
+               BUG("incompatible flags for get_sha1_with_context");
        return get_oid_with_context_1(str, flags, NULL, oid, oc);
 }
index df4d44ea7a34a6d6bef119d1494f1c055614cbb1..9ff83cabcd3be2614f7ed770884ac237294b05e4 100644 (file)
--- a/shallow.c
+++ b/shallow.c
@@ -20,7 +20,7 @@ static char *alternate_shallow_file;
 void set_alternate_shallow_file(const char *path, int override)
 {
        if (is_shallow != -1)
-               die("BUG: is_repository_shallow must not be called before set_alternate_shallow_file");
+               BUG("is_repository_shallow must not be called before set_alternate_shallow_file");
        if (alternate_shallow_file && !override)
                return;
        free(alternate_shallow_file);
@@ -218,7 +218,7 @@ struct commit_list *get_shallow_commits_by_rev_list(int ac, const char **av,
 static void check_shallow_file_for_update(void)
 {
        if (is_shallow == -1)
-               die("BUG: shallow must be initialized by now");
+               BUG("shallow must be initialized by now");
 
        if (!stat_validity_check(&shallow_stat, git_path_shallow()))
                die("shallow file has changed since we read it");
@@ -353,7 +353,7 @@ void advertise_shallow_grafts(int fd)
  */
 void prune_shallow(int show_only)
 {
-       static struct lock_file shallow_lock;
+       struct lock_file shallow_lock = LOCK_INIT;
        struct strbuf sb = STRBUF_INIT;
        int fd;
 
@@ -446,7 +446,7 @@ static uint32_t *paint_alloc(struct paint_info *info)
        void *p;
        if (!info->pool_count || size > info->end - info->free) {
                if (size > POOL_SIZE)
-                       die("BUG: pool size too small for %d in paint_alloc()",
+                       BUG("pool size too small for %d in paint_alloc()",
                            size);
                info->pool_count++;
                REALLOC_ARRAY(info->pools, info->pool_count);
diff --git a/shell.c b/shell.c
index 0200d10796c43d6ea1249c314ee158f2b57a481f..40084a30130ef844899bc1f3321285afdda82607 100644 (file)
--- a/shell.c
+++ b/shell.c
@@ -3,6 +3,7 @@
 #include "exec-cmd.h"
 #include "strbuf.h"
 #include "run-command.h"
+#include "alias.h"
 
 #define COMMAND_DIR "git-shell-commands"
 #define HELP_COMMAND COMMAND_DIR "/help"
index 2ac43bbd282492e3c35c6ac5f96b14a541ed74c6..022677b6abaf918f51804a8fcbf18776aa3f2be5 100644 (file)
@@ -13,7 +13,7 @@ static struct sigchain_signal signals[SIGCHAIN_MAX_SIGNALS];
 static void check_signum(int sig)
 {
        if (sig < 1 || sig >= SIGCHAIN_MAX_SIGNALS)
-               die("BUG: signal out of range: %d", sig);
+               BUG("signal out of range: %d", sig);
 }
 
 int sigchain_push(int sig, sigchain_fun f)
index 3eb8ff1b43db284bddac643c270e2148114c4bbf..660c75f31fb970af06af3aad48b4ecc57e8cc136 100644 (file)
@@ -18,12 +18,12 @@ int read_link_extension(struct index_state *istate,
        struct split_index *si;
        int ret;
 
-       if (sz < 20)
+       if (sz < the_hash_algo->rawsz)
                return error("corrupt link extension (too short)");
        si = init_split_index(istate);
-       hashcpy(si->base_sha1, data);
-       data += 20;
-       sz -= 20;
+       hashcpy(si->base_oid.hash, data);
+       data += the_hash_algo->rawsz;
+       sz -= the_hash_algo->rawsz;
        if (!sz)
                return 0;
        si->delete_bitmap = ewah_new();
@@ -45,7 +45,7 @@ int write_link_extension(struct strbuf *sb,
                         struct index_state *istate)
 {
        struct split_index *si = istate->split_index;
-       strbuf_add(sb, si->base_sha1, 20);
+       strbuf_add(sb, si->base_oid.hash, the_hash_algo->rawsz);
        if (!si->delete_bitmap && !si->replace_bitmap)
                return 0;
        ewah_serialize_strbuf(si->delete_bitmap, sb);
index 43d66826eb712b9a9b6872458527266bd9818146..7a435ca2c970111b40ac9edba65726e79bd7941d 100644 (file)
@@ -1,12 +1,14 @@
 #ifndef SPLIT_INDEX_H
 #define SPLIT_INDEX_H
 
+#include "cache.h"
+
 struct index_state;
 struct strbuf;
 struct ewah_bitmap;
 
 struct split_index {
-       unsigned char base_sha1[20];
+       struct object_id base_oid;
        struct index_state *base;
        struct ewah_bitmap *delete_bitmap;
        struct ewah_bitmap *replace_bitmap;
index 622c462d5478dcc6290b118787ebe71f99c4b882..b0716ac585285a971c04952254c34a00aab9f0ef 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "refs.h"
+#include "string-list.h"
 #include "utf8.h"
 
 int starts_with(const char *str, const char *prefix)
@@ -180,6 +181,21 @@ struct strbuf **strbuf_split_buf(const char *str, size_t slen,
        return ret;
 }
 
+void strbuf_add_separated_string_list(struct strbuf *str,
+                                     const char *sep,
+                                     struct string_list *slist)
+{
+       struct string_list_item *item;
+       int sep_needed = 0;
+
+       for_each_string_list_item(item, slist) {
+               if (sep_needed)
+                       strbuf_addstr(str, sep);
+               strbuf_addstr(str, item->string);
+               sep_needed = 1;
+       }
+}
+
 void strbuf_list_free(struct strbuf **sbs)
 {
        struct strbuf **s = sbs;
@@ -318,12 +334,12 @@ void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap)
        len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, cp);
        va_end(cp);
        if (len < 0)
-               die("BUG: your vsnprintf is broken (returned %d)", len);
+               BUG("your vsnprintf is broken (returned %d)", len);
        if (len > strbuf_avail(sb)) {
                strbuf_grow(sb, len);
                len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap);
                if (len > strbuf_avail(sb))
-                       die("BUG: your vsnprintf is broken (insatiable)");
+                       BUG("your vsnprintf is broken (insatiable)");
        }
        strbuf_setlen(sb, sb->len + len);
 }
index 8c25e4bb59ecaee2da936e6ab86cc16b07979444..60a35aef165ca1a1bdc20b005751ea70e3c49ee0 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -1,6 +1,8 @@
 #ifndef STRBUF_H
 #define STRBUF_H
 
+struct string_list;
+
 /**
  * strbuf's are meant to be used with all the usual C string and memory
  * APIs. Given that the length of the buffer is known, it's often better to
@@ -537,6 +539,20 @@ static inline struct strbuf **strbuf_split(const struct strbuf *sb,
        return strbuf_split_max(sb, terminator, 0);
 }
 
+/*
+ * Adds all strings of a string list to the strbuf, separated by the given
+ * separator.  For example, if sep is
+ *   ', '
+ * and slist contains
+ *   ['element1', 'element2', ..., 'elementN'],
+ * then write:
+ *   'element1, element2, ..., elementN'
+ * to str.  If only one element, just write "element1" to str.
+ */
+extern void strbuf_add_separated_string_list(struct strbuf *str,
+                                            const char *sep,
+                                            struct string_list *slist);
+
 /**
  * Free a NULL-terminated list of strbufs (for example, the return
  * values of the strbuf_split*() functions).
index cce7b17ea7b3c1328fc8969a42be7399f1706141..d1e6b2dce6877cb1407ac9d38e65d8b2bae25daa 100644 (file)
@@ -117,7 +117,7 @@ static enum input_source istream_source(const struct object_id *oid,
 
        oi->typep = type;
        oi->sizep = &size;
-       status = oid_object_info_extended(oid, oi, 0);
+       status = oid_object_info_extended(the_repository, oid, oi, 0);
        if (status < 0)
                return stream_error;
 
index d87c3ff63a3cbc5bc0964499f84b7b4cebf04494..388ef1f892bdd63a485d9c93810a945fc3eab46f 100644 (file)
@@ -44,7 +44,7 @@ static int config_path_cmp(const void *unused_cmp_data,
        const struct submodule_entry *b = entry_or_key;
 
        return strcmp(a->config->path, b->config->path) ||
-              hashcmp(a->config->gitmodules_sha1, b->config->gitmodules_sha1);
+              oidcmp(&a->config->gitmodules_oid, &b->config->gitmodules_oid);
 }
 
 static int config_name_cmp(const void *unused_cmp_data,
@@ -56,7 +56,7 @@ static int config_name_cmp(const void *unused_cmp_data,
        const struct submodule_entry *b = entry_or_key;
 
        return strcmp(a->config->name, b->config->name) ||
-              hashcmp(a->config->gitmodules_sha1, b->config->gitmodules_sha1);
+              oidcmp(&a->config->gitmodules_oid, &b->config->gitmodules_oid);
 }
 
 static struct submodule_cache *submodule_cache_alloc(void)
@@ -109,17 +109,17 @@ void submodule_cache_free(struct submodule_cache *cache)
        free(cache);
 }
 
-static unsigned int hash_sha1_string(const unsigned char *sha1,
-                                    const char *string)
+static unsigned int hash_oid_string(const struct object_id *oid,
+                                   const char *string)
 {
-       return memhash(sha1, 20) + strhash(string);
+       return memhash(oid->hash, the_hash_algo->rawsz) + strhash(string);
 }
 
 static void cache_put_path(struct submodule_cache *cache,
                           struct submodule *submodule)
 {
-       unsigned int hash = hash_sha1_string(submodule->gitmodules_sha1,
-                                            submodule->path);
+       unsigned int hash = hash_oid_string(&submodule->gitmodules_oid,
+                                           submodule->path);
        struct submodule_entry *e = xmalloc(sizeof(*e));
        hashmap_entry_init(e, hash);
        e->config = submodule;
@@ -129,8 +129,8 @@ static void cache_put_path(struct submodule_cache *cache,
 static void cache_remove_path(struct submodule_cache *cache,
                              struct submodule *submodule)
 {
-       unsigned int hash = hash_sha1_string(submodule->gitmodules_sha1,
-                                            submodule->path);
+       unsigned int hash = hash_oid_string(&submodule->gitmodules_oid,
+                                           submodule->path);
        struct submodule_entry e;
        struct submodule_entry *removed;
        hashmap_entry_init(&e, hash);
@@ -142,8 +142,8 @@ static void cache_remove_path(struct submodule_cache *cache,
 static void cache_add(struct submodule_cache *cache,
                      struct submodule *submodule)
 {
-       unsigned int hash = hash_sha1_string(submodule->gitmodules_sha1,
-                                            submodule->name);
+       unsigned int hash = hash_oid_string(&submodule->gitmodules_oid,
+                                           submodule->name);
        struct submodule_entry *e = xmalloc(sizeof(*e));
        hashmap_entry_init(e, hash);
        e->config = submodule;
@@ -151,14 +151,14 @@ static void cache_add(struct submodule_cache *cache,
 }
 
 static const struct submodule *cache_lookup_path(struct submodule_cache *cache,
-               const unsigned char *gitmodules_sha1, const char *path)
+               const struct object_id *gitmodules_oid, const char *path)
 {
        struct submodule_entry *entry;
-       unsigned int hash = hash_sha1_string(gitmodules_sha1, path);
+       unsigned int hash = hash_oid_string(gitmodules_oid, path);
        struct submodule_entry key;
        struct submodule key_config;
 
-       hashcpy(key_config.gitmodules_sha1, gitmodules_sha1);
+       oidcpy(&key_config.gitmodules_oid, gitmodules_oid);
        key_config.path = path;
 
        hashmap_entry_init(&key, hash);
@@ -171,14 +171,14 @@ static const struct submodule *cache_lookup_path(struct submodule_cache *cache,
 }
 
 static struct submodule *cache_lookup_name(struct submodule_cache *cache,
-               const unsigned char *gitmodules_sha1, const char *name)
+               const struct object_id *gitmodules_oid, const char *name)
 {
        struct submodule_entry *entry;
-       unsigned int hash = hash_sha1_string(gitmodules_sha1, name);
+       unsigned int hash = hash_oid_string(gitmodules_oid, name);
        struct submodule_entry key;
        struct submodule key_config;
 
-       hashcpy(key_config.gitmodules_sha1, gitmodules_sha1);
+       oidcpy(&key_config.gitmodules_oid, gitmodules_oid);
        key_config.name = name;
 
        hashmap_entry_init(&key, hash);
@@ -190,6 +190,31 @@ static struct submodule *cache_lookup_name(struct submodule_cache *cache,
        return NULL;
 }
 
+int check_submodule_name(const char *name)
+{
+       /* Disallow empty names */
+       if (!*name)
+               return -1;
+
+       /*
+        * Look for '..' as a path component. Check both '/' and '\\' as
+        * separators rather than is_dir_sep(), because we want the name rules
+        * to be consistent across platforms.
+        */
+       goto in_component; /* always start inside component */
+       while (*name) {
+               char c = *name++;
+               if (c == '/' || c == '\\') {
+in_component:
+                       if (name[0] == '.' && name[1] == '.' &&
+                           (!name[2] || name[2] == '/' || name[2] == '\\'))
+                               return -1;
+               }
+       }
+
+       return 0;
+}
+
 static int name_and_item_from_var(const char *var, struct strbuf *name,
                                  struct strbuf *item)
 {
@@ -201,18 +226,24 @@ static int name_and_item_from_var(const char *var, struct strbuf *name,
                return 0;
 
        strbuf_add(name, subsection, subsection_len);
+       if (check_submodule_name(name->buf) < 0) {
+               warning(_("ignoring suspicious submodule name: %s"), name->buf);
+               strbuf_release(name);
+               return 0;
+       }
+
        strbuf_addstr(item, key);
 
        return 1;
 }
 
 static struct submodule *lookup_or_create_by_name(struct submodule_cache *cache,
-               const unsigned char *gitmodules_sha1, const char *name)
+               const struct object_id *gitmodules_oid, const char *name)
 {
        struct submodule *submodule;
        struct strbuf name_buf = STRBUF_INIT;
 
-       submodule = cache_lookup_name(cache, gitmodules_sha1, name);
+       submodule = cache_lookup_name(cache, gitmodules_oid, name);
        if (submodule)
                return submodule;
 
@@ -230,7 +261,7 @@ static struct submodule *lookup_or_create_by_name(struct submodule_cache *cache,
        submodule->branch = NULL;
        submodule->recommend_shallow = -1;
 
-       hashcpy(submodule->gitmodules_sha1, gitmodules_sha1);
+       oidcpy(&submodule->gitmodules_oid, gitmodules_oid);
 
        cache_add(cache, submodule);
 
@@ -341,12 +372,12 @@ int parse_push_recurse_submodules_arg(const char *opt, const char *arg)
        return parse_push_recurse(opt, arg, 1);
 }
 
-static void warn_multiple_config(const unsigned char *treeish_name,
+static void warn_multiple_config(const struct object_id *treeish_name,
                                 const char *name, const char *option)
 {
        const char *commit_string = "WORKTREE";
        if (treeish_name)
-               commit_string = sha1_to_hex(treeish_name);
+               commit_string = oid_to_hex(treeish_name);
        warning("%s:.gitmodules, multiple configurations found for "
                        "'submodule.%s.%s'. Skipping second one!",
                        commit_string, name, option);
@@ -354,8 +385,8 @@ static void warn_multiple_config(const unsigned char *treeish_name,
 
 struct parse_config_parameter {
        struct submodule_cache *cache;
-       const unsigned char *treeish_name;
-       const unsigned char *gitmodules_sha1;
+       const struct object_id *treeish_name;
+       const struct object_id *gitmodules_oid;
        int overwrite;
 };
 
@@ -371,7 +402,7 @@ static int parse_config(const char *var, const char *value, void *data)
                return 0;
 
        submodule = lookup_or_create_by_name(me->cache,
-                                            me->gitmodules_sha1,
+                                            me->gitmodules_oid,
                                             name.buf);
 
        if (!strcmp(item.buf, "path")) {
@@ -389,7 +420,7 @@ static int parse_config(const char *var, const char *value, void *data)
                }
        } else if (!strcmp(item.buf, "fetchrecursesubmodules")) {
                /* when parsing worktree configurations we can die early */
-               int die_on_error = is_null_sha1(me->gitmodules_sha1);
+               int die_on_error = is_null_oid(me->gitmodules_oid);
                if (!me->overwrite &&
                    submodule->fetch_recurse != RECURSE_SUBMODULES_NONE)
                        warn_multiple_config(me->treeish_name, submodule->name,
@@ -511,10 +542,10 @@ static const struct submodule *config_from(struct submodule_cache *cache,
 
        switch (lookup_type) {
        case lookup_name:
-               submodule = cache_lookup_name(cache, oid.hash, key);
+               submodule = cache_lookup_name(cache, &oid, key);
                break;
        case lookup_path:
-               submodule = cache_lookup_path(cache, oid.hash, key);
+               submodule = cache_lookup_path(cache, &oid, key);
                break;
        }
        if (submodule)
@@ -526,8 +557,8 @@ static const struct submodule *config_from(struct submodule_cache *cache,
 
        /* fill the submodule config into the cache */
        parameter.cache = cache;
-       parameter.treeish_name = treeish_name->hash;
-       parameter.gitmodules_sha1 = oid.hash;
+       parameter.treeish_name = treeish_name;
+       parameter.gitmodules_oid = &oid;
        parameter.overwrite = 0;
        git_config_from_mem(parse_config, CONFIG_ORIGIN_SUBMODULE_BLOB, rev.buf,
                        config, config_size, &parameter);
@@ -536,9 +567,9 @@ static const struct submodule *config_from(struct submodule_cache *cache,
 
        switch (lookup_type) {
        case lookup_name:
-               return cache_lookup_name(cache, oid.hash, key);
+               return cache_lookup_name(cache, &oid, key);
        case lookup_path:
-               return cache_lookup_path(cache, oid.hash, key);
+               return cache_lookup_path(cache, &oid, key);
        default:
                return NULL;
        }
@@ -567,7 +598,7 @@ static int gitmodules_cb(const char *var, const char *value, void *data)
 
        parameter.cache = repo->submodule_cache;
        parameter.treeish_name = NULL;
-       parameter.gitmodules_sha1 = null_sha1;
+       parameter.gitmodules_oid = &null_oid;
        parameter.overwrite = 1;
 
        return parse_config(var, value, &parameter);
index 6f686184e86cc0004410aae3b771de4d3b488a13..ca1f94e2d2ed90f9ef4c588e27c0d8e15e1724ab 100644 (file)
@@ -1,6 +1,7 @@
 #ifndef SUBMODULE_CONFIG_CACHE_H
 #define SUBMODULE_CONFIG_CACHE_H
 
+#include "cache.h"
 #include "hashmap.h"
 #include "submodule.h"
 #include "strbuf.h"
@@ -17,13 +18,13 @@ struct submodule {
        const char *ignore;
        const char *branch;
        struct submodule_update_strategy update_strategy;
-       /* the sha1 blob id of the responsible .gitmodules file */
-       unsigned char gitmodules_sha1[20];
+       /* the object id of the responsible .gitmodules file */
+       struct object_id gitmodules_oid;
        int recommend_shallow;
 };
 
 #define SUBMODULE_INIT { NULL, NULL, NULL, RECURSE_SUBMODULES_NONE, \
-       NULL, NULL, SUBMODULE_UPDATE_STRATEGY_INIT, {0}, -1 };
+       NULL, NULL, SUBMODULE_UPDATE_STRATEGY_INIT, { { 0 } }, -1 };
 
 struct submodule_cache;
 struct repository;
@@ -47,4 +48,11 @@ const struct submodule *submodule_from_path(struct repository *r,
                                            const char *path);
 void submodule_free(struct repository *r);
 
+/*
+ * Returns 0 if the name is syntactically acceptable as a submodule "name"
+ * (e.g., that may be found in the subsection of a .gitmodules file) and -1
+ * otherwise.
+ */
+int check_submodule_name(const char *name);
+
 #endif /* SUBMODULE_CONFIG_H */
index cdeadd80e7890ce3ed77ce3793f4468033c7b3bb..edc3a206d7cb3b23c7779ddd48595df6b927c2f2 100644 (file)
@@ -153,7 +153,8 @@ void stage_updated_gitmodules(struct index_state *istate)
                die(_("staging updated .gitmodules failed"));
 }
 
-static int add_submodule_odb(const char *path)
+/* TODO: remove this function, use repo_submodule_init instead. */
+int add_submodule_odb(const char *path)
 {
        struct strbuf objects_directory = STRBUF_INIT;
        int ret = 0;
@@ -820,7 +821,7 @@ static int check_has_commit(const struct object_id *oid, void *data)
 {
        struct has_commit_data *cb = data;
 
-       enum object_type type = oid_object_info(oid, NULL);
+       enum object_type type = oid_object_info(the_repository, oid, NULL);
 
        switch (type) {
        case OBJ_COMMIT:
@@ -1399,7 +1400,7 @@ unsigned is_submodule_modified(const char *path, int ignore_untracked)
                    buf.buf[0] == '2') {
                        /* T = line type, XY = status, SSSS = submodule state */
                        if (buf.len < strlen("T XY SSSS"))
-                               die("BUG: invalid status --porcelain=2 line %s",
+                               BUG("invalid status --porcelain=2 line %s",
                                    buf.buf);
 
                        if (buf.buf[5] == 'S' && buf.buf[8] == 'U')
@@ -1568,7 +1569,7 @@ static void submodule_reset_index(const char *path)
                                   get_super_prefix_or_empty(), path);
        argv_array_pushl(&cp.args, "read-tree", "-u", "--reset", NULL);
 
-       argv_array_push(&cp.args, EMPTY_TREE_SHA1_HEX);
+       argv_array_push(&cp.args, empty_tree_oid_hex());
 
        if (run_command(&cp))
                die("could not reset submodule index");
@@ -1608,7 +1609,7 @@ int submodule_move_head(const char *path,
        sub = submodule_from_path(the_repository, &null_oid, path);
 
        if (!sub)
-               die("BUG: could not get submodule information for '%s'", path);
+               BUG("could not get submodule information for '%s'", path);
 
        if (old_head && !(flags & SUBMODULE_MOVE_HEAD_FORCE)) {
                /* Check if the submodule has a dirty index. */
@@ -1660,9 +1661,9 @@ int submodule_move_head(const char *path,
                argv_array_push(&cp.args, "-m");
 
        if (!(flags & SUBMODULE_MOVE_HEAD_FORCE))
-               argv_array_push(&cp.args, old_head ? old_head : EMPTY_TREE_SHA1_HEX);
+               argv_array_push(&cp.args, old_head ? old_head : empty_tree_oid_hex());
 
-       argv_array_push(&cp.args, new_head ? new_head : EMPTY_TREE_SHA1_HEX);
+       argv_array_push(&cp.args, new_head ? new_head : empty_tree_oid_hex());
 
        if (run_command(&cp)) {
                ret = -1;
@@ -1700,171 +1701,6 @@ int submodule_move_head(const char *path,
        return ret;
 }
 
-static int find_first_merges(struct object_array *result, const char *path,
-               struct commit *a, struct commit *b)
-{
-       int i, j;
-       struct object_array merges = OBJECT_ARRAY_INIT;
-       struct commit *commit;
-       int contains_another;
-
-       char merged_revision[42];
-       const char *rev_args[] = { "rev-list", "--merges", "--ancestry-path",
-                                  "--all", merged_revision, NULL };
-       struct rev_info revs;
-       struct setup_revision_opt rev_opts;
-
-       memset(result, 0, sizeof(struct object_array));
-       memset(&rev_opts, 0, sizeof(rev_opts));
-
-       /* get all revisions that merge commit a */
-       xsnprintf(merged_revision, sizeof(merged_revision), "^%s",
-                       oid_to_hex(&a->object.oid));
-       init_revisions(&revs, NULL);
-       rev_opts.submodule = path;
-       /* FIXME: can't handle linked worktrees in submodules yet */
-       revs.single_worktree = path != NULL;
-       setup_revisions(ARRAY_SIZE(rev_args)-1, rev_args, &revs, &rev_opts);
-
-       /* save all revisions from the above list that contain b */
-       if (prepare_revision_walk(&revs))
-               die("revision walk setup failed");
-       while ((commit = get_revision(&revs)) != NULL) {
-               struct object *o = &(commit->object);
-               if (in_merge_bases(b, commit))
-                       add_object_array(o, NULL, &merges);
-       }
-       reset_revision_walk();
-
-       /* Now we've got all merges that contain a and b. Prune all
-        * merges that contain another found merge and save them in
-        * result.
-        */
-       for (i = 0; i < merges.nr; i++) {
-               struct commit *m1 = (struct commit *) merges.objects[i].item;
-
-               contains_another = 0;
-               for (j = 0; j < merges.nr; j++) {
-                       struct commit *m2 = (struct commit *) merges.objects[j].item;
-                       if (i != j && in_merge_bases(m2, m1)) {
-                               contains_another = 1;
-                               break;
-                       }
-               }
-
-               if (!contains_another)
-                       add_object_array(merges.objects[i].item, NULL, result);
-       }
-
-       object_array_clear(&merges);
-       return result->nr;
-}
-
-static void print_commit(struct commit *commit)
-{
-       struct strbuf sb = STRBUF_INIT;
-       struct pretty_print_context ctx = {0};
-       ctx.date_mode.type = DATE_NORMAL;
-       format_commit_message(commit, " %h: %m %s", &sb, &ctx);
-       fprintf(stderr, "%s\n", sb.buf);
-       strbuf_release(&sb);
-}
-
-#define MERGE_WARNING(path, msg) \
-       warning("Failed to merge submodule %s (%s)", path, msg);
-
-int merge_submodule(struct object_id *result, const char *path,
-                   const struct object_id *base, const struct object_id *a,
-                   const struct object_id *b, int search)
-{
-       struct commit *commit_base, *commit_a, *commit_b;
-       int parent_count;
-       struct object_array merges;
-
-       int i;
-
-       /* store a in result in case we fail */
-       oidcpy(result, a);
-
-       /* we can not handle deletion conflicts */
-       if (is_null_oid(base))
-               return 0;
-       if (is_null_oid(a))
-               return 0;
-       if (is_null_oid(b))
-               return 0;
-
-       if (add_submodule_odb(path)) {
-               MERGE_WARNING(path, "not checked out");
-               return 0;
-       }
-
-       if (!(commit_base = lookup_commit_reference(base)) ||
-           !(commit_a = lookup_commit_reference(a)) ||
-           !(commit_b = lookup_commit_reference(b))) {
-               MERGE_WARNING(path, "commits not present");
-               return 0;
-       }
-
-       /* check whether both changes are forward */
-       if (!in_merge_bases(commit_base, commit_a) ||
-           !in_merge_bases(commit_base, commit_b)) {
-               MERGE_WARNING(path, "commits don't follow merge-base");
-               return 0;
-       }
-
-       /* Case #1: a is contained in b or vice versa */
-       if (in_merge_bases(commit_a, commit_b)) {
-               oidcpy(result, b);
-               return 1;
-       }
-       if (in_merge_bases(commit_b, commit_a)) {
-               oidcpy(result, a);
-               return 1;
-       }
-
-       /*
-        * Case #2: There are one or more merges that contain a and b in
-        * the submodule. If there is only one, then present it as a
-        * suggestion to the user, but leave it marked unmerged so the
-        * user needs to confirm the resolution.
-        */
-
-       /* Skip the search if makes no sense to the calling context.  */
-       if (!search)
-               return 0;
-
-       /* find commit which merges them */
-       parent_count = find_first_merges(&merges, path, commit_a, commit_b);
-       switch (parent_count) {
-       case 0:
-               MERGE_WARNING(path, "merge following commits not found");
-               break;
-
-       case 1:
-               MERGE_WARNING(path, "not fast-forward");
-               fprintf(stderr, "Found a possible merge resolution "
-                               "for the submodule:\n");
-               print_commit((struct commit *) merges.objects[0].item);
-               fprintf(stderr,
-                       "If this is correct simply add it to the index "
-                       "for example\n"
-                       "by using:\n\n"
-                       "  git update-index --cacheinfo 160000 %s \"%s\"\n\n"
-                       "which will accept this suggestion.\n",
-                       oid_to_hex(&merges.objects[0].item->oid), path);
-               break;
-
-       default:
-               MERGE_WARNING(path, "multiple merges found");
-               for (i = 0; i < merges.nr; i++)
-                       print_commit((struct commit *) merges.objects[i].item);
-       }
-
-       object_array_clear(&merges);
-       return 0;
-}
-
 /*
  * Embeds a single submodules git directory into the superprojects git dir,
  * non recursively.
@@ -1966,7 +1802,7 @@ void absorb_git_dir_into_superproject(const char *prefix,
                struct strbuf sb = STRBUF_INIT;
 
                if (flags & ~ABSORB_GITDIR_RECURSE_SUBMODULES)
-                       die("BUG: we don't know how to pass the flags down?");
+                       BUG("we don't know how to pass the flags down?");
 
                strbuf_addstr(&sb, get_super_prefix_or_empty());
                strbuf_addstr(&sb, path);
@@ -2044,7 +1880,7 @@ const char *get_superproject_working_tree(void)
 
                if (super_sub_len > cwd_len ||
                    strcmp(&cwd[cwd_len - super_sub_len], super_sub))
-                       die (_("BUG: returned path string doesn't match cwd?"));
+                       BUG("returned path string doesn't match cwd?");
 
                super_wt = xstrdup(cwd);
                super_wt[cwd_len - super_sub_len] = '\0';
index aae0c9c8fffebb5bc256f6d6bf2cef3590d80bfc..a2871d0312c6a3b0afa515a9d8c80a01ffc9e0d0 100644 (file)
@@ -89,10 +89,8 @@ extern int submodule_uses_gitfile(const char *path);
 #define SUBMODULE_REMOVAL_IGNORE_UNTRACKED (1<<1)
 #define SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED (1<<2)
 extern int bad_to_remove_submodule(const char *path, unsigned flags);
-extern int merge_submodule(struct object_id *result, const char *path,
-                          const struct object_id *base,
-                          const struct object_id *a,
-                          const struct object_id *b, int search);
+
+int add_submodule_odb(const char *path);
 
 /* Checks if there are submodule changes in a..b. */
 extern int submodule_touches_in_range(struct object_id *a,
index 24ddebfabf97be1251452fe5b6bba847597431fd..8373a27fea38b1ec111e80d203bf61f1143b7861 100644 (file)
--- a/t/README
+++ b/t/README
@@ -293,6 +293,28 @@ and know what setup is needed for it.  Or when you want to run
 everything up to a certain test.
 
 
+Running tests with special setups
+---------------------------------
+
+The whole test suite could be run to test some special features
+that cannot be easily covered by a few specific test cases. These
+could be enabled by running the test suite with correct GIT_TEST_
+environment set.
+
+GIT_TEST_SPLIT_INDEX=<boolean> forces split-index mode on the whole
+test suite. Accept any boolean values that are accepted by git-config.
+
+GIT_TEST_FULL_IN_PACK_ARRAY=<boolean> exercises the uncommon
+pack-objects code path where there are more than 1024 packs even if
+the actual number of packs in repository is below this limit. Accept
+any boolean values that are accepted by git-config.
+
+GIT_TEST_OE_SIZE=<n> exercises the uncommon pack-objects code path
+where we do not cache object size in memory and read it from existing
+packs on demand. This normally only happens when the object size is
+over 2GB. This variable forces the code path on any object larger than
+<n> bytes.
+
 Naming Tests
 ------------
 
index c211dc40eed98fc58c953d2e3b1f7ba752f98a02..2de880f7a5b0642e0bcfb6fad97a8ff462c29392 100644 (file)
@@ -1,6 +1,6 @@
 :
 
-sanitize_diff_raw='/^:/s/ '"\($_x40\)"' '"\($_x40\)"' \([A-Z]\)[0-9]*  / \1 \2 \3#     /'
+sanitize_diff_raw='/^:/s/ '"\($OID_REGEX\)"' '"\($OID_REGEX\)"' \([A-Z]\)[0-9]*        / \1 \2 \3#     /'
 compare_diff_raw () {
     # When heuristics are improved, the score numbers would change.
     # Ignore them while comparing.
@@ -12,7 +12,7 @@ compare_diff_raw () {
     test_cmp .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2
 }
 
-sanitize_diff_raw_z='/^:/s/ '"$_x40"' '"$_x40"' \([A-Z]\)[0-9]*$/ X X \1#/'
+sanitize_diff_raw_z='/^:/s/ '"$OID_REGEX"' '"$OID_REGEX"' \([A-Z]\)[0-9]*$/ X X \1#/'
 compare_diff_raw_z () {
     # When heuristics are improved, the score numbers would change.
     # Ignore them while comparing.
index 838760898b6f4ccfc0d7a3c4bd2c8653020a93ec..d6bcfddf13352b92d6be4d9d5b8e7d7f05cc06ee 100644 (file)
@@ -2,6 +2,7 @@
 #include "git-compat-util.h"
 
 #if defined(GIT_WINDOWS_NATIVE)
+#include "lazyload.h"
 
 static int cmd_sync(void)
 {
@@ -82,8 +83,7 @@ static int cmd_dropcaches(void)
 {
        HANDLE hProcess = GetCurrentProcess();
        HANDLE hToken;
-       HMODULE ntdll;
-       DWORD(WINAPI *NtSetSystemInformation)(INT, PVOID, ULONG);
+       DECLARE_PROC_ADDR(ntdll.dll, DWORD, NtSetSystemInformation, INT, PVOID, ULONG);
        SYSTEM_MEMORY_LIST_COMMAND command;
        int status;
 
@@ -95,14 +95,8 @@ static int cmd_dropcaches(void)
 
        CloseHandle(hToken);
 
-       ntdll = LoadLibrary("ntdll.dll");
-       if (!ntdll)
-               return error("Can't load ntdll.dll, wrong Windows version?");
-
-       NtSetSystemInformation =
-               (DWORD(WINAPI *)(INT, PVOID, ULONG))GetProcAddress(ntdll, "NtSetSystemInformation");
-       if (!NtSetSystemInformation)
-               return error("Can't get function addresses, wrong Windows version?");
+       if (!INIT_PROC_ADDR(NtSetSystemInformation))
+               return error("Could not find NtSetSystemInformation() function");
 
        command = MemoryPurgeStandbyList;
        status = NtSetSystemInformation(
@@ -115,8 +109,6 @@ static int cmd_dropcaches(void)
        else if (status != STATUS_SUCCESS)
                error("Unable to execute the memory list command %d", status);
 
-       FreeLibrary(ntdll);
-
        return status;
 }
 
index 4e2fdb5e30d1ae30b1f75b7e3ca636afa4c0e097..63c689d6ee9d018efd32aa228ee3cf784fa5b71d 100644 (file)
@@ -14,13 +14,13 @@ int cmd__dump_split_index(int ac, const char **av)
        int i;
 
        do_read_index(&the_index, av[1], 1);
-       printf("own %s\n", sha1_to_hex(the_index.sha1));
+       printf("own %s\n", oid_to_hex(&the_index.oid));
        si = the_index.split_index;
        if (!si) {
                printf("not a split index\n");
                return 0;
        }
-       printf("base %s\n", sha1_to_hex(si->base_sha1));
+       printf("base %s\n", oid_to_hex(&si->base_oid));
        for (i = 0; i < the_index.cache_nr; i++) {
                struct cache_entry *ce = the_index.cache[i];
                printf("%06o %s %d\t%s\n", ce->ce_mode,
index d7c55c2355ec7067e15655e99df8077344ed78fa..bd92fb305a239cb4b96d38b4a07fe885e71bbeb3 100644 (file)
@@ -23,7 +23,7 @@ static void dump(struct untracked_cache_dir *ucd, struct strbuf *base)
        len = base->len;
        strbuf_addf(base, "%s/", ucd->name);
        printf("%s %s", base->buf,
-              sha1_to_hex(ucd->exclude_sha1));
+              oid_to_hex(&ucd->exclude_oid));
        if (ucd->recurse)
                fputs(" recurse", stdout);
        if (ucd->check_only)
index 081115bf8eb7cb2e27c39f632af0e40001b5b6e9..a20a6161e4fce156cc2e987f61f4253b1f00b397 100644 (file)
@@ -30,10 +30,10 @@ int cmd__example_decorate(int argc, const char **argv)
        two = lookup_unknown_object(two_oid.hash);
        ret = add_decoration(&n, one, &decoration_a);
        if (ret)
-               die("BUG: when adding a brand-new object, NULL should be returned");
+               BUG("when adding a brand-new object, NULL should be returned");
        ret = add_decoration(&n, two, NULL);
        if (ret)
-               die("BUG: when adding a brand-new object, NULL should be returned");
+               BUG("when adding a brand-new object, NULL should be returned");
 
        /*
         * When re-adding an already existing object, the old decoration is
@@ -41,10 +41,10 @@ int cmd__example_decorate(int argc, const char **argv)
         */
        ret = add_decoration(&n, one, NULL);
        if (ret != &decoration_a)
-               die("BUG: when readding an already existing object, existing decoration should be returned");
+               BUG("when readding an already existing object, existing decoration should be returned");
        ret = add_decoration(&n, two, &decoration_b);
        if (ret)
-               die("BUG: when readding an already existing object, existing decoration should be returned");
+               BUG("when readding an already existing object, existing decoration should be returned");
 
        /*
         * Lookup returns the added declarations, or NULL if the object was
@@ -52,14 +52,14 @@ int cmd__example_decorate(int argc, const char **argv)
         */
        ret = lookup_decoration(&n, one);
        if (ret)
-               die("BUG: lookup should return added declaration");
+               BUG("lookup should return added declaration");
        ret = lookup_decoration(&n, two);
        if (ret != &decoration_b)
-               die("BUG: lookup should return added declaration");
+               BUG("lookup should return added declaration");
        three = lookup_unknown_object(three_oid.hash);
        ret = lookup_decoration(&n, three);
        if (ret)
-               die("BUG: lookup for unknown object should return NULL");
+               BUG("lookup for unknown object should return NULL");
 
        /*
         * The user can also loop through all entries.
@@ -69,7 +69,7 @@ int cmd__example_decorate(int argc, const char **argv)
                        objects_noticed++;
        }
        if (objects_noticed != 2)
-               die("BUG: should have 2 objects");
+               BUG("should have 2 objects");
 
        return 0;
 }
index e115d44ac26e1d4fed48a5fd82632e5a93a487e6..ae091d9b3e63cb506b2530217d40048b301faaa3 100644 (file)
@@ -1,6 +1,7 @@
 #include "test-tool.h"
 #include "cache.h"
 #include "string-list.h"
+#include "utf8.h"
 
 /*
  * A "string_list_each_func_t" function that normalizes an entry from
@@ -171,6 +172,11 @@ static struct test_data dirname_data[] = {
        { NULL,              NULL     }
 };
 
+static int is_dotgitmodules(const char *path)
+{
+       return is_hfs_dotgitmodules(path) || is_ntfs_dotgitmodules(path);
+}
+
 int cmd__path_utils(int argc, const char **argv)
 {
        if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) {
@@ -271,6 +277,20 @@ int cmd__path_utils(int argc, const char **argv)
        if (argc == 2 && !strcmp(argv[1], "dirname"))
                return test_function(dirname_data, posix_dirname, argv[1]);
 
+       if (argc > 2 && !strcmp(argv[1], "is_dotgitmodules")) {
+               int res = 0, expect = 1, i;
+               for (i = 2; i < argc; i++)
+                       if (!strcmp("--not", argv[i]))
+                               expect = !expect;
+                       else if (expect != is_dotgitmodules(argv[i]))
+                               res = error("'%s' is %s.gitmodules", argv[i],
+                                           expect ? "not " : "");
+                       else
+                               fprintf(stderr, "ok: '%s' is %s.gitmodules\n",
+                                       argv[i], expect ? "" : "not ");
+               return !!res;
+       }
+
        fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
                argv[1] ? argv[1] : "(there was none)");
        return 1;
index d26d3e7c8b1a407e2cf40ce20750495f500d5170..393f1604ff954703a2a5e075fbda1e14d0827dc0 100644 (file)
@@ -4,10 +4,10 @@
 #include "tree.h"
 #include "cache-tree.h"
 
-static struct lock_file index_lock;
-
 int cmd__scrap_cache_tree(int ac, const char **av)
 {
+       struct lock_file index_lock = LOCK_INIT;
+
        setup_git_directory();
        hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR);
        if (read_cache() < 0)
index 87066ced62aca5641daafb83b696e01e12b436d5..805a45de9c877d9e5fecb6aa543e6ac985443d5b 100644 (file)
@@ -48,6 +48,7 @@ int cmd_main(int argc, const char **argv)
 {
        int i;
 
+       BUG_exit_code = 99;
        if (argc < 2)
                die("I need a test name!");
 
index 017dc303800d9ba385a9836d85f81445c6b7e8b7..8837717d36a77c04346279b570e1a0a4506fe838 100644 (file)
@@ -2,22 +2,18 @@
 #include "cache.h"
 #include "lockfile.h"
 
-static struct lock_file index_lock;
-
 int cmd__write_cache(int argc, const char **argv)
 {
-       int i, cnt = 1, lockfd;
+       struct lock_file index_lock = LOCK_INIT;
+       int i, cnt = 1;
        if (argc == 2)
                cnt = strtol(argv[1], NULL, 0);
        setup_git_directory();
        read_cache();
        for (i = 0; i < cnt; i++) {
-               lockfd = hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR);
-               if (0 <= lockfd) {
-                       write_locked_index(&the_index, &index_lock, COMMIT_LOCK);
-               } else {
-                       rollback_lock_file(&index_lock);
-               }
+               hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR);
+               if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK))
+                       die("unable to write index file");
        }
 
        return 0;
index 8b4dbf22d2d140bef7ca0e106a389cb0ca1d4681..8d1e408bb58f5e097f242d5c1fd2c7bb036deb6e 100644 (file)
@@ -59,9 +59,11 @@ int main(int argc, char **argv)
 }
 EOF
 
-       cat >expect <<\EOF
+       file1=$(git rev-parse --short $(git hash-object file1))
+       file2=$(git rev-parse --short $(git hash-object file2))
+       cat >expect <<EOF
 diff --git a/file1 b/file2
-index 6faa5a3..e3af329 100644
+index $file1..$file2 100644
 --- a/file1
 +++ b/file2
 @@ -1,26 +1,25 @@
@@ -136,9 +138,11 @@ e
 f
 EOF
 
-       cat >expect <<\EOF
+       uniq1=$(git rev-parse --short $(git hash-object uniq1))
+       uniq2=$(git rev-parse --short $(git hash-object uniq2))
+       cat >expect <<EOF
 diff --git a/uniq1 b/uniq2
-index b414108..0fdf397 100644
+index $uniq1..$uniq2 100644
 --- a/uniq1
 +++ b/uniq2
 @@ -1,6 +1,6 @@
index 501078249dc7d5d5b27dd6b75a22086767a0eddf..c4d907a450a7863ff0fe32fe9aed7ef5c100dd3e 100644 (file)
@@ -79,6 +79,18 @@ pack_obj () {
                ;;
        esac
 
+       # If it's not a delta, we can convince pack-objects to generate a pack
+       # with just our entry, and then strip off the header (12 bytes) and
+       # trailer (20 bytes).
+       if test -z "$2"
+       then
+               echo "$1" | git pack-objects --stdout >pack_obj.tmp &&
+               size=$(wc -c <pack_obj.tmp) &&
+               dd if=pack_obj.tmp bs=1 count=$((size - 20 - 12)) skip=12 &&
+               rm -f pack_obj.tmp
+               return
+       fi
+
        echo >&2 "BUG: don't know how to print $1${2:+ (from $2)}"
        return 1
 }
index 3f2d873fec9ed9e98b787cd708cd9e8e816e7573..b0ed4767e320e200c592e979dce0fa1e4cfd6ede 100644 (file)
@@ -4,11 +4,11 @@ mkdir -p .git/refs/tags
 
 >sed.script
 
-# Answer the sha1 has associated with the tag. The tag must exist in .git/refs/tags
+# Answer the sha1 has associated with the tag. The tag must exist under refs/tags
 tag () {
        _tag=$1
-       test -f ".git/refs/tags/$_tag" || error "tag: \"$_tag\" does not exist"
-       cat ".git/refs/tags/$_tag"
+       git rev-parse --verify "refs/tags/$_tag" ||
+       error "tag: \"$_tag\" does not exist"
 }
 
 # Generate a commit using the text specified to make it unique and the tree
index 48637ef64bb658098d8bc1d0f00eba87f7daa681..bc865160e7e3370f9462beda9d8b3866e3c2111b 100755 (executable)
@@ -4,6 +4,7 @@
 use strict;
 use warnings;
 use JSON;
+use Getopt::Long;
 use Git;
 
 sub get_times {
@@ -36,46 +37,34 @@ sub format_times {
        return $out;
 }
 
+sub usage {
+       print <<EOT;
+./aggregate.perl [options] [--] [<dir_or_rev>...] [--] [<test_script>...] >
+
+  Options:
+    --codespeed          * Format output for Codespeed
+    --reponame    <str>  * Send given reponame to codespeed
+    --sort-by     <str>  * Sort output (only "regression" criteria is supported)
+    --subsection  <str>  * Use results from given subsection
+
+EOT
+       exit(1);
+}
+
 my (@dirs, %dirnames, %dirabbrevs, %prefixes, @tests,
     $codespeed, $sortby, $subsection, $reponame);
+
+Getopt::Long::Configure qw/ require_order /;
+
+my $rc = GetOptions("codespeed"     => \$codespeed,
+                   "reponame=s"    => \$reponame,
+                   "sort-by=s"     => \$sortby,
+                   "subsection=s"  => \$subsection);
+usage() unless $rc;
+
 while (scalar @ARGV) {
        my $arg = $ARGV[0];
        my $dir;
-       if ($arg eq "--codespeed") {
-               $codespeed = 1;
-               shift @ARGV;
-               next;
-       }
-       if ($arg =~ /--sort-by(?:=(.*))?/) {
-               shift @ARGV;
-               if (defined $1) {
-                       $sortby = $1;
-               } else {
-                       $sortby = shift @ARGV;
-                       if (! defined $sortby) {
-                               die "'--sort-by' requires an argument";
-                       }
-               }
-               next;
-       }
-       if ($arg eq "--subsection") {
-               shift @ARGV;
-               $subsection = $ARGV[0];
-               shift @ARGV;
-               if (! $subsection) {
-                       die "empty subsection";
-               }
-               next;
-       }
-       if ($arg eq "--reponame") {
-               shift @ARGV;
-               $reponame = $ARGV[0];
-               shift @ARGV;
-               if (! $reponame) {
-                       die "empty reponame";
-               }
-               next;
-       }
        last if -f $arg or $arg eq "--";
        if (! -d $arg) {
                my $rev = Git::command_oneline(qw(rev-parse --verify), $arg);
@@ -225,7 +214,8 @@ sub print_sorted_results {
        my ($sortby) = @_;
 
        if ($sortby ne "regression") {
-               die "only 'regression' is supported as '--sort-by' argument";
+               print "Only 'regression' is supported as '--sort-by' argument\n";
+               usage();
        }
 
        my @evolutions;
index 038255df4be413da74f6cc0a160243cfe467e867..3ebaf1552148da2c81635a8d7380a8417885c1d9 100755 (executable)
@@ -24,6 +24,12 @@ result_file="$info_dir/perf_${script_number}_${bisect_head}_results.txt"
 GIT_PERF_DIRS_OR_REVS="$bisect_head"
 export GIT_PERF_DIRS_OR_REVS
 
+# Don't use codespeed
+GIT_PERF_CODESPEED_OUTPUT=
+GIT_PERF_SEND_TO_CODESPEED=
+export GIT_PERF_CODESPEED_OUTPUT
+export GIT_PERF_SEND_TO_CODESPEED
+
 ./run "$script" >"$result_file" 2>&1 || die "Failed to run perf test '$script'"
 
 rtime=$(sed -n "s/^$script_number\.$test_number:.*\([0-9]\+\.[0-9]\+\)(.*).*\$/\1/p" "$result_file")
index 7fd87dd5444040ad7273be51c29a56dea738a861..af61d083b452c7973a2a703ef35fb57b76af84da 100755 (executable)
@@ -839,7 +839,7 @@ test_expect_success 'writing tree out with git write-tree' '
 '
 
 # we know the shape and contents of the tree and know the object ID for it.
-test_expect_success 'validate object ID of a known tree' '
+test_expect_success SHA1 'validate object ID of a known tree' '
        test "$tree" = 7bb943559a305bdd6bdee2cef6e5df2413c3d30a
     '
 
@@ -882,7 +882,7 @@ test_expect_success 'showing stage with git ls-files --stage' '
        git ls-files --stage >current
 '
 
-test_expect_success 'validate git ls-files output for a known tree' '
+test_expect_success SHA1 'validate git ls-files output for a known tree' '
        cat >expected <<-\EOF &&
        100644 f87290f8eb2cbbea7857214459a0739927eab154 0       path0
        120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0       path0sym
@@ -900,7 +900,7 @@ test_expect_success 'writing tree out with git write-tree' '
        tree=$(git write-tree)
 '
 
-test_expect_success 'validate object ID for a known tree' '
+test_expect_success SHA1 'validate object ID for a known tree' '
        test "$tree" = 087704a96baf1c2d1c869a8b084481e121c88b5b
 '
 
@@ -908,7 +908,7 @@ test_expect_success 'showing tree with git ls-tree' '
     git ls-tree $tree >current
 '
 
-test_expect_success 'git ls-tree output for a known tree' '
+test_expect_success SHA1 'git ls-tree output for a known tree' '
        cat >expected <<-\EOF &&
        100644 blob f87290f8eb2cbbea7857214459a0739927eab154    path0
        120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01    path0sym
@@ -924,7 +924,7 @@ test_expect_success 'showing tree with git ls-tree -r' '
        git ls-tree -r $tree >current
 '
 
-test_expect_success 'git ls-tree -r output for a known tree' '
+test_expect_success SHA1 'git ls-tree -r output for a known tree' '
        cat >expected <<-\EOF &&
        100644 blob f87290f8eb2cbbea7857214459a0739927eab154    path0
        120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01    path0sym
@@ -943,7 +943,7 @@ test_expect_success 'showing tree with git ls-tree -r -t' '
        git ls-tree -r -t $tree >current
 '
 
-test_expect_success 'git ls-tree -r output for a known tree' '
+test_expect_success SHA1 'git ls-tree -r output for a known tree' '
        cat >expected <<-\EOF &&
        100644 blob f87290f8eb2cbbea7857214459a0739927eab154    path0
        120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01    path0sym
@@ -964,7 +964,7 @@ test_expect_success 'writing partial tree out with git write-tree --prefix' '
        ptree=$(git write-tree --prefix=path3)
 '
 
-test_expect_success 'validate object ID for a known tree' '
+test_expect_success SHA1 'validate object ID for a known tree' '
        test "$ptree" = 21ae8269cacbe57ae09138dcc3a2887f904d02b3
 '
 
@@ -972,7 +972,7 @@ test_expect_success 'writing partial tree out with git write-tree --prefix' '
        ptree=$(git write-tree --prefix=path3/subp3)
 '
 
-test_expect_success 'validate object ID for a known tree' '
+test_expect_success SHA1 'validate object ID for a known tree' '
        test "$ptree" = 3c5e5399f3a333eddecce7a9b9465b63f65f51e2
 '
 
@@ -1006,7 +1006,7 @@ test_expect_success 'git read-tree followed by write-tree should be idempotent'
        test "$newtree" = "$tree"
 '
 
-test_expect_success 'validate git diff-files output for a know cache/work tree state' '
+test_expect_success SHA1 'validate git diff-files output for a know cache/work tree state' '
        cat >expected <<\EOF &&
 :100644 100644 f87290f8eb2cbbea7857214459a0739927eab154 0000000000000000000000000000000000000000 M     path0
 :120000 120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0000000000000000000000000000000000000000 M     path0sym
@@ -1033,21 +1033,21 @@ test_expect_success 'no diff after checkout and git update-index --refresh' '
 ################################################################
 P=087704a96baf1c2d1c869a8b084481e121c88b5b
 
-test_expect_success 'git commit-tree records the correct tree in a commit' '
+test_expect_success SHA1 'git commit-tree records the correct tree in a commit' '
        commit0=$(echo NO | git commit-tree $P) &&
        tree=$(git show --pretty=raw $commit0 |
                 sed -n -e "s/^tree //p" -e "/^author /q") &&
        test "z$tree" = "z$P"
 '
 
-test_expect_success 'git commit-tree records the correct parent in a commit' '
+test_expect_success SHA1 'git commit-tree records the correct parent in a commit' '
        commit1=$(echo NO | git commit-tree $P -p $commit0) &&
        parent=$(git show --pretty=raw $commit1 |
                sed -n -e "s/^parent //p" -e "/^author /q") &&
        test "z$commit0" = "z$parent"
 '
 
-test_expect_success 'git commit-tree omits duplicated parent in a commit' '
+test_expect_success SHA1 'git commit-tree omits duplicated parent in a commit' '
        commit2=$(echo NO | git commit-tree $P -p $commit0 -p $commit0) &&
             parent=$(git show --pretty=raw $commit2 |
                sed -n -e "s/^parent //p" -e "/^author /q" |
index 487b92a5de3238821ed3d8aadadb1f07d35e0609..bc27df7f383d14fb5fea6266591469c32e562c4e 100755 (executable)
@@ -25,6 +25,15 @@ test_expect_success "setup" '
        EOF
 '
 
+# make sure to exercise these code paths, the output is a bit tricky
+# to verify
+test_expect_success 'basic help commands' '
+       git help >/dev/null &&
+       git help -a >/dev/null &&
+       git help -g >/dev/null &&
+       git help -av >/dev/null
+'
+
 test_expect_success "works for commands and guides by default" '
        configure_help &&
        git help status &&
@@ -49,8 +58,23 @@ test_expect_success "--help does not work for guides" "
        test_i18ncmp expect actual
 "
 
+test_expect_success 'git help' '
+       git help >help.output &&
+       test_i18ngrep "^   clone  " help.output &&
+       test_i18ngrep "^   add    " help.output &&
+       test_i18ngrep "^   log    " help.output &&
+       test_i18ngrep "^   commit " help.output &&
+       test_i18ngrep "^   fetch  " help.output
+'
+test_expect_success 'git help -g' '
+       git help -g >help.output &&
+       test_i18ngrep "^   attributes " help.output &&
+       test_i18ngrep "^   everyday   " help.output &&
+       test_i18ngrep "^   tutorial   " help.output
+'
+
 test_expect_success 'generate builtin list' '
-       git --list-builtins >builtins
+       git --list-cmds=builtins >builtins
 '
 
 while read builtin
index f46e3c4995509f62f954231f53321f1d24756eff..21a8b531322ca75695ca16c6e8ec7e0ba1056f10 100755 (executable)
@@ -349,4 +349,90 @@ test_submodule_relative_url "(null)" "ssh://hostname:22/repo" "../subrepo" "ssh:
 test_submodule_relative_url "(null)" "user@host:path/to/repo" "../subrepo" "user@host:path/to/subrepo"
 test_submodule_relative_url "(null)" "user@host:repo" "../subrepo" "user@host:subrepo"
 
+test_expect_success 'match .gitmodules' '
+       test-tool path-utils is_dotgitmodules \
+               .gitmodules \
+               \
+               .git${u200c}modules \
+               \
+               .Gitmodules \
+               .gitmoduleS \
+               \
+               ".gitmodules " \
+               ".gitmodules." \
+               ".gitmodules  " \
+               ".gitmodules. " \
+               ".gitmodules ." \
+               ".gitmodules.." \
+               ".gitmodules   " \
+               ".gitmodules.  " \
+               ".gitmodules . " \
+               ".gitmodules  ." \
+               \
+               ".Gitmodules " \
+               ".Gitmodules." \
+               ".Gitmodules  " \
+               ".Gitmodules. " \
+               ".Gitmodules ." \
+               ".Gitmodules.." \
+               ".Gitmodules   " \
+               ".Gitmodules.  " \
+               ".Gitmodules . " \
+               ".Gitmodules  ." \
+               \
+               GITMOD~1 \
+               gitmod~1 \
+               GITMOD~2 \
+               gitmod~3 \
+               GITMOD~4 \
+               \
+               "GITMOD~1 " \
+               "gitmod~2." \
+               "GITMOD~3  " \
+               "gitmod~4. " \
+               "GITMOD~1 ." \
+               "gitmod~2   " \
+               "GITMOD~3.  " \
+               "gitmod~4 . " \
+               \
+               GI7EBA~1 \
+               gi7eba~9 \
+               \
+               GI7EB~10 \
+               GI7EB~11 \
+               GI7EB~99 \
+               GI7EB~10 \
+               GI7E~100 \
+               GI7E~101 \
+               GI7E~999 \
+               ~1000000 \
+               ~9999999 \
+               \
+               --not \
+               ".gitmodules x"  \
+               ".gitmodules .x" \
+               \
+               " .gitmodules" \
+               \
+               ..gitmodules \
+               \
+               gitmodules \
+               \
+               .gitmodule \
+               \
+               ".gitmodules x " \
+               ".gitmodules .x" \
+               \
+               GI7EBA~ \
+               GI7EBA~0 \
+               GI7EBA~~1 \
+               GI7EBA~X \
+               Gx7EBA~1 \
+               GI7EBX~1 \
+               \
+               GI7EB~1 \
+               GI7EB~01 \
+               GI7EB~1X
+'
+
 test_done
index 4ae0995cd9140d3030da7d975c74ff3d9e55b039..0c61268fd22ade66c6cbd91ad743986b33af0d5d 100755 (executable)
@@ -9,7 +9,7 @@ cache-tree extension.
 
 cmp_cache_tree () {
        test-tool dump-cache-tree | sed -e '/#(ref)/d' >actual &&
-       sed "s/$_x40/SHA/" <actual >filtered &&
+       sed "s/$OID_REGEX/SHA/" <actual >filtered &&
        test_cmp "$1" filtered
 }
 
index 3c4d2d6045bf026cf164374a41ae759d08a69bf8..013c5a7bc32849eaa1c2fe7dea007d2788e3ea8a 100755 (executable)
@@ -128,7 +128,7 @@ cat >expected <<\EOF
 EOF
 
 check_result () {
-       git ls-files --stage | sed -e 's/ '"$_x40"' / X /' >current &&
+       git ls-files --stage | sed -e 's/ '"$OID_REGEX"' / X /' >current &&
        test_cmp expected current
 }
 
index 5ededd8e400e7e64faa1bd559d67251ecbf8608e..1057a96b2498d1a8abf87f21f90d108eee2c2f96 100755 (executable)
@@ -30,7 +30,7 @@ read_tree_twoway () {
 compare_change () {
        sed -n >current \
            -e '/^--- /d; /^+++ /d; /^@@ /d;' \
-           -e 's/^\([-+][0-7][0-7][0-7][0-7][0-7][0-7]\) '"$_x40"' /\1 X /p' \
+           -e 's/^\([-+][0-7][0-7][0-7][0-7][0-7][0-7]\) '"$OID_REGEX"' /\1 X /p' \
            "$1"
        test_cmp expected current
 }
index 7ca2e65d102afd76dc4136a9f12c53a5ba0fce25..9c05f5e1f510664ca6ecce60eab5ffe1a30a58ef 100755 (executable)
@@ -16,7 +16,7 @@ compare_change () {
            -e '1{/^diff --git /d;}' \
            -e '2{/^index /d;}' \
            -e '/^--- /d; /^+++ /d; /^@@ /d;' \
-           -e 's/^\(.[0-7][0-7][0-7][0-7][0-7][0-7]\) '"$_x40"' /\1 X /' "$1"
+           -e 's/^\(.[0-7][0-7][0-7][0-7][0-7][0-7]\) '"$OID_REGEX"' /\1 X /' "$1"
        test_cmp expected current
 }
 
index 2ac3b940c611db08fd48e5782db13d2f8f1d48ec..13dd510b2ed8c4ce3b48666557bf68ad2aa03953 100755 (executable)
@@ -236,8 +236,8 @@ test_expect_success "--batch-check for an empty line" '
 '
 
 test_expect_success 'empty --batch-check notices missing object' '
-       echo "$_z40 missing" >expect &&
-       echo "$_z40" | git cat-file --batch-check="" >actual &&
+       echo "$ZERO_OID missing" >expect &&
+       echo "$ZERO_OID" | git cat-file --batch-check="" >actual &&
        test_cmp expect actual
 '
 
@@ -294,8 +294,8 @@ test_expect_success 'setup blobs which are likely to delta' '
 
 test_expect_success 'confirm that neither loose blob is a delta' '
        cat >expect <<-EOF &&
-       $_z40
-       $_z40
+       $ZERO_OID
+       $ZERO_OID
        EOF
        git cat-file --batch-check="%(deltabase)" <blobs >actual &&
        test_cmp expect actual
index 532682f51c4f9cefc34a44cf7e7f4231c60b7f65..a37753047e084c9be40251e7c4286084bff6870a 100755 (executable)
@@ -9,13 +9,13 @@ echo_without_newline() {
 }
 
 test_blob_does_not_exist() {
-       test_expect_success 'blob does not exist in database' "
+       test_expect_success SHA1 'blob does not exist in database' "
                test_must_fail git cat-file blob $1
        "
 }
 
 test_blob_exists() {
-       test_expect_success 'blob exists in database' "
+       test_expect_success SHA1 'blob exists in database' "
                git cat-file blob $1
        "
 }
@@ -73,19 +73,19 @@ test_expect_success "Can't use --path with --no-filters" '
 
 push_repo
 
-test_expect_success 'hash a file' '
+test_expect_success SHA1 'hash a file' '
        test $hello_sha1 = $(git hash-object hello)
 '
 
 test_blob_does_not_exist $hello_sha1
 
-test_expect_success 'hash from stdin' '
+test_expect_success SHA1 'hash from stdin' '
        test $example_sha1 = $(git hash-object --stdin < example)
 '
 
 test_blob_does_not_exist $example_sha1
 
-test_expect_success 'hash a file and write to database' '
+test_expect_success SHA1 'hash a file and write to database' '
        test $hello_sha1 = $(git hash-object -w hello)
 '
 
@@ -161,7 +161,7 @@ pop_repo
 for args in "-w --stdin" "--stdin -w"; do
        push_repo
 
-       test_expect_success "hash from stdin and write to database ($args)" '
+       test_expect_success SHA1 "hash from stdin and write to database ($args)" '
                test $example_sha1 = $(git hash-object $args < example)
        '
 
@@ -176,14 +176,14 @@ example"
 sha1s="$hello_sha1
 $example_sha1"
 
-test_expect_success "hash two files with names on stdin" '
+test_expect_success SHA1 "hash two files with names on stdin" '
        test "$sha1s" = "$(echo_without_newline "$filenames" | git hash-object --stdin-paths)"
 '
 
 for args in "-w --stdin-paths" "--stdin-paths -w"; do
        push_repo
 
-       test_expect_success "hash two files with names on stdin and write to database ($args)" '
+       test_expect_success SHA1 "hash two files with names on stdin and write to database ($args)" '
                test "$sha1s" = "$(echo_without_newline "$filenames" | git hash-object $args)"
        '
 
index a6a04b6b90d290a445ed7c33e5928a9a168ed28b..57f0770df1410ea2005d8637323ff0eeeb82d880 100755 (executable)
@@ -32,7 +32,7 @@ settree () {
 
 checkindex () {
        git ls-files -s |
-       sed "s|^[0-7][0-7]* $_x40 \([0-3]\)     |\1 |" >current &&
+       sed "s|^[0-7][0-7]* $OID_REGEX \([0-3]\)        |\1 |" >current &&
        cat >expect &&
        test_cmp expect current
 }
index eed31ffa30eebaa994b2fee569eb753e2a6a1477..37dc689d8c98fd776895e1ce6d18faee092001d3 100755 (executable)
@@ -73,4 +73,8 @@ test_expect_success 'can parse blob ending with CR' '
        test_cmp expect actual
 '
 
+test_expect_success 'config --blob outside of a repository is an error' '
+       test_must_fail nongit git config --blob=foo --list
+'
+
 test_done
index 664a3a4e4e9b81d3e017c22414b0bbe2ef25b83a..e1fd0f0ca8b5d731d79ccca1d6e6140683a3665f 100755 (executable)
@@ -6,7 +6,7 @@
 test_description='Test git update-ref and basic ref logging'
 . ./test-lib.sh
 
-Z=$_z40
+Z=$ZERO_OID
 
 m=refs/heads/master
 n_dir=refs/heads/gu
@@ -457,6 +457,66 @@ test_expect_success 'git cat-file blob master@{2005-05-26 23:42}:F (expect OTHER
        test OTHER = $(git cat-file blob "master@{2005-05-26 23:42}:F")
 '
 
+# Test adding and deleting pseudorefs
+
+test_expect_success 'given old value for missing pseudoref, do not create' '
+       test_must_fail git update-ref PSEUDOREF $A $B 2>err &&
+       test_path_is_missing .git/PSEUDOREF &&
+       grep "could not read ref" err
+'
+
+test_expect_success 'create pseudoref' '
+       git update-ref PSEUDOREF $A &&
+       test $A = $(cat .git/PSEUDOREF)
+'
+
+test_expect_success 'overwrite pseudoref with no old value given' '
+       git update-ref PSEUDOREF $B &&
+       test $B = $(cat .git/PSEUDOREF)
+'
+
+test_expect_success 'overwrite pseudoref with correct old value' '
+       git update-ref PSEUDOREF $C $B &&
+       test $C = $(cat .git/PSEUDOREF)
+'
+
+test_expect_success 'do not overwrite pseudoref with wrong old value' '
+       test_must_fail git update-ref PSEUDOREF $D $E 2>err &&
+       test $C = $(cat .git/PSEUDOREF) &&
+       grep "unexpected object ID" err
+'
+
+test_expect_success 'delete pseudoref' '
+       git update-ref -d PSEUDOREF &&
+       test_path_is_missing .git/PSEUDOREF
+'
+
+test_expect_success 'do not delete pseudoref with wrong old value' '
+       git update-ref PSEUDOREF $A &&
+       test_must_fail git update-ref -d PSEUDOREF $B 2>err &&
+       test $A = $(cat .git/PSEUDOREF) &&
+       grep "unexpected object ID" err
+'
+
+test_expect_success 'delete pseudoref with correct old value' '
+       git update-ref -d PSEUDOREF $A &&
+       test_path_is_missing .git/PSEUDOREF
+'
+
+test_expect_success 'create pseudoref with old OID zero' '
+       git update-ref PSEUDOREF $A $Z &&
+       test $A = $(cat .git/PSEUDOREF)
+'
+
+test_expect_success 'do not overwrite pseudoref with old OID zero' '
+       test_when_finished git update-ref -d PSEUDOREF &&
+       test_must_fail git update-ref PSEUDOREF $B $Z 2>err &&
+       test $A = $(cat .git/PSEUDOREF) &&
+       grep "already exists" err
+'
+
+# Test --stdin
+
 a=refs/heads/a
 b=refs/heads/b
 c=refs/heads/c
index 9e782a8122f84939156e2bb4e0f871d86399f938..a4ebb0b65fecfada915209cfbc994a1b8ddce439 100755 (executable)
@@ -65,7 +65,7 @@ reset_to_sane
 test_expect_success 'symbolic-ref fails to delete real ref' '
        echo "fatal: Cannot delete refs/heads/foo, not a symbolic ref" >expect &&
        test_must_fail git symbolic-ref -d refs/heads/foo >actual 2>&1 &&
-       test_path_is_file .git/refs/heads/foo &&
+       git rev-parse --verify refs/heads/foo &&
        test_cmp expect actual
 '
 reset_to_sane
index 2211f9831fb07f933c8c3f0c7cfd89cc5512c253..4623ae15c4fd45df64b4acb94308e5ac4ca8f1f5 100755 (executable)
@@ -50,13 +50,13 @@ test_expect_success 'create_symref(FOO, refs/heads/master)' '
 '
 
 test_expect_success 'for_each_reflog()' '
-       echo $_z40 > .git/logs/PSEUDO-MAIN &&
+       echo $ZERO_OID > .git/logs/PSEUDO-MAIN &&
        mkdir -p     .git/logs/refs/bisect &&
-       echo $_z40 > .git/logs/refs/bisect/random &&
+       echo $ZERO_OID > .git/logs/refs/bisect/random &&
 
-       echo $_z40 > .git/worktrees/wt/logs/PSEUDO-WT &&
+       echo $ZERO_OID > .git/worktrees/wt/logs/PSEUDO-WT &&
        mkdir -p     .git/worktrees/wt/logs/refs/bisect &&
-       echo $_z40 > .git/worktrees/wt/logs/refs/bisect/wt-random &&
+       echo $ZERO_OID > .git/worktrees/wt/logs/refs/bisect/wt-random &&
 
        $RWT for-each-reflog | cut -c 42- | sort >actual &&
        cat >expected <<-\EOF &&
index cb4b66e29dd8c38c734f5d29df3e8062c78884a9..91fd71444dbe40400b6608b56d51ae72e8ceafbc 100755 (executable)
@@ -713,7 +713,7 @@ test_expect_success 'fsck notices dangling objects' '
 
 test_expect_success 'fsck $name notices bogus $name' '
        test_must_fail git fsck bogus &&
-       test_must_fail git fsck $_z40
+       test_must_fail git fsck $ZERO_OID
 '
 
 test_expect_success 'bogus head does not fallback to all heads' '
@@ -723,7 +723,7 @@ test_expect_success 'bogus head does not fallback to all heads' '
        blob=$(git rev-parse :foo) &&
        test_when_finished "git rm --cached foo" &&
        remove_object $blob &&
-       test_must_fail git fsck $_z40 >out 2>&1 &&
+       test_must_fail git fsck $ZERO_OID >out 2>&1 &&
        ! grep $blob out
 '
 
index 9c0bc6525034021a1947d07faec84570d0dd629a..afcdfafe45521872b0e2f1648da782c61d0cc1d5 100755 (executable)
@@ -238,10 +238,10 @@ test_expect_success '_gently() groks relative GIT_DIR & GIT_WORK_TREE' '
 
 test_expect_success 'diff-index respects work tree under .git dir' '
        cat >diff-index-cached.expected <<-EOF &&
-       :000000 100644 $_z40 $EMPTY_BLOB A      sub/dir/tracked
+       :000000 100644 $ZERO_OID $EMPTY_BLOB A  sub/dir/tracked
        EOF
        cat >diff-index.expected <<-EOF &&
-       :000000 100644 $_z40 $_z40 A    sub/dir/tracked
+       :000000 100644 $ZERO_OID $ZERO_OID A    sub/dir/tracked
        EOF
 
        (
@@ -257,7 +257,7 @@ test_expect_success 'diff-index respects work tree under .git dir' '
 
 test_expect_success 'diff-files respects work tree under .git dir' '
        cat >diff-files.expected <<-EOF &&
-       :100644 100644 $EMPTY_BLOB $_z40 M      sub/dir/tracked
+       :100644 100644 $EMPTY_BLOB $ZERO_OID M  sub/dir/tracked
        EOF
 
        (
index 711704ba5a052b15d94ff1ce958ada3fb86530a9..96fe3754c8c96fd65286d4eea44cb65b0edde343 100755 (executable)
@@ -22,6 +22,12 @@ one tagged as v1.0.0.  They all have one regular file each.
 
 . ./test-lib.sh
 
+if ! test_have_prereq SHA1
+then
+       skip_all='not using SHA-1 for objects'
+       test_done
+fi
+
 test_expect_success 'blob and tree' '
        test_tick &&
        (
@@ -361,4 +367,25 @@ test_expect_success 'core.disambiguate does not override context' '
                git -c core.disambiguate=committish rev-parse $sha1^{tree}
 '
 
+test_expect_success C_LOCALE_OUTPUT 'ambiguous commits are printed by type first, then hash order' '
+       test_must_fail git rev-parse 0000 2>stderr &&
+       grep ^hint: stderr >hints &&
+       grep 0000 hints >objects &&
+       cat >expected <<-\EOF &&
+       tag
+       commit
+       tree
+       blob
+       EOF
+       awk "{print \$3}" <objects >objects.types &&
+       uniq <objects.types >objects.types.uniq &&
+       test_cmp expected objects.types.uniq &&
+       for type in tag commit tree blob
+       do
+               grep $type objects >$type.objects &&
+               sort $type.objects >$type.objects.sorted &&
+               test_cmp $type.objects.sorted $type.objects
+       done
+'
+
 test_done
index 73cc9323cdc2a40105bd6218983381b8a72b381a..4171f1e14103135ef3e47a3b3df19d5a285b4a8a 100755 (executable)
@@ -4,7 +4,7 @@ test_description='test handling of bogus index entries'
 . ./test-lib.sh
 
 test_expect_success 'create tree with null sha1' '
-       tree=$(printf "160000 commit $_z40\\tbroken\\n" | git mktree)
+       tree=$(printf "160000 commit $ZERO_OID\\tbroken\\n" | git mktree)
 '
 
 test_expect_success 'read-tree refuses to read null sha1' '
index e4f4c4df4ee3686e5ad1621134a827b941b38bca..1e81b33b2e3a23a135c9b9440787a6e0c0deaa22 100755 (executable)
@@ -426,7 +426,7 @@ test_expect_success 'writing split index with null sha1 does not write cache tre
        git commit -m "commit" &&
        {
                git ls-tree HEAD &&
-               printf "160000 commit $_z40\\tbroken\\n"
+               printf "160000 commit $ZERO_OID\\tbroken\\n"
        } >broken-tree &&
        echo "add broken entry" >msg &&
 
index c5501b008c8f56ac0712d9f475ebc460a0d4eea4..0e8d56aa7631cb30eebd468abe8d93f720d92913 100755 (executable)
@@ -15,7 +15,7 @@ test_expect_success 'checkout should not start branch from a tree' '
 '
 
 test_expect_success 'checkout master from invalid HEAD' '
-       echo $_z40 >.git/HEAD &&
+       echo $ZERO_OID >.git/HEAD &&
        git checkout master --
 '
 
index d0d2e4f7ec3310ec51da7144fa87151129f393c0..d2e49f763263333e8e269051686d3b8b2cae44ba 100755 (executable)
@@ -198,13 +198,25 @@ test_expect_success '"add" with <branch> omitted' '
        test_cmp_rev HEAD bat
 '
 
-test_expect_success '"add" auto-vivify does not clobber existing branch' '
-       test_commit c1 &&
-       test_commit c2 &&
-       git branch precious HEAD~1 &&
-       test_must_fail git worktree add precious &&
-       test_cmp_rev HEAD~1 precious &&
-       test_path_is_missing precious
+test_expect_success '"add" checks out existing branch of dwimd name' '
+       git branch dwim HEAD~1 &&
+       git worktree add dwim &&
+       test_cmp_rev HEAD~1 dwim &&
+       (
+               cd dwim &&
+               test_cmp_rev HEAD dwim
+       )
+'
+
+test_expect_success '"add <path>" dwim fails with checked out branch' '
+       git checkout -b test-branch &&
+       test_must_fail git worktree add test-branch &&
+       test_path_is_missing test-branch
+'
+
+test_expect_success '"add --force" with existing dwimd name doesnt die' '
+       git checkout test-branch &&
+       git worktree add --force test-branch
 '
 
 test_expect_success '"add" no auto-vivify with --detach and <branch> omitted' '
@@ -465,7 +477,7 @@ post_checkout_hook () {
 test_expect_success '"add" invokes post-checkout hook (branch)' '
        post_checkout_hook &&
        {
-               echo $_z40 $(git rev-parse HEAD) 1 &&
+               echo $ZERO_OID $(git rev-parse HEAD) 1 &&
                echo $(pwd)/.git/worktrees/gumby &&
                echo $(pwd)/gumby
        } >hook.expect &&
@@ -476,7 +488,7 @@ test_expect_success '"add" invokes post-checkout hook (branch)' '
 test_expect_success '"add" invokes post-checkout hook (detached)' '
        post_checkout_hook &&
        {
-               echo $_z40 $(git rev-parse HEAD) 1 &&
+               echo $ZERO_OID $(git rev-parse HEAD) 1 &&
                echo $(pwd)/.git/worktrees/grumpy &&
                echo $(pwd)/grumpy
        } >hook.expect &&
@@ -494,7 +506,7 @@ test_expect_success '"add --no-checkout" suppresses post-checkout hook' '
 test_expect_success '"add" in other worktree invokes post-checkout hook' '
        post_checkout_hook &&
        {
-               echo $_z40 $(git rev-parse HEAD) 1 &&
+               echo $ZERO_OID $(git rev-parse HEAD) 1 &&
                echo $(pwd)/.git/worktrees/guppy &&
                echo $(pwd)/guppy
        } >hook.expect &&
@@ -506,7 +518,7 @@ test_expect_success '"add" in bare repo invokes post-checkout hook' '
        rm -rf bare &&
        git clone --bare . bare &&
        {
-               echo $_z40 $(git --git-dir=bare rev-parse HEAD) 1 &&
+               echo $ZERO_OID $(git --git-dir=bare rev-parse HEAD) 1 &&
                echo $(pwd)/bare/worktrees/goozy &&
                echo $(pwd)/goozy
        } >hook.expect &&
index 720063bf0d09bb8c577b78e099187abfc7da71b0..bb6fb9b12cb7f41cea671a7df830ccc97c592e4f 100755 (executable)
@@ -116,7 +116,7 @@ test_expect_success 'broken main worktree still at the top' '
                git worktree add linked &&
                cat >expected <<-EOF &&
                worktree $(pwd)
-               HEAD $_z40
+               HEAD $ZERO_OID
 
                EOF
                cd linked &&
index 1db7e6a1abbebb63f811fa6ecbcd1db67607298e..2242cd098ec4ee0b278c008220d8c2a678b174c7 100755 (executable)
@@ -37,7 +37,7 @@ test_expect_success '--cacheinfo does not accept blob null sha1' '
        echo content >file &&
        git add file &&
        git rev-parse :file >expect &&
-       test_must_fail git update-index --cacheinfo 100644 $_z40 file &&
+       test_must_fail git update-index --cacheinfo 100644 $ZERO_OID file &&
        git rev-parse :file >actual &&
        test_cmp expect actual
 '
@@ -47,7 +47,7 @@ test_expect_success '--cacheinfo does not accept gitlink null sha1' '
        (cd submodule && test_commit foo) &&
        git add submodule &&
        git rev-parse :submodule >expect &&
-       test_must_fail git update-index --cacheinfo 160000 $_z40 submodule &&
+       test_must_fail git update-index --cacheinfo 160000 $ZERO_OID submodule &&
        git rev-parse :submodule >actual &&
        test_cmp expect actual
 '
index 954fc51e5b560af918661a0ee051521a43f4efba..a4eec0a3465bc02fb5c88e007ee4e32aa9c2794f 100755 (executable)
@@ -75,35 +75,35 @@ test_expect_success modify '
                git ls-tree -r HEAD |
                sed -e "s/^/:/" -e "
                        /       caskly/{
-                               s/      caskly/ $_z40 D&/
+                               s/      caskly/ $ZERO_OID D&/
                                s/blob/000000/
                        }
                        /       nitfol/{
-                               s/      nitfol/ $_z40 $T_letter&/
+                               s/      nitfol/ $ZERO_OID $T_letter&/
                                s/blob/100644/
                        }
                        /       rezrov.bozbar/{
-                               s/      rezrov.bozbar/ $_z40 D&/
+                               s/      rezrov.bozbar/ $ZERO_OID D&/
                                s/blob/000000/
                        }
                        /       xyzzy/{
-                               s/      xyzzy/ $_z40 D&/
+                               s/      xyzzy/ $ZERO_OID D&/
                                s/blob/000000/
                        }
                        /       yomin/{
-                           s/  yomin/ $_z40 T&/
+                           s/  yomin/ $ZERO_OID T&/
                                s/blob/160000/
                        }
                "
        } >expect &&
        {
                cat expect
-               echo ":100644 160000 $_empty $_z40 T    yonk"
-               echo ":100644 000000 $_empty $_z40 D    zifmia"
+               echo ":100644 160000 $_empty $ZERO_OID T        yonk"
+               echo ":100644 000000 $_empty $ZERO_OID D        zifmia"
        } >expect-files &&
        {
                cat expect
-               echo ":000000 160000 $_z40 $_z40 A      yonk"
+               echo ":000000 160000 $ZERO_OID $ZERO_OID A      yonk"
        } >expect-index &&
        {
                echo "100644 $_empty 0  nitfol"
index 78236dc7d851663c4488e7acae5e6997edd35f1c..04d840a5448567bf438688c4aebd7e94d63da4ec 100755 (executable)
@@ -27,12 +27,12 @@ test_expect_success 'git status' '
 
 test_expect_success 'git status with porcelain v2' '
        git status --porcelain=v2 | grep -v "^?" >actual &&
-       nam1=d00491fd7e5bb6fa28c517a0bb32b8b506539d4d &&
-       nam2=ce013625030ba8dba906f756967f9e9ca394464a &&
+       nam1=$(echo 1 | git hash-object --stdin) &&
+       nam2=$(git hash-object elif) &&
        cat >expect <<-EOF &&
-       1 DA N... 100644 000000 100644 $nam1 $_z40 1.t
-       1 A. N... 000000 100644 100644 $_z40 $nam2 elif
-       1 .A N... 000000 000000 100644 $_z40 $_z40 file
+       1 DA N... 100644 000000 100644 $nam1 $ZERO_OID 1.t
+       1 A. N... 000000 100644 100644 $ZERO_OID $nam2 elif
+       1 .A N... 000000 000000 100644 $ZERO_OID $ZERO_OID file
        EOF
        test_cmp expect actual
 '
@@ -181,7 +181,7 @@ test_expect_success 'rename detection finds the right names' '
                EOF
                test_cmp expected.2 actual.2 &&
 
-               hash=12f00e90b6ef79117ce6e650416b8cf517099b78 &&
+               hash=$(git hash-object third) &&
                git status --porcelain=v2 | grep -v "^?" >actual.3 &&
                cat >expected.3 <<-EOF &&
                2 .R N... 100644 100644 100644 $hash $hash R100 third   first
@@ -212,7 +212,7 @@ test_expect_success 'double rename detection in status' '
                EOF
                test_cmp expected.2 actual.2 &&
 
-               hash=12f00e90b6ef79117ce6e650416b8cf517099b78 &&
+               hash=$(git hash-object third) &&
                git status --porcelain=v2 | grep -v "^?" >actual.3 &&
                cat >expected.3 <<-EOF &&
                2 R. N... 100644 100644 100644 $hash $hash R100 second  first
index b9c4028496f97bc9a620df89768e07c6a99bb07d..3d9fae68c41c85b4393e618cfe3706212d5c2dce 100755 (executable)
@@ -309,4 +309,22 @@ test_expect_success 'last wins in --find-renames=<m> --rename-threshold=<n>' '
        check_threshold_0
 '
 
+test_expect_success 'merge.renames disables rename detection' '
+       git read-tree --reset -u HEAD &&
+       git -c merge.renames=false merge-recursive $tail &&
+       check_no_renames
+'
+
+test_expect_success 'merge.renames defaults to diff.renames' '
+       git read-tree --reset -u HEAD &&
+       git -c diff.renames=false merge-recursive $tail &&
+       check_no_renames
+'
+
+test_expect_success 'merge.renames overrides diff.renames' '
+       git read-tree --reset -u HEAD &&
+       test_must_fail git -c diff.renames=false -c merge.renames=true merge-recursive $tail &&
+       $check_50
+'
+
 test_done
index 325114f8fee810ac9cf1836f50e060f69b5fe46e..18baf49a49c792952f3c8028cafc2d193d88cb5d 100755 (executable)
@@ -32,7 +32,7 @@ test_expect_success \
      echo $tree'
 
 test_output () {
-    sed -e "s/ $_x40   / X     /" <current >check
+    sed -e "s/ $OID_REGEX      / X     /" <current >check
     test_cmp expected check
 }
 
index 327ded4000b47cf3f4da42515fb803c47c0bd742..12bf31022a871c4d2e43866ec6053c55205a3476 100755 (executable)
@@ -40,7 +40,7 @@ test_expect_success 'setup' '
 '
 
 test_output () {
-       sed -e "s/ $_x40        / X     /" <current >check &&
+       sed -e "s/ $OID_REGEX   / X     /" <current >check &&
        test_cmp expected check
 }
 
index 09dcf043fdff1495dc4e0d1e56b9118c4f5feaeb..14520913afca8d1c980e4cd58d0b943a1a498611 100755 (executable)
@@ -17,7 +17,8 @@ test_expect_success 'setup' '
 '
 
 test_expect_success 'ls-tree fails with non-zero exit code on broken tree' '
-       rm -f .git/objects/5f/cffbd6e4c5c5b8d81f5e9314b20e338e3ffff5 &&
+       tree=$(git rev-parse HEAD:a) &&
+       rm -f .git/objects/$(echo $tree | sed -e "s,^\(..\),\1/,") &&
        test_must_fail git ls-tree -r HEAD
 '
 
index c0ef946811dd534dfa5e704c992b6ab1fab82c35..69ea8202f437a47b1c52b60f4a2afacd3d5a4f21 100755 (executable)
@@ -47,7 +47,7 @@ test_expect_success 'git branch HEAD should fail' '
 '
 
 cat >expect <<EOF
-$_z40 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000        branch: Created from master
+$ZERO_OID $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000    branch: Created from master
 EOF
 test_expect_success 'git branch -l d/e/f should create a branch and a log' '
        GIT_COMMITTER_DATE="2005-05-26 23:30" \
@@ -234,34 +234,34 @@ test_expect_success 'git branch -M master2 master2 should work when master is ch
 
 test_expect_success 'git branch -v -d t should work' '
        git branch t &&
-       test_path_is_file .git/refs/heads/t &&
+       git rev-parse --verify refs/heads/t &&
        git branch -v -d t &&
-       test_path_is_missing .git/refs/heads/t
+       test_must_fail git rev-parse --verify refs/heads/t
 '
 
 test_expect_success 'git branch -v -m t s should work' '
        git branch t &&
-       test_path_is_file .git/refs/heads/t &&
+       git rev-parse --verify refs/heads/t &&
        git branch -v -m t s &&
-       test_path_is_missing .git/refs/heads/t &&
-       test_path_is_file .git/refs/heads/s &&
+       test_must_fail git rev-parse --verify refs/heads/t &&
+       git rev-parse --verify refs/heads/s &&
        git branch -d s
 '
 
 test_expect_success 'git branch -m -d t s should fail' '
        git branch t &&
-       test_path_is_file .git/refs/heads/t &&
+       git rev-parse refs/heads/t &&
        test_must_fail git branch -m -d t s &&
        git branch -d t &&
-       test_path_is_missing .git/refs/heads/t
+       test_must_fail git rev-parse refs/heads/t
 '
 
 test_expect_success 'git branch --list -d t should fail' '
        git branch t &&
-       test_path_is_file .git/refs/heads/t &&
+       git rev-parse refs/heads/t &&
        test_must_fail git branch --list -d t &&
        git branch -d t &&
-       test_path_is_missing .git/refs/heads/t
+       test_must_fail git rev-parse refs/heads/t
 '
 
 test_expect_success 'git branch --list -v with --abbrev' '
@@ -901,7 +901,7 @@ test_expect_success '--set-upstream-to notices an error to set branch as own ups
 
 # Keep this test last, as it changes the current branch
 cat >expect <<EOF
-$_z40 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000        branch: Created from master
+$ZERO_OID $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000    branch: Created from master
 EOF
 test_expect_success 'git checkout -b g/h/i -l should create a branch and a log' '
        GIT_COMMITTER_DATE="2005-05-26 23:30" \
index 59c766540e5361af0eab19e33d1d61ef650bc72d..c65826ddaced2f4bafeea0e4570ef9018218692c 100755 (executable)
@@ -1204,10 +1204,6 @@ test_expect_success 'drop' '
        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 &&
@@ -1215,7 +1211,9 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = ignore' '
        FAKE_LINES="1 2 3 4" \
                git rebase -i --root 2>actual &&
        test D = $(git cat-file commit HEAD | sed -ne \$p) &&
-       test_i18ncmp expect actual
+       test_i18ngrep \
+               "Successfully rebased and updated refs/heads/missing-commit" \
+               actual
 '
 
 cat >expect <<EOF
@@ -1227,15 +1225,24 @@ 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.
 
+Rebasing (1/4)
+Rebasing (2/4)
+Rebasing (3/4)
+Rebasing (4/4)
 Successfully rebased and updated refs/heads/missing-commit.
 EOF
 
+cr_to_nl () {
+       tr '\015' '\012'
+}
+
 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 &&
+               git rebase -i --root 2>actual.2 &&
+       cr_to_nl <actual.2 >actual &&
        test_i18ncmp expect actual &&
        test D = $(git cat-file commit HEAD | sed -ne \$p)
 '
index 9214d0bb511e24f401188790858dc1e0ca1b92fd..03bf1b8a3b3df2e44ed0f70dc176c25af9ac244a 100755 (executable)
@@ -88,6 +88,55 @@ test_expect_success 'rebase passes merge strategy options correctly' '
        git rebase --continue
 '
 
+test_expect_success '--skip after failed fixup cleans commit message' '
+       test_when_finished "test_might_fail git rebase --abort" &&
+       git checkout -b with-conflicting-fixup &&
+       test_commit wants-fixup &&
+       test_commit "fixup! wants-fixup" wants-fixup.t 1 wants-fixup-1 &&
+       test_commit "fixup! wants-fixup" wants-fixup.t 2 wants-fixup-2 &&
+       test_commit "fixup! wants-fixup" wants-fixup.t 3 wants-fixup-3 &&
+       test_must_fail env FAKE_LINES="1 fixup 2 squash 4" \
+               git rebase -i HEAD~4 &&
+
+       : now there is a conflict, and comments in the commit message &&
+       git show HEAD >out &&
+       grep "fixup! wants-fixup" out &&
+
+       : skip and continue &&
+       echo "cp \"\$1\" .git/copy.txt" | write_script copy-editor.sh &&
+       (test_set_editor "$PWD/copy-editor.sh" && git rebase --skip) &&
+
+       : the user should not have had to edit the commit message &&
+       test_path_is_missing .git/copy.txt &&
+
+       : now the comments in the commit message should have been cleaned up &&
+       git show HEAD >out &&
+       ! grep "fixup! wants-fixup" out &&
+
+       : now, let us ensure that "squash" is handled correctly &&
+       git reset --hard wants-fixup-3 &&
+       test_must_fail env FAKE_LINES="1 squash 4 squash 2 squash 4" \
+               git rebase -i HEAD~4 &&
+
+       : the first squash failed, but there are two more in the chain &&
+       (test_set_editor "$PWD/copy-editor.sh" &&
+        test_must_fail git rebase --skip) &&
+
+       : not the final squash, no need to edit the commit message &&
+       test_path_is_missing .git/copy.txt &&
+
+       : The first squash was skipped, therefore: &&
+       git show HEAD >out &&
+       test_i18ngrep "# This is a combination of 2 commits" out &&
+
+       (test_set_editor "$PWD/copy-editor.sh" && git rebase --skip) &&
+       git show HEAD >out &&
+       test_i18ngrep ! "# This is a combination" out &&
+
+       : Final squash failed, but there was still a squash &&
+       test_i18ngrep "# This is a combination of 2 commits" .git/copy.txt
+'
+
 test_expect_success 'setup rerere database' '
        rm -fr .git/rebase-* &&
        git reset --hard commit-new-file-F3-on-topic-branch &&
index b078f930462d7187546376c9fe2331b3e0303773..99b2aac9219da0e9a77e845509ca7b84c65ab88b 100755 (executable)
@@ -217,6 +217,7 @@ test_run_rebase success ''
 test_run_rebase success -m
 test_run_rebase success -i
 test_run_rebase failure -p
+test_run_rebase success --rebase-merges
 
 #       m
 #      /
@@ -327,9 +328,9 @@ test_run_rebase () {
                test_cmp_rev c HEAD
        "
 }
-test_run_rebase failure ''
-test_run_rebase failure -m
-test_run_rebase failure -i
+test_run_rebase success ''
+test_run_rebase success -m
+test_run_rebase success -i
 test_run_rebase failure -p
 
 test_run_rebase () {
diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
new file mode 100755 (executable)
index 0000000..2608e54
--- /dev/null
@@ -0,0 +1,333 @@
+#!/bin/sh
+#
+# Copyright (c) 2018 Johannes E. Schindelin
+#
+
+test_description='git rebase -i --rebase-merges
+
+This test runs git rebase "interactively", retaining the branch structure by
+recreating merge commits.
+
+Initial setup:
+
+    -- B --                   (first)
+   /       \
+ A - C - D - E - H            (master)
+       \       /
+         F - G                (second)
+'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+test_cmp_graph () {
+       cat >expect &&
+       git log --graph --boundary --format=%s "$@" >output &&
+       sed "s/ *$//" <output >output.trimmed &&
+       test_cmp expect output.trimmed
+}
+
+test_expect_success 'setup' '
+       write_script replace-editor.sh <<-\EOF &&
+       mv "$1" "$(git rev-parse --git-path ORIGINAL-TODO)"
+       cp script-from-scratch "$1"
+       EOF
+
+       test_commit A &&
+       git checkout -b first &&
+       test_commit B &&
+       git checkout master &&
+       test_commit C &&
+       test_commit D &&
+       git merge --no-commit B &&
+       test_tick &&
+       git commit -m E &&
+       git tag -m E E &&
+       git checkout -b second C &&
+       test_commit F &&
+       test_commit G &&
+       git checkout master &&
+       git merge --no-commit G &&
+       test_tick &&
+       git commit -m H &&
+       git tag -m H H
+'
+
+test_expect_success 'create completely different structure' '
+       cat >script-from-scratch <<-\EOF &&
+       label onto
+
+       # onebranch
+       pick G
+       pick D
+       label onebranch
+
+       # second
+       reset onto
+       pick B
+       label second
+
+       reset onto
+       merge -C H second
+       merge onebranch # Merge the topic branch '\''onebranch'\''
+       EOF
+       cp script-from-scratch script-from-scratch-orig &&
+       test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
+       test_tick &&
+       git rebase -i -r A &&
+       test_cmp_graph <<-\EOF
+       *   Merge the topic branch '\''onebranch'\''
+       |\
+       | * D
+       | * G
+       * |   H
+       |\ \
+       | |/
+       |/|
+       | * B
+       |/
+       * A
+       EOF
+'
+
+test_expect_success 'generate correct todo list' '
+       cat >expect <<-\EOF &&
+       label onto
+
+       reset onto
+       pick d9df450 B
+       label E
+
+       reset onto
+       pick 5dee784 C
+       label branch-point
+       pick ca2c861 F
+       pick 088b00a G
+       label H
+
+       reset branch-point # C
+       pick 12bd07b D
+       merge -C 2051b56 E # E
+       merge -C 233d48a H # H
+
+       EOF
+
+       grep -v "^#" <.git/ORIGINAL-TODO >output &&
+       test_cmp expect output
+'
+
+test_expect_success '`reset` refuses to overwrite untracked files' '
+       git checkout -b refuse-to-reset &&
+       test_commit dont-overwrite-untracked &&
+       git checkout @{-1} &&
+       : >dont-overwrite-untracked.t &&
+       echo "reset refs/tags/dont-overwrite-untracked" >script-from-scratch &&
+       test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
+       test_must_fail git rebase -r HEAD &&
+       git rebase --abort
+'
+
+test_expect_success 'failed `merge` writes patch (may be rescheduled, too)' '
+       test_when_finished "test_might_fail git rebase --abort" &&
+       git checkout -b conflicting-merge A &&
+
+       : fail because of conflicting untracked file &&
+       >G.t &&
+       echo "merge -C H G" >script-from-scratch &&
+       test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
+       test_tick &&
+       test_must_fail git rebase -ir HEAD &&
+       grep "^merge -C .* G$" .git/rebase-merge/done &&
+       grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo &&
+       test_path_is_file .git/rebase-merge/patch &&
+
+       : fail because of merge conflict &&
+       rm G.t .git/rebase-merge/patch &&
+       git reset --hard &&
+       test_commit conflicting-G G.t not-G conflicting-G &&
+       test_must_fail git rebase --continue &&
+       ! grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo &&
+       test_path_is_file .git/rebase-merge/patch
+'
+
+test_expect_success 'with a branch tip that was cherry-picked already' '
+       git checkout -b already-upstream master &&
+       base="$(git rev-parse --verify HEAD)" &&
+
+       test_commit A1 &&
+       test_commit A2 &&
+       git reset --hard $base &&
+       test_commit B1 &&
+       test_tick &&
+       git merge -m "Merge branch A" A2 &&
+
+       git checkout -b upstream-with-a2 $base &&
+       test_tick &&
+       git cherry-pick A2 &&
+
+       git checkout already-upstream &&
+       test_tick &&
+       git rebase -i -r upstream-with-a2 &&
+       test_cmp_graph upstream-with-a2.. <<-\EOF
+       *   Merge branch A
+       |\
+       | * A1
+       * | B1
+       |/
+       o A2
+       EOF
+'
+
+test_expect_success 'do not rebase cousins unless asked for' '
+       git checkout -b cousins master &&
+       before="$(git rev-parse --verify HEAD)" &&
+       test_tick &&
+       git rebase -r HEAD^ &&
+       test_cmp_rev HEAD $before &&
+       test_tick &&
+       git rebase --rebase-merges=rebase-cousins HEAD^ &&
+       test_cmp_graph HEAD^.. <<-\EOF
+       *   Merge the topic branch '\''onebranch'\''
+       |\
+       | * D
+       | * G
+       |/
+       o H
+       EOF
+'
+
+test_expect_success 'refs/rewritten/* is worktree-local' '
+       git worktree add wt &&
+       cat >wt/script-from-scratch <<-\EOF &&
+       label xyz
+       exec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :
+       exec git rev-parse --verify refs/rewritten/xyz >b
+       EOF
+
+       test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" &&
+       git -C wt rebase -i HEAD &&
+       test_must_be_empty wt/a &&
+       test_cmp_rev HEAD "$(cat wt/b)"
+'
+
+test_expect_success 'post-rewrite hook and fixups work for merges' '
+       git checkout -b post-rewrite &&
+       test_commit same1 &&
+       git reset --hard HEAD^ &&
+       test_commit same2 &&
+       git merge -m "to fix up" same1 &&
+       echo same old same old >same2.t &&
+       test_tick &&
+       git commit --fixup HEAD same2.t &&
+       fixup="$(git rev-parse HEAD)" &&
+
+       mkdir -p .git/hooks &&
+       test_when_finished "rm .git/hooks/post-rewrite" &&
+       echo "cat >actual" | write_script .git/hooks/post-rewrite &&
+
+       test_tick &&
+       git rebase -i --autosquash -r HEAD^^^ &&
+       printf "%s %s\n%s %s\n%s %s\n%s %s\n" >expect $(git rev-parse \
+               $fixup^^2 HEAD^2 \
+               $fixup^^ HEAD^ \
+               $fixup^ HEAD \
+               $fixup HEAD) &&
+       test_cmp expect actual
+'
+
+test_expect_success 'refuse to merge ancestors of HEAD' '
+       echo "merge HEAD^" >script-from-scratch &&
+       test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" &&
+       before="$(git rev-parse HEAD)" &&
+       git rebase -i HEAD &&
+       test_cmp_rev HEAD $before
+'
+
+test_expect_success 'root commits' '
+       git checkout --orphan unrelated &&
+       (GIT_AUTHOR_NAME="Parsnip" GIT_AUTHOR_EMAIL="root@example.com" \
+        test_commit second-root) &&
+       test_commit third-root &&
+       cat >script-from-scratch <<-\EOF &&
+       pick third-root
+       label first-branch
+       reset [new root]
+       pick second-root
+       merge first-branch # Merge the 3rd root
+       EOF
+       test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
+       test_tick &&
+       git rebase -i --force --root -r &&
+       test "Parsnip" = "$(git show -s --format=%an HEAD^)" &&
+       test $(git rev-parse second-root^0) != $(git rev-parse HEAD^) &&
+       test $(git rev-parse second-root:second-root.t) = \
+               $(git rev-parse HEAD^:second-root.t) &&
+       test_cmp_graph HEAD <<-\EOF &&
+       *   Merge the 3rd root
+       |\
+       | * third-root
+       * second-root
+       EOF
+
+       : fast forward if possible &&
+       before="$(git rev-parse --verify HEAD)" &&
+       test_might_fail git config --unset sequence.editor &&
+       test_tick &&
+       git rebase -i --root -r &&
+       test_cmp_rev HEAD $before
+'
+
+test_expect_success 'a "merge" into a root commit is a fast-forward' '
+       head=$(git rev-parse HEAD) &&
+       cat >script-from-scratch <<-EOF &&
+       reset [new root]
+       merge $head
+       EOF
+       test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
+       test_tick &&
+       git rebase -i -r HEAD^ &&
+       test_cmp_rev HEAD $head
+'
+
+test_expect_success 'A root commit can be a cousin, treat it that way' '
+       git checkout --orphan khnum &&
+       test_commit yama &&
+       git checkout -b asherah master &&
+       test_commit shamkat &&
+       git merge --allow-unrelated-histories khnum &&
+       test_tick &&
+       git rebase -f -r HEAD^ &&
+       ! test_cmp_rev HEAD^2 khnum &&
+       test_cmp_graph HEAD^.. <<-\EOF &&
+       *   Merge branch '\''khnum'\'' into asherah
+       |\
+       | * yama
+       o shamkat
+       EOF
+       test_tick &&
+       git rebase --rebase-merges=rebase-cousins HEAD^ &&
+       test_cmp_graph HEAD^.. <<-\EOF
+       *   Merge branch '\''khnum'\'' into asherah
+       |\
+       | * yama
+       |/
+       o shamkat
+       EOF
+'
+
+test_expect_success 'labels that are object IDs are rewritten' '
+       git checkout -b third B &&
+       test_tick &&
+       test_commit I &&
+       third=$(git rev-parse HEAD) &&
+       git checkout -b labels master &&
+       git merge --no-commit third &&
+       test_tick &&
+       git commit -m "Merge commit '\''$third'\'' into labels" &&
+       cp script-from-scratch-orig script-from-scratch &&
+       test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
+       test_tick &&
+       git rebase -i -r A &&
+       ! grep "^label $third$" .git/ORIGINAL-TODO
+'
+
+test_done
index ccbc11851497a10c74dcdaba1ed7238aa6b409f7..d1c68af8c50e75255baa618ea297549b548c0fc2 100755 (executable)
@@ -141,7 +141,7 @@ test_expect_success 'cherry-pick "-" works with arguments' '
        test_cmp expect actual
 '
 
-test_expect_failure 'cherry-pick works with dirty renamed file' '
+test_expect_success 'cherry-pick works with dirty renamed file' '
        test_commit to-rename &&
        git checkout -b unrelated &&
        test_commit unrelated &&
@@ -150,9 +150,8 @@ test_expect_failure 'cherry-pick works with dirty renamed file' '
        test_tick &&
        git commit -m renamed &&
        echo modified >renamed &&
-       test_must_fail git cherry-pick refs/heads/unrelated >out &&
-       test_i18ngrep "Refusing to lose dirty file at renamed" out &&
-       test $(git rev-parse :0:renamed) = $(git rev-parse HEAD^:to-rename.t) &&
+       git cherry-pick refs/heads/unrelated >out &&
+       test $(git rev-parse :0:renamed) = $(git rev-parse HEAD~2:to-rename.t) &&
        grep -q "^modified$" renamed
 '
 
index 21b4f194a2466b64023017b9711473f15187c638..b42cd66d3a574138148a311b34472506b433c286 100755 (executable)
@@ -122,7 +122,7 @@ test_expect_success '--quit keeps HEAD and conflicted index intact' '
        {
                git rev-list HEAD |
                git diff-tree --root --stdin |
-               sed "s/$_x40/OBJID/g"
+               sed "s/$OID_REGEX/OBJID/g"
        } >actual &&
        test_cmp expect actual
 '
@@ -220,7 +220,7 @@ test_expect_success 'cherry-pick still writes sequencer state when one commit is
        {
                git rev-list HEAD |
                git diff-tree --root --stdin |
-               sed "s/$_x40/OBJID/g"
+               sed "s/$OID_REGEX/OBJID/g"
        } >actual &&
        cat >expect <<-\EOF &&
        OBJID
@@ -317,7 +317,7 @@ test_expect_success '--continue after resolving conflicts' '
        {
                git rev-list HEAD |
                git diff-tree --root --stdin |
-               sed "s/$_x40/OBJID/g"
+               sed "s/$OID_REGEX/OBJID/g"
        } >actual.log &&
        test_cmp expect foo &&
        test_cmp expect.log actual.log
@@ -334,7 +334,7 @@ test_expect_success '--continue after resolving conflicts and committing' '
        {
                git rev-list HEAD |
                git diff-tree --root --stdin |
-               sed "s/$_x40/OBJID/g"
+               sed "s/$OID_REGEX/OBJID/g"
        } >actual &&
        cat >expect <<-\EOF &&
        OBJID
index 3cb74ca296d141a6bd248e527680f5f217608ca7..c6af7f82b58f606b3f6c7e2612931a1fc2af15c7 100755 (executable)
@@ -40,7 +40,6 @@ test_expect_success 'setup' '
 
 cat > expected-patch << EOF
 diff --git a/file b/file
-index b9834b5..9020acb 100644
 --- a/file
 +++ b/file
 @@ -1,11 +1,6 @@
@@ -80,7 +79,6 @@ EOF
 
 cat > expected << EOF
 diff --git a/file b/file
-index b9834b5..ef6e94c 100644
 --- a/file
 +++ b/file
 @@ -1,10 +1,12 @@
@@ -100,7 +98,7 @@ EOF
 
 echo "#!$SHELL_PATH" >fake-editor.sh
 cat >> fake-editor.sh <<\EOF
-mv -f "$1" orig-patch &&
+egrep -v '^index' "$1" >orig-patch &&
 mv -f patch "$1"
 EOF
 
@@ -113,7 +111,8 @@ test_expect_success 'add -e' '
        git add -e &&
        test_cmp second-part file &&
        test_cmp orig-patch expected-patch &&
-       git diff --cached > out &&
+       git diff --cached >actual &&
+       grep -v index actual >out &&
        test_cmp out expected
 
 '
index aefde7b1726bdc8d71d2b78e313e29daa2b31a24..1f871d3cca9aba4dcb32b5197b9b3d296fc5c1fa 100755 (executable)
@@ -726,7 +726,7 @@ test_expect_success 'store updates stash ref and reflog' '
        git reset --hard &&
        ! grep quux bazzy &&
        git stash store -m quuxery $STASH_ID &&
-       test $(cat .git/refs/stash) = $STASH_ID &&
+       test $(git rev-parse stash) = $STASH_ID &&
        git reflog --format=%H stash| grep $STASH_ID &&
        git stash pop &&
        grep quux bazzy
index 3ea5b9bb3ff0a4e439b1fc6cd8f9df86386f4126..597b0637d160be0cab4d3114aace598616e371fd 100755 (executable)
@@ -35,24 +35,26 @@ test_expect_success 'stash save --include-untracked cleaned the untracked files'
        test_cmp expect actual
 '
 
+tracked=$(git rev-parse --short $(echo 1 | git hash-object --stdin))
+untracked=$(git rev-parse --short $(echo untracked | git hash-object --stdin))
 cat > expect.diff <<EOF
 diff --git a/HEAD b/HEAD
 new file mode 100644
-index 0000000..d00491f
+index 0000000..$tracked
 --- /dev/null
 +++ b/HEAD
 @@ -0,0 +1 @@
 +1
 diff --git a/file2 b/file2
 new file mode 100644
-index 0000000..d00491f
+index 0000000..$tracked
 --- /dev/null
 +++ b/file2
 @@ -0,0 +1 @@
 +1
 diff --git a/untracked/untracked b/untracked/untracked
 new file mode 100644
-index 0000000..5a72eb2
+index 0000000..$untracked
 --- /dev/null
 +++ b/untracked/untracked
 @@ -0,0 +1 @@
@@ -109,10 +111,11 @@ test_expect_success 'stash save -u dirty index' '
        git stash -u
 '
 
+blob=$(git rev-parse --short $(echo 4 | git hash-object --stdin))
 cat > expect <<EOF
 diff --git a/file3 b/file3
 new file mode 100644
-index 0000000..b8626c4
+index 0000000..$blob
 --- /dev/null
 +++ b/file3
 @@ -0,0 +1 @@
index a07816d5605f30ccb7f7ce6fe83288fff535538c..bf4030371a9fc8f5abe7e88d84c98a097b9821dd 100755 (executable)
@@ -138,6 +138,18 @@ test_expect_success 'favour same basenames over different ones' '
        test_i18ngrep "renamed: .*path1 -> subdir/path1" out
 '
 
+test_expect_success 'test diff.renames=true for git status' '
+       git -c diff.renames=true status >out &&
+       test_i18ngrep "renamed: .*path1 -> subdir/path1" out
+'
+
+test_expect_success 'test diff.renames=false for git status' '
+       git -c diff.renames=false status >out &&
+       test_i18ngrep ! "renamed: .*path1 -> subdir/path1" out &&
+       test_i18ngrep "new file: .*subdir/path1" out &&
+       test_i18ngrep "deleted: .*[^/]path1" out
+'
+
 test_expect_success 'favour same basenames even with minor differences' '
        git show HEAD:path1 | sed "s/15/16/" > subdir/path1 &&
        git status >out &&
index a5e8b830834f4b5feb531f8f4f4d08462325b1de..3a6c21e8251b84b2979ac7fc5d730a3155b624b4 100755 (executable)
@@ -131,7 +131,7 @@ cmp_diff_files_output () {
     # object ID for the changed files because it wants you to look at the
     # filesystem.
     sed <"$2" >.test-tmp \
-       -e '/^:000000 /d;s/'$_x40'\( [MCRNDU][0-9]*\)   /'$_z40'\1      /' &&
+       -e '/^:000000 /d;s/'$OID_REGEX'\( [MCRNDU][0-9]*\)      /'$ZERO_OID'\1  /' &&
     test_cmp "$1" .test-tmp
 }
 
index 76f643b2c2a2c7e307135de6942cf46743b6cbef..a8e01eccd158131118019ca7c9b05d08a65d07e3 100755 (executable)
@@ -8,7 +8,7 @@ test_description='Test mode change diffs.
 '
 . ./test-lib.sh
 
-sed_script='s/\(:100644 100755\) \('"$_x40"'\) \2 /\1 X X /'
+sed_script='s/\(:100644 100755\) \('"$OID_REGEX"'\) \2 /\1 X X /'
 
 test_expect_success 'setup' '
        echo frotz >rezrov &&
index dae327fabbfe390a3dc707ef2ebc665c9c6e3cb3..b187b7f6c66b8ade9d0ef73aa346a9205df153bc 100755 (executable)
@@ -17,6 +17,7 @@ test_expect_success 'prepare reference tree' '
        echo $tree
 '
 
+blob=$(git hash-object "$TEST_DIRECTORY/diff-lib/COPYING")
 test_expect_success 'prepare work tree' '
        cp path0/COPYING path1/COPYING &&
        git update-index --add --remove path0/COPYING path1/COPYING
@@ -26,8 +27,8 @@ test_expect_success 'prepare work tree' '
 # path1 both have COPYING and the latter is a copy of path0/COPYING.
 # Comparing the full tree with cache should tell us so.
 
-cat >expected <<\EOF
-:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 C100  path0/COPYING   path1/COPYING
+cat >expected <<EOF
+:100644 100644 $blob $blob C100        path0/COPYING   path1/COPYING
 EOF
 
 test_expect_success 'copy detection' '
@@ -46,8 +47,8 @@ test_expect_success 'copy detection, cached' '
 # path1/COPYING suddenly appearing from nowhere, not detected as
 # a copy from path0/COPYING.
 
-cat >expected <<\EOF
-:000000 100644 0000000000000000000000000000000000000000 6ff87c4664981e4397625791c8ea3bbb5f2279a3 A     path1/COPYING
+cat >expected <<EOF
+:000000 100644 $ZERO_OID $blob A       path1/COPYING
 EOF
 
 test_expect_success 'copy, limited to a subtree' '
@@ -64,8 +65,8 @@ test_expect_success 'tweak work tree' '
 # path0/COPYING.  Showing the full tree with cache should tell us about
 # the rename.
 
-cat >expected <<\EOF
-:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 R100  path0/COPYING   path1/COPYING
+cat >expected <<EOF
+:100644 100644 $blob $blob R100        path0/COPYING   path1/COPYING
 EOF
 
 test_expect_success 'rename detection' '
@@ -78,8 +79,8 @@ test_expect_success 'rename detection' '
 # path0/COPYING.  When we say we care only about path1, we should just
 # see path1/COPYING appearing from nowhere.
 
-cat >expected <<\EOF
-:000000 100644 0000000000000000000000000000000000000000 6ff87c4664981e4397625791c8ea3bbb5f2279a3 A     path1/COPYING
+cat >expected <<EOF
+:000000 100644 $ZERO_OID $blob A       path1/COPYING
 EOF
 
 test_expect_success 'rename, limited to a subtree' '
index 9dd1bc5e162bb0b8821337a1e43cd8ad85423507..b1ccd4102e09e27529eb09e873c2b4c2467a40db 100755 (executable)
@@ -27,29 +27,32 @@ Further, with -B and -M together, these should turn into two renames.
 test_expect_success setup '
        cat "$TEST_DIRECTORY"/diff-lib/README >file0 &&
        cat "$TEST_DIRECTORY"/diff-lib/COPYING >file1 &&
+       blob0_id=$(git hash-object file0) &&
+       blob1_id=$(git hash-object file1) &&
        git update-index --add file0 file1 &&
        git tag reference $(git write-tree)
 '
 
 test_expect_success 'change file1 with copy-edit of file0 and remove file0' '
        sed -e "s/git/GIT/" file0 >file1 &&
+       blob2_id=$(git hash-object file1) &&
        rm -f file0 &&
        git update-index --remove file0 file1
 '
 
 test_expect_success 'run diff with -B (#1)' '
        git diff-index -B --cached reference >current &&
-       cat >expect <<-\EOF &&
-       :100644 000000 548142c327a6790ff8821d67c2ee1eff7a656b52 0000000000000000000000000000000000000000 D      file0
-       :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 2fbedd0b5d4b8126e4750c3bee305e8ff79f80ec M100   file1
+       cat >expect <<-EOF &&
+       :100644 000000 $blob0_id $ZERO_OID D    file0
+       :100644 100644 $blob1_id $blob2_id M100 file1
        EOF
        compare_diff_raw expect current
 '
 
 test_expect_success 'run diff with -B and -M (#2)' '
        git diff-index -B -M reference >current &&
-       cat >expect <<-\EOF &&
-       :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 2fbedd0b5d4b8126e4750c3bee305e8ff79f80ec R100   file0   file1
+       cat >expect <<-EOF &&
+       :100644 100644 $blob0_id $blob2_id R100 file0   file1
        EOF
        compare_diff_raw expect current
 '
@@ -66,18 +69,18 @@ test_expect_success 'swap file0 and file1' '
 
 test_expect_success 'run diff with -B (#3)' '
        git diff-index -B reference >current &&
-       cat >expect <<-\EOF &&
-       :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 6ff87c4664981e4397625791c8ea3bbb5f2279a3 M100   file0
-       :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 548142c327a6790ff8821d67c2ee1eff7a656b52 M100   file1
+       cat >expect <<-EOF &&
+       :100644 100644 $blob0_id $blob1_id M100 file0
+       :100644 100644 $blob1_id $blob0_id M100 file1
        EOF
        compare_diff_raw expect current
 '
 
 test_expect_success 'run diff with -B and -M (#4)' '
        git diff-index -B -M reference >current &&
-       cat >expect <<-\EOF &&
-       :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 R100   file1   file0
-       :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 548142c327a6790ff8821d67c2ee1eff7a656b52 R100   file0   file1
+       cat >expect <<-EOF &&
+       :100644 100644 $blob1_id $blob1_id R100 file1   file0
+       :100644 100644 $blob0_id $blob0_id R100 file0   file1
        EOF
        compare_diff_raw expect current
 '
@@ -85,14 +88,15 @@ test_expect_success 'run diff with -B and -M (#4)' '
 test_expect_success 'make file0 into something completely different' '
        rm -f file0 &&
        test_ln_s_add frotz file0 &&
+       slink_id=$(printf frotz | git hash-object --stdin) &&
        git update-index file1
 '
 
 test_expect_success 'run diff with -B (#5)' '
        git diff-index -B reference >current &&
-       cat >expect <<-\EOF &&
-       :100644 120000 548142c327a6790ff8821d67c2ee1eff7a656b52 67be421f88824578857624f7b3dc75e99a8a1481 T      file0
-       :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 548142c327a6790ff8821d67c2ee1eff7a656b52 M100   file1
+       cat >expect <<-EOF &&
+       :100644 120000 $blob0_id $slink_id T    file0
+       :100644 100644 $blob1_id $blob0_id M100 file1
        EOF
        compare_diff_raw expect current
 '
@@ -103,9 +107,9 @@ test_expect_success 'run diff with -B -M (#6)' '
        # file0 changed from regular to symlink.  file1 is the same as the preimage
        # of file0.  Because the change does not make file0 disappear, file1 is
        # denoted as a copy of file0
-       cat >expect <<-\EOF &&
-       :100644 120000 548142c327a6790ff8821d67c2ee1eff7a656b52 67be421f88824578857624f7b3dc75e99a8a1481 T      file0
-       :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 548142c327a6790ff8821d67c2ee1eff7a656b52 C      file0   file1
+       cat >expect <<-EOF &&
+       :100644 120000 $blob0_id $slink_id T    file0
+       :100644 100644 $blob0_id $blob0_id C    file0   file1
        EOF
        compare_diff_raw expect current
 '
@@ -115,9 +119,9 @@ test_expect_success 'run diff with -M (#7)' '
 
        # This should not mistake file0 as the copy source of new file1
        # due to type differences.
-       cat >expect <<-\EOF &&
-       :100644 120000 548142c327a6790ff8821d67c2ee1eff7a656b52 67be421f88824578857624f7b3dc75e99a8a1481 T      file0
-       :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 548142c327a6790ff8821d67c2ee1eff7a656b52 M      file1
+       cat >expect <<-EOF &&
+       :100644 120000 $blob0_id $slink_id T    file0
+       :100644 100644 $blob1_id $blob0_id M    file1
        EOF
        compare_diff_raw expect current
 '
@@ -128,25 +132,26 @@ test_expect_success 'file1 edited to look like file0 and file0 rename-edited to
        git checkout-index -f -u -a &&
        sed -e "s/git/GIT/" file0 >file1 &&
        sed -e "s/git/GET/" file0 >file2 &&
+       blob3_id=$(git hash-object file2) &&
        rm -f file0 &&
        git update-index --add --remove file0 file1 file2
 '
 
 test_expect_success 'run diff with -B (#8)' '
        git diff-index -B reference >current &&
-       cat >expect <<-\EOF &&
-       :100644 000000 548142c327a6790ff8821d67c2ee1eff7a656b52 0000000000000000000000000000000000000000 D      file0
-       :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 2fbedd0b5d4b8126e4750c3bee305e8ff79f80ec M100   file1
-       :000000 100644 0000000000000000000000000000000000000000 69a939f651686f56322566e2fd76715947a24162 A      file2
+       cat >expect <<-EOF &&
+       :100644 000000 $blob0_id $ZERO_OID D    file0
+       :100644 100644 $blob1_id $blob2_id M100 file1
+       :000000 100644 $ZERO_OID $blob3_id A    file2
        EOF
        compare_diff_raw expect current
 '
 
 test_expect_success 'run diff with -B -C (#9)' '
        git diff-index -B -C reference >current &&
-       cat >expect <<-\EOF &&
-       :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 2fbedd0b5d4b8126e4750c3bee305e8ff79f80ec C095   file0   file1
-       :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 69a939f651686f56322566e2fd76715947a24162 R095   file0   file2
+       cat >expect <<-EOF &&
+       :100644 100644 $blob0_id $blob2_id C095 file0   file1
+       :100644 100644 $blob0_id $blob3_id R095 file0   file2
        EOF
        compare_diff_raw expect current
 '
index 482112ca339f05fc31e6f9c2c6168971a1121e87..028d5507a6b684208ba82292379ec74bc6628574 100755 (executable)
@@ -578,7 +578,11 @@ test_expect_success 'excessive subject' '
 
        rm -rf patches/ &&
        git checkout side &&
+       before=$(git hash-object file) &&
+       before=$(git rev-parse --short $before) &&
        for i in 5 6 1 2 3 A 4 B C 7 8 9 10 D E F; do echo "$i"; done >>file &&
+       after=$(git hash-object file) &&
+       after=$(git rev-parse --short $after) &&
        git update-index file &&
        git commit -m "This is an excessively long subject line for a message due to the habit some projects have of not having a short, one-line subject at the start of the commit message, but rather sticking a whole paragraph right at the start as the only thing in the commit message. It had better not become the filename for the patch." &&
        git format-patch -o patches/ master..side &&
@@ -586,7 +590,6 @@ test_expect_success 'excessive subject' '
 '
 
 test_expect_success 'cover-letter inherits diff options' '
-
        git mv file foo &&
        git commit -m foo &&
        git format-patch --no-renames --cover-letter -1 &&
@@ -616,7 +619,7 @@ test_expect_success 'shortlog of cover-letter wraps overly-long onelines' '
 '
 
 cat > expect << EOF
-index 40f36c6..2dc5c23 100644
+index $before..$after 100644
 --- a/file
 +++ b/file
 @@ -13,4 +13,20 @@ C
@@ -640,7 +643,7 @@ test_expect_success 'format-patch respects -U' '
 cat > expect << EOF
 
 diff --git a/file b/file
-index 40f36c6..2dc5c23 100644
+index $before..$after 100644
 --- a/file
 +++ b/file
 @@ -14,3 +14,19 @@ C
@@ -1523,14 +1526,14 @@ test_expect_success 'cover letter auto user override' '
 test_expect_success 'format-patch --zero-commit' '
        git format-patch --zero-commit --stdout v2..v1 >patch2 &&
        grep "^From " patch2 | sort | uniq >actual &&
-       echo "From $_z40 Mon Sep 17 00:00:00 2001" >expect &&
+       echo "From $ZERO_OID Mon Sep 17 00:00:00 2001" >expect &&
        test_cmp expect actual
 '
 
 test_expect_success 'From line has expected format' '
        git format-patch --stdout v2..v1 >patch2 &&
        grep "^From " patch2 >from &&
-       grep "^From $_x40 Mon Sep 17 00:00:00 2001$" patch2 >filtered &&
+       grep "^From $OID_REGEX Mon Sep 17 00:00:00 2001$" patch2 >filtered &&
        test_cmp from filtered
 '
 
@@ -1661,6 +1664,15 @@ test_expect_success 'format-patch --base with --attach' '
        test_write_lines 1 2 >expect &&
        test_cmp expect actual
 '
+test_expect_success 'format-patch --attach cover-letter only is non-multipart' '
+       test_when_finished "rm -fr patches" &&
+       git format-patch -o patches --cover-letter --attach=mimemime --base=HEAD~ -1 &&
+       ! egrep "^--+mimemime" patches/0000*.patch &&
+       egrep "^--+mimemime$" patches/0001*.patch >output &&
+       test_line_count = 2 output &&
+       egrep "^--+mimemime--$" patches/0001*.patch >output &&
+       test_line_count = 1 output
+'
 
 test_expect_success 'format-patch --pretty=mboxrd' '
        sp=" " &&
index 044620186d135fe5caeb9376665305af1c8acbe5..e009826fcbe5a893df748a47d162e010b95d758b 100755 (executable)
@@ -13,6 +13,8 @@ test_expect_success setup '
 
        test_tick &&
        echo second >file &&
+       before=$(git hash-object file) &&
+       before=$(git rev-parse --short $before) &&
        git add file &&
        git commit -m second &&
 
@@ -26,7 +28,7 @@ test_expect_success 'GIT_EXTERNAL_DIFF environment' '
                read path oldfile oldhex oldmode newfile newhex newmode &&
                test "z$path" = zfile &&
                test "z$oldmode" = z100644 &&
-               test "z$newhex" = "z$_z40" &&
+               test "z$newhex" = "z$ZERO_OID" &&
                test "z$newmode" = z100644 &&
                oh=$(git rev-parse --verify HEAD:file) &&
                test "z$oh" = "z$oldhex"
@@ -55,7 +57,7 @@ test_expect_success SYMLINKS 'typechange diff' '
                read path oldfile oldhex oldmode newfile newhex newmode &&
                test "z$path" = zfile &&
                test "z$oldmode" = z100644 &&
-               test "z$newhex" = "z$_z40" &&
+               test "z$newhex" = "z$ZERO_OID" &&
                test "z$newmode" = z120000 &&
                oh=$(git rev-parse --verify HEAD:file) &&
                test "z$oh" = "z$oldhex"
@@ -73,7 +75,7 @@ test_expect_success 'diff.external' '
                read path oldfile oldhex oldmode newfile newhex newmode &&
                test "z$path" = zfile &&
                test "z$oldmode" = z100644 &&
-               test "z$newhex" = "z$_z40" &&
+               test "z$newhex" = "z$ZERO_OID" &&
                test "z$newmode" = z100644 &&
                oh=$(git rev-parse --verify HEAD:file) &&
                test "z$oh" = "z$oldhex"
@@ -104,7 +106,7 @@ test_expect_success 'diff attribute' '
                read path oldfile oldhex oldmode newfile newhex newmode &&
                test "z$path" = zfile &&
                test "z$oldmode" = z100644 &&
-               test "z$newhex" = "z$_z40" &&
+               test "z$newhex" = "z$ZERO_OID" &&
                test "z$newmode" = z100644 &&
                oh=$(git rev-parse --verify HEAD:file) &&
                test "z$oh" = "z$oldhex"
@@ -137,7 +139,7 @@ test_expect_success 'diff attribute' '
                read path oldfile oldhex oldmode newfile newhex newmode &&
                test "z$path" = zfile &&
                test "z$oldmode" = z100644 &&
-               test "z$newhex" = "z$_z40" &&
+               test "z$newhex" = "z$ZERO_OID" &&
                test "z$newmode" = z100644 &&
                oh=$(git rev-parse --verify HEAD:file) &&
                test "z$oh" = "z$oldhex"
@@ -180,9 +182,13 @@ test_expect_success 'no diff with -diff' '
 echo NULZbetweenZwords | perl -pe 'y/Z/\000/' > file
 
 test_expect_success 'force diff with "diff"' '
+       after=$(git hash-object file) &&
+       after=$(git rev-parse --short $after) &&
        echo >.gitattributes "file diff" &&
        git diff >actual &&
-       test_cmp "$TEST_DIRECTORY"/t4020/diff.NUL actual
+       sed -e "s/^index .*/index $before..$after 100644/" \
+               "$TEST_DIRECTORY"/t4020/diff.NUL >expected-diff &&
+       test_cmp expected-diff actual
 '
 
 test_expect_success 'GIT_EXTERNAL_DIFF with more than one changed files' '
@@ -237,7 +243,7 @@ test_expect_success 'diff --cached' '
        git update-index --assume-unchanged file &&
        echo second >file &&
        git diff --cached >actual &&
-       test_cmp "$TEST_DIRECTORY"/t4020/diff.NUL actual
+       test_cmp expected-diff actual
 '
 
 test_expect_success 'clean up crlf leftovers' '
index cb51d9f9d47d61c5b73de78edeeedcc7652f262d..6d1c3d949c78bc8fd5a82dbaee519163a8653e86 100755 (executable)
@@ -13,6 +13,8 @@ test_expect_success setup '
          "nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM" \
          <"$TEST_DIRECTORY"/../COPYING >test &&
        echo "to be deleted" >test2 &&
+       blob=$(git hash-object test2) &&
+       blob=$(git rev-parse --short $blob) &&
        git add test2
 
 '
@@ -27,7 +29,7 @@ test_expect_success 'detect rewrite' '
 cat >expect <<EOF
 diff --git a/test2 b/test2
 deleted file mode 100644
-index 4202011..0000000
+index $blob..0000000
 --- a/test2
 +++ /dev/null
 @@ -1 +0,0 @@
@@ -43,7 +45,7 @@ test_expect_success 'show deletion diff without -D' '
 cat >expect <<EOF
 diff --git a/test2 b/test2
 deleted file mode 100644
-index 4202011..0000000
+index $blob..0000000
 EOF
 test_expect_success 'suppress deletion diff with -D' '
 
index 2ffd11a142d8c1d2f532f662951a27a90119a76e..6304130ad4737bfac1e2cf06fd8c600bb7b42f76 100755 (executable)
@@ -31,7 +31,7 @@ test_expect_success setup '
                cd sub &&
                git rev-list HEAD
        ) &&
-       echo ":160000 160000 $3 $_z40 M sub" >expect &&
+       echo ":160000 160000 $3 $ZERO_OID M     sub" >expect &&
        subtip=$3 subprev=$2
 '
 
@@ -250,7 +250,7 @@ test_expect_success 'conflicted submodule setup' '
        # 39 efs
        c=fffffffffffffffffffffffffffffffffffffff &&
        (
-               echo "000000 $_z40 0    sub" &&
+               echo "000000 $ZERO_OID 0        sub" &&
                echo "160000 1$c 1      sub" &&
                echo "160000 2$c 2      sub" &&
                echo "160000 3$c 3      sub"
@@ -265,7 +265,7 @@ index 2ffffff,3ffffff..0000000
 ++Subproject commit 0000000000000000000000000000000000000000'\'' &&
 
        hh=$(git rev-parse HEAD) &&
-       sed -e "s/$_z40/$hh/" expect.nosub >expect.withsub
+       sed -e "s/$ZERO_OID/$hh/" expect.nosub >expect.withsub
 
 '
 
index 3ccc237a8d4443bfc8763fbb9cb51033f846b0e8..32b6e9a4e76217d8de771b405dd3ce57c73643ad 100755 (executable)
@@ -6,7 +6,7 @@ test_description='diff honors config option, diff.suppressBlankEmpty'
 
 . ./test-lib.sh
 
-cat <<\EOF > exp ||
+cat <<\EOF >expected ||
 diff --git a/f b/f
 index 5f6a263..8cb8bae 100644
 --- a/f
@@ -18,22 +18,26 @@ index 5f6a263..8cb8bae 100644
 EOF
 exit 1
 
-test_expect_success \
-    "$test_description" \
-    'printf "\nx\n" > f &&
-     git add f &&
-     git commit -q -m. f &&
-     printf "\ny\n" > f &&
-     git config --bool diff.suppressBlankEmpty true &&
-     git diff f > actual &&
-     test_cmp exp actual &&
-     perl -i.bak -p -e "s/^\$/ /" exp &&
-     git config --bool diff.suppressBlankEmpty false &&
-     git diff f > actual &&
-     test_cmp exp actual &&
-     git config --bool --unset diff.suppressBlankEmpty &&
-     git diff f > actual &&
-     test_cmp exp actual
-     '
+test_expect_success "$test_description" '
+       printf "\nx\n" > f &&
+       before=$(git hash-object f) &&
+       before=$(git rev-parse --short $before) &&
+       git add f &&
+       git commit -q -m. f &&
+       printf "\ny\n" > f &&
+       after=$(git hash-object f) &&
+       after=$(git rev-parse --short $after) &&
+       sed -e "s/^index .*/index $before..$after 100644/" expected >exp &&
+       git config --bool diff.suppressBlankEmpty true &&
+       git diff f > actual &&
+       test_cmp exp actual &&
+       perl -i.bak -p -e "s/^\$/ /" exp &&
+       git config --bool diff.suppressBlankEmpty false &&
+       git diff f > actual &&
+       test_cmp exp actual &&
+       git config --bool --unset diff.suppressBlankEmpty &&
+       git diff f > actual &&
+       test_cmp exp actual
+'
 
 test_done
index aad6c7f78db34703b2f9b1ed72239cd3b4005016..4cb9f0e523d2218566554cdccd9c2e3929f3d05b 100755 (executable)
@@ -148,7 +148,8 @@ test_expect_success 'diffstat does not run textconv' '
 # restore working setup
 echo file diff=foo >.gitattributes
 
-cat >expect.typechange <<'EOF'
+symlink=$(git rev-parse --short $(printf frotz | git hash-object --stdin))
+cat >expect.typechange <<EOF
 --- a/file
 +++ /dev/null
 @@ -1,2 +0,0 @@
@@ -156,7 +157,7 @@ cat >expect.typechange <<'EOF'
 -1
 diff --git a/file b/file
 new file mode 120000
-index 0000000..67be421
+index 0000000..$symlink
 --- /dev/null
 +++ b/file
 @@ -0,0 +1 @@
index 04a44d5c61495c4c87db6ada952f5652b8e17642..bf33aedf4b22868fd820330a460965d27e66cb91 100755 (executable)
@@ -15,9 +15,13 @@ test_expect_success 'setup' '
        echo bar content 1 >bar.bin &&
        git add . &&
        git commit -m one &&
+       foo1=$(git rev-parse --short HEAD:foo.bin) &&
+       bar1=$(git rev-parse --short HEAD:bar.bin) &&
        echo foo content 2 >foo.bin &&
        echo bar content 2 >bar.bin &&
        git commit -a -m two &&
+       foo2=$(git rev-parse --short HEAD:foo.bin) &&
+       bar2=$(git rev-parse --short HEAD:bar.bin) &&
        echo "*.bin diff=magic" >.gitattributes &&
        git config diff.magic.textconv ./helper &&
        git config diff.magic.cachetextconv true
@@ -25,14 +29,14 @@ test_expect_success 'setup' '
 
 cat >expect <<EOF
 diff --git a/bar.bin b/bar.bin
-index fcf9166..28283d5 100644
+index $bar1..$bar2 100644
 --- a/bar.bin
 +++ b/bar.bin
 @@ -1 +1 @@
 -converted: bar content 1
 +converted: bar content 2
 diff --git a/foo.bin b/foo.bin
-index d5b9fe3..1345db2 100644
+index $foo1..$foo2 100644
 --- a/foo.bin
 +++ b/foo.bin
 @@ -1 +1 @@
@@ -59,7 +63,7 @@ test_expect_success 'cached textconv does not run helper' '
 
 cat >expect <<EOF
 diff --git a/bar.bin b/bar.bin
-index fcf9166..28283d5 100644
+index $bar1..$bar2 100644
 --- a/bar.bin
 +++ b/bar.bin
 @@ -1,2 +1,2 @@
@@ -67,7 +71,7 @@ index fcf9166..28283d5 100644
 -converted: bar content 1
 +converted: bar content 2
 diff --git a/foo.bin b/foo.bin
-index d5b9fe3..1345db2 100644
+index $foo1..$foo2 100644
 --- a/foo.bin
 +++ b/foo.bin
 @@ -1,2 +1,2 @@
@@ -84,7 +88,7 @@ test_expect_success 'changing textconv invalidates cache' '
 
 cat >expect <<EOF
 diff --git a/bar.bin b/bar.bin
-index fcf9166..28283d5 100644
+index $bar1..$bar2 100644
 --- a/bar.bin
 +++ b/bar.bin
 @@ -1,2 +1,2 @@
@@ -92,7 +96,7 @@ index fcf9166..28283d5 100644
 -converted: bar content 1
 +converted: bar content 2
 diff --git a/foo.bin b/foo.bin
-index d5b9fe3..1345db2 100644
+index $foo1..$foo2 100644
 --- a/foo.bin
 +++ b/foo.bin
 @@ -1 +1 @@
index d5ce72be63a5515d270bd407da47fee9048d1d1b..647905e01fb963efce34cd30b351a31a671cbb5e 100755 (executable)
@@ -3,6 +3,12 @@
 test_description='test unique sha1 abbreviation on "index from..to" line'
 . ./test-lib.sh
 
+if ! test_have_prereq SHA1
+then
+       skip_all='not using SHA-1 for objects'
+       test_done
+fi
+
 cat >expect_initial <<EOF
 100644 blob 51d2738463ea4ca66f8691c91e33ce64b7d41bb1   foo
 EOF
index 6471a68701dd4bc140d43ba91ebb899036ae0d4d..36f8ed8a818714abc234ca940a59693f4be21046 100755 (executable)
@@ -8,6 +8,7 @@ test_expect_success 'setup' '
        echo content >file1 &&
        mkdir subdir &&
        echo other content >subdir/file2 &&
+       blob=$(git hash-object subdir/file2) &&
        git add . &&
        git commit -m one
 '
@@ -17,10 +18,11 @@ check_diff () {
        shift
        expect=$1
        shift
+       short_blob=$(git rev-parse --short $blob)
        cat >expected <<-EOF
        diff --git a/$expect b/$expect
        new file mode 100644
-       index 0000000..25c05ef
+       index 0000000..$short_blob
        --- /dev/null
        +++ b/$expect
        @@ -0,0 +1 @@
@@ -68,7 +70,7 @@ check_raw () {
        expect=$1
        shift
        cat >expected <<-EOF
-       :000000 100644 0000000000000000000000000000000000000000 25c05ef3639d2d270e7fe765a67668f098092bc5 A      $expect
+       :000000 100644 0000000000000000000000000000000000000000 $blob A $expect
        EOF
        test_expect_success "--raw $*" "
                git -C '$dir' diff --no-abbrev --raw $* HEAD^ >actual &&
index d0f14475ca99b596fbe4addb4f8c543d07944ea5..ff7cfd884a44ebe0844c7ac6a21f25ed8374e7d4 100755 (executable)
@@ -37,7 +37,7 @@ test_expect_success 'diff-files -0' '
        for path in $paths
        do
                >"$path" &&
-               echo ":000000 100644 $_z40 $_z40 U      $path"
+               echo ":000000 100644 $ZERO_OID $ZERO_OID U      $path"
        done >diff-files-0.expect &&
        git diff-files -0 >diff-files-0.actual &&
        test_cmp diff-files-0.expect diff-files-0.actual
@@ -47,9 +47,9 @@ test_expect_success 'diff-files -1' '
        for path in $paths
        do
                >"$path" &&
-               echo ":000000 100644 $_z40 $_z40 U      $path" &&
+               echo ":000000 100644 $ZERO_OID $ZERO_OID U      $path" &&
                case "$path" in
-               x??) echo ":100644 100644 $blob1 $_z40 M        $path"
+               x??) echo ":100644 100644 $blob1 $ZERO_OID M    $path"
                esac
        done >diff-files-1.expect &&
        git diff-files -1 >diff-files-1.actual &&
@@ -60,9 +60,9 @@ test_expect_success 'diff-files -2' '
        for path in $paths
        do
                >"$path" &&
-               echo ":000000 100644 $_z40 $_z40 U      $path" &&
+               echo ":000000 100644 $ZERO_OID $ZERO_OID U      $path" &&
                case "$path" in
-               ?x?) echo ":100644 100644 $blob2 $_z40 M        $path"
+               ?x?) echo ":100644 100644 $blob2 $ZERO_OID M    $path"
                esac
        done >diff-files-2.expect &&
        git diff-files -2 >diff-files-2.actual &&
@@ -75,9 +75,9 @@ test_expect_success 'diff-files -3' '
        for path in $paths
        do
                >"$path" &&
-               echo ":000000 100644 $_z40 $_z40 U      $path" &&
+               echo ":000000 100644 $ZERO_OID $ZERO_OID U      $path" &&
                case "$path" in
-               ??x) echo ":100644 100644 $blob3 $_z40 M        $path"
+               ??x) echo ":100644 100644 $blob3 $ZERO_OID M    $path"
                esac
        done >diff-files-3.expect &&
        git diff-files -3 >diff-files-3.actual &&
index 18f42c5fff9a6e3ccb56f9e3122c6c0469ad81a9..fcae82fffa7f74a65fc6b475cc5c3eeefda3029a 100755 (executable)
@@ -19,37 +19,37 @@ test_expect_success 'create tree with matching file' '
 '
 
 test_expect_success 'raw diff shows null sha1 (addition)' '
-       echo ":000000 100644 $_z40 $_z40 A      foo" >expect &&
+       echo ":000000 100644 $ZERO_OID $ZERO_OID A      foo" >expect &&
        git diff-tree $EMPTY_TREE $bogus_tree >actual &&
        test_cmp expect actual
 '
 
 test_expect_success 'raw diff shows null sha1 (removal)' '
-       echo ":100644 000000 $_z40 $_z40 D      foo" >expect &&
+       echo ":100644 000000 $ZERO_OID $ZERO_OID D      foo" >expect &&
        git diff-tree $bogus_tree $EMPTY_TREE >actual &&
        test_cmp expect actual
 '
 
 test_expect_success 'raw diff shows null sha1 (modification)' '
-       echo ":100644 100644 $blob $_z40 M      foo" >expect &&
+       echo ":100644 100644 $blob $ZERO_OID M  foo" >expect &&
        git diff-tree $good_tree $bogus_tree >actual &&
        test_cmp expect actual
 '
 
 test_expect_success 'raw diff shows null sha1 (other direction)' '
-       echo ":100644 100644 $_z40 $blob M      foo" >expect &&
+       echo ":100644 100644 $ZERO_OID $blob M  foo" >expect &&
        git diff-tree $bogus_tree $good_tree >actual &&
        test_cmp expect actual
 '
 
 test_expect_success 'raw diff shows null sha1 (reverse)' '
-       echo ":100644 100644 $_z40 $blob M      foo" >expect &&
+       echo ":100644 100644 $ZERO_OID $blob M  foo" >expect &&
        git diff-tree -R $good_tree $bogus_tree >actual &&
        test_cmp expect actual
 '
 
 test_expect_success 'raw diff shows null sha1 (index)' '
-       echo ":100644 100644 $_z40 $blob M      foo" >expect &&
+       echo ":100644 100644 $ZERO_OID $blob M  foo" >expect &&
        git diff-index $bogus_tree >actual &&
        test_cmp expect actual
 '
index 0a23242cb6aab7903aac9b767b0c4913208c044b..c24ee175ef0e9c5d1abf2c3e931a8df76c8cd4b4 100755 (executable)
@@ -59,12 +59,12 @@ test_expect_success 'create trees with duplicate entries' '
 
 test_expect_success 'diff-tree between trees' '
        {
-               printf ":000000 100644 $_z40 $blob_two A\touter/inner\n" &&
-               printf ":000000 100644 $_z40 $blob_two A\touter/inner\n" &&
-               printf ":000000 100644 $_z40 $blob_two A\touter/inner\n" &&
-               printf ":100644 000000 $blob_two $_z40 D\touter/inner\n" &&
-               printf ":100644 000000 $blob_two $_z40 D\touter/inner\n" &&
-               printf ":100644 000000 $blob_two $_z40 D\touter/inner\n"
+               printf ":000000 100644 $ZERO_OID $blob_two A\touter/inner\n" &&
+               printf ":000000 100644 $ZERO_OID $blob_two A\touter/inner\n" &&
+               printf ":000000 100644 $ZERO_OID $blob_two A\touter/inner\n" &&
+               printf ":100644 000000 $blob_two $ZERO_OID D\touter/inner\n" &&
+               printf ":100644 000000 $blob_two $ZERO_OID D\touter/inner\n" &&
+               printf ":100644 000000 $blob_two $ZERO_OID D\touter/inner\n"
        } >expect &&
        git diff-tree -r --no-abbrev one two >actual &&
        test_cmp expect actual
index 1eccfb71d0c8e26f88708459f065dea622baf9d3..1ebc587f8fc0010713f52196206462fa915cbca5 100755 (executable)
@@ -140,8 +140,8 @@ test_expect_success setup '
                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" &&
+               echo "# Node ID $ZERO_OID" &&
+               echo "# Parent  $ZERO_OID" &&
                cat msg &&
                echo &&
                git diff-tree --no-commit-id -p second
index eaf18c81cbffe012663fae3a537ef8302d6c7b31..8417e5a4b105a54a8de23af8bae2bdaf28c257cb 100755 (executable)
@@ -243,7 +243,7 @@ rerere_gc_custom_expiry_test () {
        five_days="$1" right_now="$2"
        test_expect_success "rerere gc with custom expiry ($five_days, $right_now)" '
                rm -fr .git/rr-cache &&
-               rr=.git/rr-cache/$_z40 &&
+               rr=.git/rr-cache/$ZERO_OID &&
                mkdir -p "$rr" &&
                >"$rr/preimage" &&
                >"$rr/postimage" &&
index ff6649ed9a70721523da3c55142a9622b152243a..58c27736762dde372408a4dacfb030f0e1a55b99 100755 (executable)
@@ -59,7 +59,7 @@ test_expect_success 'setup' '
 fuzz() {
        file=$1 &&
        sed "
-                       s/$_x40/OBJECT_NAME/g
+                       s/$OID_REGEX/OBJECT_NAME/g
                        s/$_x35/OBJID/g
                        s/^ \{6\}[CTa].*/      SUBJECT/g
                        s/^ \{8\}[^ ].*/        CONTINUATION/g
index 591f35daaf5825cfdcf8e7fccf4f52462bbf8f78..2052cadb1109d3644b0c3f82b707f8e8bb35f945 100755 (executable)
@@ -516,22 +516,22 @@ test_expect_success 'log decoration properly follows tag chain' '
        git commit --amend -m shorter &&
        git log --no-walk --tags --pretty="%H %d" --decorate=full >actual &&
        cat <<-EOF >expected &&
-       $head1  (tag: refs/tags/tag2)
        $head2  (tag: refs/tags/message-one)
        $old_head1  (tag: refs/tags/message-two)
+       $head1  (tag: refs/tags/tag2)
        EOF
-       sort actual >actual1 &&
+       sort -k3 actual >actual1 &&
        test_cmp expected actual1
 '
 
 test_expect_success 'clean log decoration' '
        git log --no-walk --tags --pretty="%H %D" --decorate=full >actual &&
        cat >expected <<-EOF &&
-       $head1 tag: refs/tags/tag2
        $head2 tag: refs/tags/message-one
        $old_head1 tag: refs/tags/message-two
+       $head1 tag: refs/tags/tag2
        EOF
-       sort actual >actual1 &&
+       sort -k3 actual >actual1 &&
        test_cmp expected actual1
 '
 
index a1705f70cffe2332fc65f670797539986f330f70..62f335b2d9fe7268f8f6cd878067de8528b5cb75 100755 (executable)
@@ -45,8 +45,9 @@ test_expect_success 'git log -- :' '
 '
 
 test_expect_success 'git log HEAD -- :/' '
+       initial=$(git rev-parse --short HEAD^) &&
        cat >expected <<-EOF &&
-       24b24cf initial
+       $initial initial
        EOF
        (cd sub && git log --oneline HEAD -- :/ >../actual) &&
        test_cmp expected actual
index 08c210f03586ab8cd26d69cb0761180ef5588f58..fca001eb9bb5767b1aff6439dbc98e49ed3aaef8 100755 (executable)
@@ -81,7 +81,7 @@ test_expect_success 'setup: two scripts for reading pull requests' '
        cat <<-EOT >fuzz.sed
        #!/bin/sed -nf
        s/$downstream_url_for_sed/URL/g
-       s/$_x40/OBJECT_NAME/g
+       s/$OID_REGEX/OBJECT_NAME/g
        s/A U Thor/AUTHOR/g
        s/[-0-9]\{10\} [:0-9]\{8\} [-+][0-9]\{4\}/DATE/g
        s/        [^ ].*/        SUBJECT/g
index 65ff60f2ee9b4af61f3f2d7d0f889fc4529e2b60..87a590c4a952d743c2d9f8b0daac76d448f51e8e 100755 (executable)
@@ -457,6 +457,11 @@ test_expect_success !PTHREADS,C_LOCALE_OUTPUT 'pack-objects --threads=N or pack.
        grep -F "no threads support, ignoring pack.threads" err
 '
 
+test_expect_success 'pack-objects in too-many-packs mode' '
+       GIT_TEST_FULL_IN_PACK_ARRAY=1 git repack -ad &&
+       git fsck
+'
+
 #
 # WARNING!
 #
@@ -466,9 +471,11 @@ test_expect_success !PTHREADS,C_LOCALE_OUTPUT 'pack-objects --threads=N or pack.
 
 test_expect_success \
     'fake a SHA1 hash collision' \
-    'test -f   .git/objects/c8/2de19312b6c3695c0c18f70709a6c535682a67 &&
-     cp -f     .git/objects/9d/235ed07cd19811a6ceb342de82f190e49c9f68 \
-               .git/objects/c8/2de19312b6c3695c0c18f70709a6c535682a67'
+    'long_a=$(git hash-object a | sed -e "s!^..!&/!") &&
+     long_b=$(git hash-object b | sed -e "s!^..!&/!") &&
+     test -f   .git/objects/$long_b &&
+     cp -f     .git/objects/$long_a \
+               .git/objects/$long_b'
 
 test_expect_success \
     'make sure index-pack detects the SHA1 collision' \
index 156ae9e9d335029e63430a6cc7d0ec2fd0d68dd3..6845c1f3c3a3acc4540e1340627240d90d7bb145 100755 (executable)
@@ -4,6 +4,12 @@ test_description='handling of duplicate objects in incoming packfiles'
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-pack.sh
 
+if ! test_have_prereq SHA1
+then
+       skip_all='not using SHA-1 for objects'
+       test_done
+fi
+
 # The sha1s we have in our pack. It's important that these have the same
 # starting byte, so that they end up in the same fanout section of the index.
 # That lets us make sure we are exercising the binary search with both sets.
index 3e7861b075ffa54f0214aaa349df33be7a113d35..491556dad9792619974851ef255198c5d247af2f 100755 (executable)
@@ -4,6 +4,12 @@ test_description='test index-pack handling of delta cycles in packfiles'
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-pack.sh
 
+if ! test_have_prereq SHA1
+then
+       skip_all='not using SHA-1 for objects'
+       test_done
+fi
+
 # Two similar-ish objects that we have computed deltas between.
 A=01d7713666f4de822776c7622c10f1b07de280dc
 B=e68fe8129b546b101aee9510c5328e7f21ca1d18
index f6d600fd82fd499a80f67f0a4ff1a60b4a1db1b5..423c0a475f7e87b4c32abf60ef85ccbb898eca11 100755 (executable)
@@ -264,9 +264,9 @@ test_expect_success 'pack with missing parent' '
 '
 
 test_expect_success JGIT 'we can read jgit bitmaps' '
-       git clone . compat-jgit &&
+       git clone --bare . compat-jgit.git &&
        (
-               cd compat-jgit &&
+               cd compat-jgit.git &&
                rm -f .git/objects/pack/*.bitmap &&
                jgit gc &&
                git rev-list --test-bitmap HEAD
@@ -274,9 +274,9 @@ test_expect_success JGIT 'we can read jgit bitmaps' '
 '
 
 test_expect_success JGIT 'jgit can read our bitmaps' '
-       git clone . compat-us &&
+       git clone --bare . compat-us.git &&
        (
-               cd compat-us &&
+               cd compat-us.git &&
                git repack -adb &&
                # jgit gc will barf if it does not like our bitmaps
                jgit gc
index 0680dec808574fb334612163ebd43d31418a37d5..d4f435155f5c9350f196904b910e2b5466c3ffd6 100755 (executable)
@@ -30,7 +30,7 @@ add () {
        test_tick &&
        commit=$(echo "$text" | git commit-tree $tree $parents) &&
        eval "$name=$commit; export $name" &&
-       echo $commit > .git/refs/heads/$branch &&
+       git update-ref "refs/heads/$branch" "$commit" &&
        eval ${branch}TIP=$commit
 }
 
@@ -45,10 +45,10 @@ pull_to_client () {
 
                        case "$heads" in
                            *A*)
-                                   echo $ATIP > .git/refs/heads/A;;
+                                   git update-ref refs/heads/A "$ATIP";;
                        esac &&
                        case "$heads" in *B*)
-                           echo $BTIP > .git/refs/heads/B;;
+                           git update-ref refs/heads/B "$BTIP";;
                        esac &&
                        git symbolic-ref HEAD refs/heads/$(echo $heads \
                                | sed -e "s/^\(.\).*$/\1/") &&
@@ -92,8 +92,8 @@ test_expect_success 'setup' '
                cur=$(($cur+1))
        done &&
        add B1 $A1 &&
-       echo $ATIP > .git/refs/heads/A &&
-       echo $BTIP > .git/refs/heads/B &&
+       git update-ref refs/heads/A "$ATIP" &&
+       git update-ref refs/heads/B "$BTIP" &&
        git symbolic-ref HEAD refs/heads/B
 '
 
index ae5a530a2dc61a7926abfd471e3325d1638ca457..e402aee6a22e64306c9f093ad1b529b3923fd65e 100755 (executable)
@@ -63,7 +63,7 @@ test_expect_success "fetch test" '
        git commit -a -m "updated by origin" &&
        cd two &&
        git fetch &&
-       test -f .git/refs/heads/one &&
+       git rev-parse --verify refs/heads/one &&
        mine=$(git rev-parse refs/heads/one) &&
        his=$(cd ../one && git rev-parse refs/heads/master) &&
        test "z$mine" = "z$his"
@@ -73,8 +73,8 @@ test_expect_success "fetch test for-merge" '
        cd "$D" &&
        cd three &&
        git fetch &&
-       test -f .git/refs/heads/two &&
-       test -f .git/refs/heads/one &&
+       git rev-parse --verify refs/heads/two &&
+       git rev-parse --verify refs/heads/one &&
        master_in_two=$(cd ../two && git rev-parse master) &&
        one_in_two=$(cd ../two && git rev-parse one) &&
        {
index 83cd35c391dc59973447c7db3ac03e59591ca02f..6a949484d090ea2df02603f9d82bf4f203a799e9 100755 (executable)
@@ -206,8 +206,8 @@ test_expect_success 'overrides work between mixed transfer/upload-pack hideRefs'
        grep refs/tags/magic actual
 '
 
-git fetch origin
 test_expect_success 'ls-remote --symref' '
+       git fetch origin &&
        cat >expect <<-EOF &&
        ref: refs/heads/master  HEAD
        $(git rev-parse HEAD)   HEAD
index 96c46587a510ed60ad33383de9cf43dc01bb862b..a5077d8b7c596523a3b0e0e5e45385e5d7871b9e 100755 (executable)
@@ -94,6 +94,9 @@ mk_child() {
 }
 
 check_push_result () {
+       test $# -ge 3 ||
+       error "bug in the test script: check_push_result requires at least 3 parameters"
+
        repo_name="$1"
        shift
 
@@ -553,10 +556,7 @@ test_expect_success 'branch.*.pushremote config order is irrelevant' '
 test_expect_success 'push with dry-run' '
 
        mk_test testrepo heads/master &&
-       (
-               cd testrepo &&
-               old_commit=$(git show-ref -s --verify refs/heads/master)
-       ) &&
+       old_commit=$(git -C testrepo show-ref -s --verify refs/heads/master) &&
        git push --dry-run testrepo : &&
        check_push_result testrepo $old_commit heads/master
 '
@@ -612,7 +612,7 @@ test_expect_success 'push does not update local refs on failure' '
        chmod +x testrepo/.git/hooks/pre-receive &&
        (
                cd child &&
-               git pull .. master
+               git pull .. master &&
                test_must_fail git push &&
                test $(git rev-parse master) != \
                        $(git rev-parse remotes/origin/master)
@@ -634,7 +634,7 @@ test_expect_success 'pushing valid refs triggers post-receive and post-update ho
        orgmaster=$(cd testrepo && git show-ref -s --verify refs/heads/master) &&
        newmaster=$(git show-ref -s --verify refs/heads/master) &&
        orgnext=$(cd testrepo && git show-ref -s --verify refs/heads/next) &&
-       newnext=$_z40 &&
+       newnext=$ZERO_OID &&
        git push testrepo refs/heads/master:refs/heads/master :refs/heads/next &&
        (
                cd testrepo/.git &&
@@ -672,15 +672,15 @@ test_expect_success 'deleting dangling ref triggers hooks with correct args' '
        (
                cd testrepo/.git &&
                cat >pre-receive.expect <<-EOF &&
-               $_z40 $_z40 refs/heads/master
+               $ZERO_OID $ZERO_OID refs/heads/master
                EOF
 
                cat >update.expect <<-EOF &&
-               refs/heads/master $_z40 $_z40
+               refs/heads/master $ZERO_OID $ZERO_OID
                EOF
 
                cat >post-receive.expect <<-EOF &&
-               $_z40 $_z40 refs/heads/master
+               $ZERO_OID $ZERO_OID refs/heads/master
                EOF
 
                cat >post-update.expect <<-EOF &&
@@ -703,12 +703,12 @@ test_expect_success 'deletion of a non-existent ref is not fed to post-receive a
                cd testrepo/.git &&
                cat >pre-receive.expect <<-EOF &&
                $orgmaster $newmaster refs/heads/master
-               $_z40 $_z40 refs/heads/nonexistent
+               $ZERO_OID $ZERO_OID refs/heads/nonexistent
                EOF
 
                cat >update.expect <<-EOF &&
                refs/heads/master $orgmaster $newmaster
-               refs/heads/nonexistent $_z40 $_z40
+               refs/heads/nonexistent $ZERO_OID $ZERO_OID
                EOF
 
                cat >post-receive.expect <<-EOF &&
@@ -732,11 +732,11 @@ test_expect_success 'deletion of a non-existent ref alone does trigger post-rece
        (
                cd testrepo/.git &&
                cat >pre-receive.expect <<-EOF &&
-               $_z40 $_z40 refs/heads/nonexistent
+               $ZERO_OID $ZERO_OID refs/heads/nonexistent
                EOF
 
                cat >update.expect <<-EOF &&
-               refs/heads/nonexistent $_z40 $_z40
+               refs/heads/nonexistent $ZERO_OID $ZERO_OID
                EOF
 
                test_cmp pre-receive.expect pre-receive.actual &&
@@ -751,7 +751,7 @@ test_expect_success 'mixed ref updates, deletes, invalid deletes trigger hooks w
        orgmaster=$(cd testrepo && git show-ref -s --verify refs/heads/master) &&
        newmaster=$(git show-ref -s --verify refs/heads/master) &&
        orgnext=$(cd testrepo && git show-ref -s --verify refs/heads/next) &&
-       newnext=$_z40 &&
+       newnext=$ZERO_OID &&
        orgpu=$(cd testrepo && git show-ref -s --verify refs/heads/pu) &&
        newpu=$(git show-ref -s --verify refs/heads/master) &&
        git push testrepo refs/heads/master:refs/heads/master \
@@ -763,14 +763,14 @@ test_expect_success 'mixed ref updates, deletes, invalid deletes trigger hooks w
                $orgmaster $newmaster refs/heads/master
                $orgnext $newnext refs/heads/next
                $orgpu $newpu refs/heads/pu
-               $_z40 $_z40 refs/heads/nonexistent
+               $ZERO_OID $ZERO_OID refs/heads/nonexistent
                EOF
 
                cat >update.expect <<-EOF &&
                refs/heads/master $orgmaster $newmaster
                refs/heads/next $orgnext $newnext
                refs/heads/pu $orgpu $newpu
-               refs/heads/nonexistent $_z40 $_z40
+               refs/heads/nonexistent $ZERO_OID $ZERO_OID
                EOF
 
                cat >post-receive.expect <<-EOF &&
index 207899a99faa4ff8fb4d7226e930ba1e9803dc74..3b0cb98422930891548cb45344dd226911a8063f 100755 (executable)
@@ -27,7 +27,7 @@ test_expect_success 'suffix ref is ignored during fetch' '
 '
 
 test_expect_success 'try to create repo with absurdly long refname' '
-       ref240=$_z40/$_z40/$_z40/$_z40/$_z40/$_z40 &&
+       ref240=$ZERO_OID/$ZERO_OID/$ZERO_OID/$ZERO_OID/$ZERO_OID/$ZERO_OID &&
        ref1440=$ref240/$ref240/$ref240/$ref240/$ref240/$ref240 &&
        git init long &&
        (
index f5721b4a59ea4a3333cc0e74d8ab9ad2f182dd5b..913089b14452976144878415c05a9a2c743d22bf 100755 (executable)
@@ -26,14 +26,14 @@ setup_askpass_helper
 cat >exp <<EOF
 > GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1
 > Accept: */*
-> Accept-Encoding: gzip
+> Accept-Encoding: ENCODINGS
 > Pragma: no-cache
 < HTTP/1.1 200 OK
 < Pragma: no-cache
 < Cache-Control: no-cache, max-age=0, must-revalidate
 < Content-Type: application/x-git-upload-pack-advertisement
 > POST /smart/repo.git/git-upload-pack HTTP/1.1
-> Accept-Encoding: gzip
+> Accept-Encoding: ENCODINGS
 > Content-Type: application/x-git-upload-pack-request
 > Accept: application/x-git-upload-pack-result
 > Content-Length: xxx
@@ -79,8 +79,13 @@ test_expect_success 'clone http repository' '
                /^< Date: /d
                /^< Content-Length: /d
                /^< Transfer-Encoding: /d
-       " >act &&
-       test_cmp exp act
+       " >actual &&
+       sed -e "s/^> Accept-Encoding: .*/> Accept-Encoding: ENCODINGS/" \
+                       actual >actual.smudged &&
+       test_cmp exp actual.smudged &&
+
+       grep "Accept-Encoding:.*gzip" actual >actual.gzip &&
+       test_line_count = 2 actual.gzip
 '
 
 test_expect_success 'fetch changes via http' '
index ba975bb3557b5e243deb2c40217dbe04ff4953ae..ac53d638695b69b67e18832846be8105864367de 100755 (executable)
@@ -78,8 +78,8 @@ test_expect_success 'push to default' '
 cat >expected <<EOF
 parent1
 repo1
-refs/tags/one $COMMIT1 refs/tags/tag1 $_z40
-HEAD~ $COMMIT2 refs/heads/prev $_z40
+refs/tags/one $COMMIT1 refs/tags/tag1 $ZERO_OID
+HEAD~ $COMMIT2 refs/heads/prev $ZERO_OID
 EOF
 
 test_expect_success 'push non-branches' '
@@ -90,7 +90,7 @@ test_expect_success 'push non-branches' '
 cat >expected <<EOF
 parent1
 repo1
-(delete) $_z40 refs/heads/prev $COMMIT2
+(delete) $ZERO_OID refs/heads/prev $COMMIT2
 EOF
 
 test_expect_success 'push delete' '
@@ -101,7 +101,7 @@ test_expect_success 'push delete' '
 cat >expected <<EOF
 repo1
 repo1
-HEAD $COMMIT3 refs/heads/other $_z40
+HEAD $COMMIT3 refs/heads/other $ZERO_OID
 EOF
 
 test_expect_success 'push to URL' '
index 72d7bc56280b13c68b930ea41690788ad87c9853..75ec79e6cb839e71f6830d9301d1982d0fc68925 100755 (executable)
@@ -10,6 +10,7 @@ test_expect_success 'test capability advertisement' '
        agent=git/$(git version | cut -d" " -f3)
        ls-refs
        fetch=shallow
+       server-option
        0000
        EOF
 
@@ -173,4 +174,38 @@ test_expect_success 'symrefs parameter' '
        test_cmp actual expect
 '
 
+test_expect_success 'sending server-options' '
+       test-pkt-line pack >in <<-EOF &&
+       command=ls-refs
+       server-option=hello
+       server-option=world
+       0001
+       ref-prefix HEAD
+       0000
+       EOF
+
+       cat >expect <<-EOF &&
+       $(git rev-parse HEAD) HEAD
+       0000
+       EOF
+
+       git serve --stateless-rpc <in >out &&
+       test-pkt-line unpack <out >actual &&
+       test_cmp actual expect
+'
+
+test_expect_success 'unexpected lines are not allowed in fetch request' '
+       git init server &&
+
+       test-pkt-line pack >in <<-EOF &&
+       command=fetch
+       0001
+       this-is-not-a-command
+       0000
+       EOF
+
+       test_must_fail git -C server serve --stateless-rpc <in >/dev/null 2>err &&
+       grep "unexpected line: .this-is-not-a-command." err
+'
+
 test_done
index b6c72ab51ed2f4ab3012252b70c6b03fafbcaf65..a4fe6508bdd73784758b2ea80ab81752e15c01b3 100755 (executable)
@@ -154,6 +154,22 @@ test_expect_success 'ref advertisment is filtered with ls-remote using protocol
        test_cmp actual expect
 '
 
+test_expect_success 'server-options are sent when using ls-remote' '
+       test_when_finished "rm -f log" &&
+
+       GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \
+               ls-remote -o hello -o world "file://$(pwd)/file_parent" master >actual &&
+
+       cat >expect <<-EOF &&
+       $(git -C file_parent rev-parse refs/heads/master)$(printf "\t")refs/heads/master
+       EOF
+
+       test_cmp actual expect &&
+       grep "server-option=hello" log &&
+       grep "server-option=world" log
+'
+
+
 test_expect_success 'clone with file:// using protocol v2' '
        test_when_finished "rm -f log" &&
 
@@ -201,6 +217,134 @@ test_expect_success 'ref advertisment is filtered during fetch using protocol v2
        ! grep "refs/tags/three" log
 '
 
+test_expect_success 'server-options are sent when fetching' '
+       test_when_finished "rm -f log" &&
+
+       test_commit -C file_parent four &&
+
+       GIT_TRACE_PACKET="$(pwd)/log" git -C file_child -c protocol.version=2 \
+               fetch -o hello -o world origin master &&
+
+       git -C file_child log -1 --format=%s origin/master >actual &&
+       git -C file_parent log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       grep "server-option=hello" log &&
+       grep "server-option=world" log
+'
+
+test_expect_success 'upload-pack respects config using protocol v2' '
+       git init server &&
+       write_script server/.git/hook <<-\EOF &&
+               touch hookout
+               "$@"
+       EOF
+       test_commit -C server one &&
+
+       test_config_global uploadpack.packobjectshook ./hook &&
+       test_path_is_missing server/.git/hookout &&
+       git -c protocol.version=2 clone "file://$(pwd)/server" client &&
+       test_path_is_file server/.git/hookout
+'
+
+test_expect_success 'setup filter tests' '
+       rm -rf server client &&
+       git init server &&
+
+       # 1 commit to create a file, and 1 commit to modify it
+       test_commit -C server message1 a.txt &&
+       test_commit -C server message2 a.txt &&
+       git -C server config protocol.version 2 &&
+       git -C server config uploadpack.allowfilter 1 &&
+       git -C server config uploadpack.allowanysha1inwant 1 &&
+       git -C server config protocol.version 2
+'
+
+test_expect_success 'partial clone' '
+       GIT_TRACE_PACKET="$(pwd)/trace" git -c protocol.version=2 \
+               clone --filter=blob:none "file://$(pwd)/server" client &&
+       grep "version 2" trace &&
+
+       # Ensure that the old version of the file is missing
+       git -C client rev-list master --quiet --objects --missing=print \
+               >observed.oids &&
+       grep "$(git -C server rev-parse message1:a.txt)" observed.oids &&
+
+       # Ensure that client passes fsck
+       git -C client fsck
+'
+
+test_expect_success 'dynamically fetch missing object' '
+       rm "$(pwd)/trace" &&
+       GIT_TRACE_PACKET="$(pwd)/trace" git -C client -c protocol.version=2 \
+               cat-file -p $(git -C server rev-parse message1:a.txt) &&
+       grep "version 2" trace
+'
+
+test_expect_success 'partial fetch' '
+       rm -rf client "$(pwd)/trace" &&
+       git init client &&
+       SERVER="file://$(pwd)/server" &&
+       test_config -C client extensions.partialClone "$SERVER" &&
+
+       GIT_TRACE_PACKET="$(pwd)/trace" git -C client -c protocol.version=2 \
+               fetch --filter=blob:none "$SERVER" master:refs/heads/other &&
+       grep "version 2" trace &&
+
+       # Ensure that the old version of the file is missing
+       git -C client rev-list other --quiet --objects --missing=print \
+               >observed.oids &&
+       grep "$(git -C server rev-parse message1:a.txt)" observed.oids &&
+
+       # Ensure that client passes fsck
+       git -C client fsck
+'
+
+test_expect_success 'do not advertise filter if not configured to do so' '
+       SERVER="file://$(pwd)/server" &&
+
+       rm "$(pwd)/trace" &&
+       git -C server config uploadpack.allowfilter 1 &&
+       GIT_TRACE_PACKET="$(pwd)/trace" git -c protocol.version=2 \
+               ls-remote "$SERVER" &&
+       grep "fetch=.*filter" trace &&
+
+       rm "$(pwd)/trace" &&
+       git -C server config uploadpack.allowfilter 0 &&
+       GIT_TRACE_PACKET="$(pwd)/trace" git -c protocol.version=2 \
+               ls-remote "$SERVER" &&
+       grep "fetch=" trace >fetch_capabilities &&
+       ! grep filter fetch_capabilities
+'
+
+test_expect_success 'partial clone warns if filter is not advertised' '
+       rm -rf client &&
+       git -C server config uploadpack.allowfilter 0 &&
+       git -c protocol.version=2 \
+               clone --filter=blob:none "file://$(pwd)/server" client 2>err &&
+       test_i18ngrep "filtering not recognized by server, ignoring" err
+'
+
+test_expect_success 'even with handcrafted request, filter does not work if not advertised' '
+       git -C server config uploadpack.allowfilter 0 &&
+
+       # Custom request that tries to filter even though it is not advertised.
+       test-pkt-line pack >in <<-EOF &&
+       command=fetch
+       0001
+       want $(git -C server rev-parse master)
+       filter blob:none
+       0000
+       EOF
+
+       test_must_fail git -C server serve --stateless-rpc <in >/dev/null 2>err &&
+       grep "unexpected line: .filter blob:none." err &&
+
+       # Exercise to ensure that if advertised, filter works
+       git -C server config uploadpack.allowfilter 1 &&
+       git -C server serve --stateless-rpc <in >/dev/null
+'
+
 test_expect_success 'default refspec is used to filter ref when fetchcing' '
        test_when_finished "rm -f log" &&
 
index 05ddc69cf2ad9470a125fda47fc450401f94902b..7504ba47511bf5df27dd54d7090fdd920285db07 100755 (executable)
@@ -110,4 +110,13 @@ do
        "
 
 done
+
+test_expect_success 'show advice that grafts are deprecated' '
+       git show HEAD 2>err &&
+       test_i18ngrep "git replace" err &&
+       test_config advice.graftFileDeprecated false &&
+       git show HEAD 2>err &&
+       test_i18ngrep ! "git replace" err
+'
+
 test_done
index 98be78b4a2123cad7e16911ae2eedff5695ba3d3..ec42c2f7797c38f39bb3420402d975a94217d1f4 100755 (executable)
@@ -447,8 +447,8 @@ test_expect_success '--abbrev' '
        git log -1 --format="%h %h %h" HEAD >actual1 &&
        git log -1 --abbrev=5 --format="%h %h %h" HEAD >actual2 &&
        git log -1 --abbrev=5 --format="%H %H %H" HEAD >actual3 &&
-       sed -e "s/$_x40/LONG/g" -e "s/$_x05/SHORT/g" <actual2 >fuzzy2 &&
-       sed -e "s/$_x40/LONG/g" -e "s/$_x05/SHORT/g" <actual3 >fuzzy3 &&
+       sed -e "s/$OID_REGEX/LONG/g" -e "s/$_x05/SHORT/g" <actual2 >fuzzy2 &&
+       sed -e "s/$OID_REGEX/LONG/g" -e "s/$_x05/SHORT/g" <actual3 >fuzzy3 &&
        test_cmp expect2 fuzzy2 &&
        test_cmp expect3 fuzzy3 &&
        ! test_cmp actual1 actual2
index 31db7b5f9125ed346450d7bfe29d8bb5fac0460a..aa2d360ce35940a176a0ef04ca7ba8560adefb23 100755 (executable)
@@ -34,7 +34,7 @@ doit () {
 
        commit=$(echo $NAME | git commit-tree $T $PARENTS) &&
 
-       echo $commit >.git/refs/tags/$NAME &&
+       git update-ref "refs/tags/$NAME" "$commit" &&
        echo $commit
 }
 
index 2a0fbb87b1d63f1a07f8a3934e1546c695a73f56..b5a1190ffe63b53d8b637b39a15d3d94b3efa6cd 100755 (executable)
@@ -9,7 +9,7 @@ note () {
 }
 
 unnote () {
-       git name-rev --tags --stdin | sed -e "s|$_x40 (tags/\([^)]*\)) |\1 |g"
+       git name-rev --tags --stdin | sed -e "s|$OID_REGEX (tags/\([^)]*\)) |\1 |g"
 }
 
 test_expect_success setup '
index 48747e71df82bf20e053ac9475608ae9d9071d51..b760c223c6a56609b7dace5353671aec8243b85d 100755 (executable)
@@ -247,7 +247,7 @@ test_expect_success 'merge of identical changes in a renamed file' '
        git reset --hard HEAD^ &&
        git checkout change &&
        GIT_MERGE_VERBOSITY=3 git merge change+rename >out &&
-       test_i18ngrep "^Skipped B" out
+       test_i18ngrep "^Skipped B" out
 '
 
 test_expect_success 'setup for rename + d/f conflicts' '
diff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh
new file mode 100755 (executable)
index 0000000..2e28f29
--- /dev/null
@@ -0,0 +1,3998 @@
+#!/bin/sh
+
+test_description="recursive merge with directory renames"
+# includes checking of many corner cases, with a similar methodology to:
+#   t6042: corner cases with renames but not criss-cross merges
+#   t6036: corner cases with both renames and criss-cross merges
+#
+# The setup for all of them, pictorially, is:
+#
+#      A
+#      o
+#     / \
+#  O o   ?
+#     \ /
+#      o
+#      B
+#
+# To help make it easier to follow the flow of tests, they have been
+# divided into sections and each test will start with a quick explanation
+# of what commits O, A, and B contain.
+#
+# Notation:
+#    z/{b,c}   means  files z/b and z/c both exist
+#    x/d_1     means  file x/d exists with content d1.  (Purpose of the
+#                     underscore notation is to differentiate different
+#                     files that might be renamed into each other's paths.)
+
+. ./test-lib.sh
+
+
+###########################################################################
+# SECTION 1: Basic cases we should be able to handle
+###########################################################################
+
+# Testcase 1a, Basic directory rename.
+#   Commit O: z/{b,c}
+#   Commit A: y/{b,c}
+#   Commit B: z/{b,c,d,e/f}
+#   Expected: y/{b,c,d,e/f}
+
+test_expect_success '1a-setup: Simple directory rename detection' '
+       test_create_repo 1a &&
+       (
+               cd 1a &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo d >z/d &&
+               mkdir z/e &&
+               echo f >z/e/f &&
+               git add z/d z/e/f &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '1a-check: Simple directory rename detection' '
+       (
+               cd 1a &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 4 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:y/d HEAD:y/e/f &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    B:z/d    B:z/e/f &&
+               test_cmp expect actual &&
+
+               git hash-object y/d >actual &&
+               git rev-parse B:z/d >expect &&
+               test_cmp expect actual &&
+
+               test_must_fail git rev-parse HEAD:z/d &&
+               test_must_fail git rev-parse HEAD:z/e/f &&
+               test_path_is_missing z/d &&
+               test_path_is_missing z/e/f
+       )
+'
+
+# Testcase 1b, Merge a directory with another
+#   Commit O: z/{b,c},   y/d
+#   Commit A: z/{b,c,e}, y/d
+#   Commit B: y/{b,c,d}
+#   Expected: y/{b,c,d,e}
+
+test_expect_success '1b-setup: Merge a directory with another' '
+       test_create_repo 1b &&
+       (
+               cd 1b &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               mkdir y &&
+               echo d >y/d &&
+               git add z y &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               echo e >z/e &&
+               git add z/e &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv z/b y &&
+               git mv z/c y &&
+               rmdir z &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '1b-check: Merge a directory with another' '
+       (
+               cd 1b &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 4 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:y/d HEAD:y/e &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    O:y/d    A:z/e &&
+               test_cmp expect actual &&
+               test_must_fail git rev-parse HEAD:z/e
+       )
+'
+
+# Testcase 1c, Transitive renaming
+#   (Related to testcases 3a and 6d -- when should a transitive rename apply?)
+#   (Related to testcases 9c and 9d -- can transitivity repeat?)
+#   (Related to testcase 12b -- joint-transitivity?)
+#   Commit O: z/{b,c},   x/d
+#   Commit A: y/{b,c},   x/d
+#   Commit B: z/{b,c,d}
+#   Expected: y/{b,c,d}  (because x/d -> z/d -> y/d)
+
+test_expect_success '1c-setup: Transitive renaming' '
+       test_create_repo 1c &&
+       (
+               cd 1c &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               mkdir x &&
+               echo d >x/d &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv x/d z/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '1c-check: Transitive renaming' '
+       (
+               cd 1c &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:y/d &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    O:x/d &&
+               test_cmp expect actual &&
+               test_must_fail git rev-parse HEAD:x/d &&
+               test_must_fail git rev-parse HEAD:z/d &&
+               test_path_is_missing z/d
+       )
+'
+
+# Testcase 1d, Directory renames (merging two directories into one new one)
+#              cause a rename/rename(2to1) conflict
+#   (Related to testcases 1c and 7b)
+#   Commit O. z/{b,c},        y/{d,e}
+#   Commit A. x/{b,c},        y/{d,e,m,wham_1}
+#   Commit B. z/{b,c,n,wham_2}, x/{d,e}
+#   Expected: x/{b,c,d,e,m,n}, CONFLICT:(y/wham_1 & z/wham_2 -> x/wham)
+#   Note: y/m & z/n should definitely move into x.  By the same token, both
+#         y/wham_1 & z/wham_2 should too...giving us a conflict.
+
+test_expect_success '1d-setup: Directory renames cause a rename/rename(2to1) conflict' '
+       test_create_repo 1d &&
+       (
+               cd 1d &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               mkdir y &&
+               echo d >y/d &&
+               echo e >y/e &&
+               git add z y &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z x &&
+               echo m >y/m &&
+               echo wham1 >y/wham &&
+               git add y &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv y x &&
+               echo n >z/n &&
+               echo wham2 >z/wham &&
+               git add z &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '1d-check: Directory renames cause a rename/rename(2to1) conflict' '
+       (
+               cd 1d &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT (rename/rename)" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 8 out &&
+               git ls-files -u >out &&
+               test_line_count = 2 out &&
+               git ls-files -o >out &&
+               test_line_count = 3 out &&
+
+               git rev-parse >actual \
+                       :0:x/b :0:x/c :0:x/d :0:x/e :0:x/m :0:x/n &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c  O:y/d  O:y/e  A:y/m  B:z/n &&
+               test_cmp expect actual &&
+
+               test_must_fail git rev-parse :0:x/wham &&
+               git rev-parse >actual \
+                       :2:x/wham :3:x/wham &&
+               git rev-parse >expect \
+                        A:y/wham  B:z/wham &&
+               test_cmp expect actual &&
+
+               test_path_is_missing x/wham &&
+               test_path_is_file x/wham~HEAD &&
+               test_path_is_file x/wham~B^0 &&
+
+               git hash-object >actual \
+                       x/wham~HEAD x/wham~B^0 &&
+               git rev-parse >expect \
+                       A:y/wham    B:z/wham &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 1e, Renamed directory, with all filenames being renamed too
+#   (Related to testcases 9f & 9g)
+#   Commit O: z/{oldb,oldc}
+#   Commit A: y/{newb,newc}
+#   Commit B: z/{oldb,oldc,d}
+#   Expected: y/{newb,newc,d}
+
+test_expect_success '1e-setup: Renamed directory, with all files being renamed too' '
+       test_create_repo 1e &&
+       (
+               cd 1e &&
+
+               mkdir z &&
+               echo b >z/oldb &&
+               echo c >z/oldc &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               mkdir y &&
+               git mv z/oldb y/newb &&
+               git mv z/oldc y/newc &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo d >z/d &&
+               git add z/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '1e-check: Renamed directory, with all files being renamed too' '
+       (
+               cd 1e &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/newb HEAD:y/newc HEAD:y/d &&
+               git rev-parse >expect \
+                       O:z/oldb    O:z/oldc    B:z/d &&
+               test_cmp expect actual &&
+               test_must_fail git rev-parse HEAD:z/d
+       )
+'
+
+# Testcase 1f, Split a directory into two other directories
+#   (Related to testcases 3a, all of section 2, and all of section 4)
+#   Commit O: z/{b,c,d,e,f}
+#   Commit A: z/{b,c,d,e,f,g}
+#   Commit B: y/{b,c}, x/{d,e,f}
+#   Expected: y/{b,c}, x/{d,e,f,g}
+
+test_expect_success '1f-setup: Split a directory into two other directories' '
+       test_create_repo 1f &&
+       (
+               cd 1f &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               echo d >z/d &&
+               echo e >z/e &&
+               echo f >z/f &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               echo g >z/g &&
+               git add z/g &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               mkdir y &&
+               mkdir x &&
+               git mv z/b y/ &&
+               git mv z/c y/ &&
+               git mv z/d x/ &&
+               git mv z/e x/ &&
+               git mv z/f x/ &&
+               rmdir z &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '1f-check: Split a directory into two other directories' '
+       (
+               cd 1f &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 6 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:x/d HEAD:x/e HEAD:x/f HEAD:x/g &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    O:z/d    O:z/e    O:z/f    A:z/g &&
+               test_cmp expect actual &&
+               test_path_is_missing z/g &&
+               test_must_fail git rev-parse HEAD:z/g
+       )
+'
+
+###########################################################################
+# Rules suggested by testcases in section 1:
+#
+#   We should still detect the directory rename even if it wasn't just
+#   the directory renamed, but the files within it. (see 1b)
+#
+#   If renames split a directory into two or more others, the directory
+#   with the most renames, "wins" (see 1c).  However, see the testcases
+#   in section 2, plus testcases 3a and 4a.
+###########################################################################
+
+
+###########################################################################
+# SECTION 2: Split into multiple directories, with equal number of paths
+#
+# Explore the splitting-a-directory rules a bit; what happens in the
+# edge cases?
+#
+# Note that there is a closely related case of a directory not being
+# split on either side of history, but being renamed differently on
+# each side.  See testcase 8e for that.
+###########################################################################
+
+# Testcase 2a, Directory split into two on one side, with equal numbers of paths
+#   Commit O: z/{b,c}
+#   Commit A: y/b, w/c
+#   Commit B: z/{b,c,d}
+#   Expected: y/b, w/c, z/d, with warning about z/ -> (y/ vs. w/) conflict
+test_expect_success '2a-setup: Directory split into two on one side, with equal numbers of paths' '
+       test_create_repo 2a &&
+       (
+               cd 2a &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               mkdir y &&
+               mkdir w &&
+               git mv z/b y/ &&
+               git mv z/c w/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo d >z/d &&
+               git add z/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '2a-check: Directory split into two on one side, with equal numbers of paths' '
+       (
+               cd 2a &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT.*directory rename split" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :0:w/c :0:z/d &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c  B:z/d &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 2b, Directory split into two on one side, with equal numbers of paths
+#   Commit O: z/{b,c}
+#   Commit A: y/b, w/c
+#   Commit B: z/{b,c}, x/d
+#   Expected: y/b, w/c, x/d; No warning about z/ -> (y/ vs. w/) conflict
+test_expect_success '2b-setup: Directory split into two on one side, with equal numbers of paths' '
+       test_create_repo 2b &&
+       (
+               cd 2b &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               mkdir y &&
+               mkdir w &&
+               git mv z/b y/ &&
+               git mv z/c w/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               mkdir x &&
+               echo d >x/d &&
+               git add x/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '2b-check: Directory split into two on one side, with equal numbers of paths' '
+       (
+               cd 2b &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 >out &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :0:w/c :0:x/d &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c  B:x/d &&
+               test_cmp expect actual &&
+               test_i18ngrep ! "CONFLICT.*directory rename split" out
+       )
+'
+
+###########################################################################
+# Rules suggested by section 2:
+#
+#   None; the rule was already covered in section 1.  These testcases are
+#   here just to make sure the conflict resolution and necessary warning
+#   messages are handled correctly.
+###########################################################################
+
+
+###########################################################################
+# SECTION 3: Path in question is the source path for some rename already
+#
+# Combining cases from Section 1 and trying to handle them could lead to
+# directory renaming detection being over-applied.  So, this section
+# provides some good testcases to check that the implementation doesn't go
+# too far.
+###########################################################################
+
+# Testcase 3a, Avoid implicit rename if involved as source on other side
+#   (Related to testcases 1c, 1f, and 9h)
+#   Commit O: z/{b,c,d}
+#   Commit A: z/{b,c,d} (no change)
+#   Commit B: y/{b,c}, x/d
+#   Expected: y/{b,c}, x/d
+test_expect_success '3a-setup: Avoid implicit rename if involved as source on other side' '
+       test_create_repo 3a &&
+       (
+               cd 3a &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               echo d >z/d &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               test_tick &&
+               git commit --allow-empty -m "A" &&
+
+               git checkout B &&
+               mkdir y &&
+               mkdir x &&
+               git mv z/b y/ &&
+               git mv z/c y/ &&
+               git mv z/d x/ &&
+               rmdir z &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '3a-check: Avoid implicit rename if involved as source on other side' '
+       (
+               cd 3a &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:x/d &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    O:z/d &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 3b, Avoid implicit rename if involved as source on other side
+#   (Related to testcases 5c and 7c, also kind of 1e and 1f)
+#   Commit O: z/{b,c,d}
+#   Commit A: y/{b,c}, x/d
+#   Commit B: z/{b,c}, w/d
+#   Expected: y/{b,c}, CONFLICT:(z/d -> x/d vs. w/d)
+#   NOTE: We're particularly checking that since z/d is already involved as
+#         a source in a file rename on the same side of history, that we don't
+#         get it involved in directory rename detection.  If it were, we might
+#         end up with CONFLICT:(z/d -> y/d vs. x/d vs. w/d), i.e. a
+#         rename/rename/rename(1to3) conflict, which is just weird.
+test_expect_success '3b-setup: Avoid implicit rename if involved as source on current side' '
+       test_create_repo 3b &&
+       (
+               cd 3b &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               echo d >z/d &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               mkdir y &&
+               mkdir x &&
+               git mv z/b y/ &&
+               git mv z/c y/ &&
+               git mv z/d x/ &&
+               rmdir z &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               mkdir w &&
+               git mv z/d w/ &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '3b-check: Avoid implicit rename if involved as source on current side' '
+       (
+               cd 3b &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep CONFLICT.*rename/rename.*z/d.*x/d.*w/d out &&
+               test_i18ngrep ! CONFLICT.*rename/rename.*y/d out &&
+
+               git ls-files -s >out &&
+               test_line_count = 5 out &&
+               git ls-files -u >out &&
+               test_line_count = 3 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :0:y/c :1:z/d :2:x/d :3:w/d &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c  O:z/d  O:z/d  O:z/d &&
+               test_cmp expect actual &&
+
+               test_path_is_missing z/d &&
+               git hash-object >actual \
+                       x/d   w/d &&
+               git rev-parse >expect \
+                       O:z/d O:z/d &&
+               test_cmp expect actual
+       )
+'
+
+###########################################################################
+# Rules suggested by section 3:
+#
+#   Avoid directory-rename-detection for a path, if that path is the source
+#   of a rename on either side of a merge.
+###########################################################################
+
+
+###########################################################################
+# SECTION 4: Partially renamed directory; still exists on both sides of merge
+#
+# What if we were to attempt to do directory rename detection when someone
+# "mostly" moved a directory but still left some files around, or,
+# equivalently, fully renamed a directory in one commmit and then recreated
+# that directory in a later commit adding some new files and then tried to
+# merge?
+#
+# It's hard to divine user intent in these cases, because you can make an
+# argument that, depending on the intermediate history of the side being
+# merged, that some users will want files in that directory to
+# automatically be detected and renamed, while users with a different
+# intermediate history wouldn't want that rename to happen.
+#
+# I think that it is best to simply not have directory rename detection
+# apply to such cases.  My reasoning for this is four-fold: (1) it's
+# easiest for users in general to figure out what happened if we don't
+# apply directory rename detection in any such case, (2) it's an easy rule
+# to explain ["We don't do directory rename detection if the directory
+# still exists on both sides of the merge"], (3) we can get some hairy
+# edge/corner cases that would be really confusing and possibly not even
+# representable in the index if we were to even try, and [related to 3] (4)
+# attempting to resolve this issue of divining user intent by examining
+# intermediate history goes against the spirit of three-way merges and is a
+# path towards crazy corner cases that are far more complex than what we're
+# already dealing with.
+#
+# Note that the wording of the rule ("We don't do directory rename
+# detection if the directory still exists on both sides of the merge.")
+# also excludes "renaming" of a directory into a subdirectory of itself
+# (e.g. /some/dir/* -> /some/dir/subdir/*).  It may be possible to carve
+# out an exception for "renaming"-beneath-itself cases without opening
+# weird edge/corner cases for other partial directory renames, but for now
+# we are keeping the rule simple.
+#
+# This section contains a test for a partially-renamed-directory case.
+###########################################################################
+
+# Testcase 4a, Directory split, with original directory still present
+#   (Related to testcase 1f)
+#   Commit O: z/{b,c,d,e}
+#   Commit A: y/{b,c,d}, z/e
+#   Commit B: z/{b,c,d,e,f}
+#   Expected: y/{b,c,d}, z/{e,f}
+#   NOTE: Even though most files from z moved to y, we don't want f to follow.
+
+test_expect_success '4a-setup: Directory split, with original directory still present' '
+       test_create_repo 4a &&
+       (
+               cd 4a &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               echo d >z/d &&
+               echo e >z/e &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               mkdir y &&
+               git mv z/b y/ &&
+               git mv z/c y/ &&
+               git mv z/d y/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo f >z/f &&
+               git add z/f &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '4a-check: Directory split, with original directory still present' '
+       (
+               cd 4a &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 5 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:y/d HEAD:z/e HEAD:z/f &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    O:z/d    O:z/e    B:z/f &&
+               test_cmp expect actual
+       )
+'
+
+###########################################################################
+# Rules suggested by section 4:
+#
+#   Directory-rename-detection should be turned off for any directories (as
+#   a source for renames) that exist on both sides of the merge.  (The "as
+#   a source for renames" clarification is due to cases like 1c where
+#   the target directory exists on both sides and we do want the rename
+#   detection.)  But, sadly, see testcase 8b.
+###########################################################################
+
+
+###########################################################################
+# SECTION 5: Files/directories in the way of subset of to-be-renamed paths
+#
+# Implicitly renaming files due to a detected directory rename could run
+# into problems if there are files or directories in the way of the paths
+# we want to rename.  Explore such cases in this section.
+###########################################################################
+
+# Testcase 5a, Merge directories, other side adds files to original and target
+#   Commit O: z/{b,c},       y/d
+#   Commit A: z/{b,c,e_1,f}, y/{d,e_2}
+#   Commit B: y/{b,c,d}
+#   Expected: z/e_1, y/{b,c,d,e_2,f} + CONFLICT warning
+#   NOTE: While directory rename detection is active here causing z/f to
+#         become y/f, we did not apply this for z/e_1 because that would
+#         give us an add/add conflict for y/e_1 vs y/e_2.  This problem with
+#         this add/add, is that both versions of y/e are from the same side
+#         of history, giving us no way to represent this conflict in the
+#         index.
+
+test_expect_success '5a-setup: Merge directories, other side adds files to original and target' '
+       test_create_repo 5a &&
+       (
+               cd 5a &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               mkdir y &&
+               echo d >y/d &&
+               git add z y &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               echo e1 >z/e &&
+               echo f >z/f &&
+               echo e2 >y/e &&
+               git add z/e z/f y/e &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv z/b y/ &&
+               git mv z/c y/ &&
+               rmdir z &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '5a-check: Merge directories, other side adds files to original and target' '
+       (
+               cd 5a &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT.*implicit dir rename" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 6 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :0:y/c :0:y/d :0:y/e :0:z/e :0:y/f &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c  O:y/d  A:y/e  A:z/e  A:z/f &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 5b, Rename/delete in order to get add/add/add conflict
+#   (Related to testcase 8d; these may appear slightly inconsistent to users;
+#    Also related to testcases 7d and 7e)
+#   Commit O: z/{b,c,d_1}
+#   Commit A: y/{b,c,d_2}
+#   Commit B: z/{b,c,d_1,e}, y/d_3
+#   Expected: y/{b,c,e}, CONFLICT(add/add: y/d_2 vs. y/d_3)
+#   NOTE: If z/d_1 in commit B were to be involved in dir rename detection, as
+#         we normaly would since z/ is being renamed to y/, then this would be
+#         a rename/delete (z/d_1 -> y/d_1 vs. deleted) AND an add/add/add
+#         conflict of y/d_1 vs. y/d_2 vs. y/d_3.  Add/add/add is not
+#         representable in the index, so the existence of y/d_3 needs to
+#         cause us to bail on directory rename detection for that path, falling
+#         back to git behavior without the directory rename detection.
+
+test_expect_success '5b-setup: Rename/delete in order to get add/add/add conflict' '
+       test_create_repo 5b &&
+       (
+               cd 5b &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               echo d1 >z/d &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git rm z/d &&
+               git mv z y &&
+               echo d2 >y/d &&
+               git add y/d &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               mkdir y &&
+               echo d3 >y/d &&
+               echo e >z/e &&
+               git add y/d z/e &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '5b-check: Rename/delete in order to get add/add/add conflict' '
+       (
+               cd 5b &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT (add/add).* y/d" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 5 out &&
+               git ls-files -u >out &&
+               test_line_count = 2 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :0:y/c :0:y/e :2:y/d :3:y/d &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c  B:z/e  A:y/d  B:y/d &&
+               test_cmp expect actual &&
+
+               test_must_fail git rev-parse :1:y/d &&
+               test_path_is_file y/d
+       )
+'
+
+# Testcase 5c, Transitive rename would cause rename/rename/rename/add/add/add
+#   (Directory rename detection would result in transitive rename vs.
+#    rename/rename(1to2) and turn it into a rename/rename(1to3).  Further,
+#    rename paths conflict with separate adds on the other side)
+#   (Related to testcases 3b and 7c)
+#   Commit O: z/{b,c}, x/d_1
+#   Commit A: y/{b,c,d_2}, w/d_1
+#   Commit B: z/{b,c,d_1,e}, w/d_3, y/d_4
+#   Expected: A mess, but only a rename/rename(1to2)/add/add mess.  Use the
+#             presence of y/d_4 in B to avoid doing transitive rename of
+#             x/d_1 -> z/d_1 -> y/d_1, so that the only paths we have at
+#             y/d are y/d_2 and y/d_4.  We still do the move from z/e to y/e,
+#             though, because it doesn't have anything in the way.
+
+test_expect_success '5c-setup: Transitive rename would cause rename/rename/rename/add/add/add' '
+       test_create_repo 5c &&
+       (
+               cd 5c &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               mkdir x &&
+               echo d1 >x/d &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               echo d2 >y/d &&
+               git add y/d &&
+               git mv x w &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv x/d z/ &&
+               mkdir w &&
+               mkdir y &&
+               echo d3 >w/d &&
+               echo d4 >y/d &&
+               echo e >z/e &&
+               git add w/ y/ z/e &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '5c-check: Transitive rename would cause rename/rename/rename/add/add/add' '
+       (
+               cd 5c &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT (rename/rename).*x/d.*w/d.*z/d" out &&
+               test_i18ngrep "CONFLICT (add/add).* y/d" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 9 out &&
+               git ls-files -u >out &&
+               test_line_count = 6 out &&
+               git ls-files -o >out &&
+               test_line_count = 3 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :0:y/c :0:y/e &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c  B:z/e &&
+               test_cmp expect actual &&
+
+               test_must_fail git rev-parse :1:y/d &&
+               git rev-parse >actual \
+                       :2:w/d :3:w/d :1:x/d :2:y/d :3:y/d :3:z/d &&
+               git rev-parse >expect \
+                        O:x/d  B:w/d  O:x/d  A:y/d  B:y/d  O:x/d &&
+               test_cmp expect actual &&
+
+               git hash-object >actual \
+                       w/d~HEAD w/d~B^0 z/d &&
+               git rev-parse >expect \
+                       O:x/d    B:w/d   O:x/d &&
+               test_cmp expect actual &&
+               test_path_is_missing x/d &&
+               test_path_is_file y/d &&
+               grep -q "<<<<" y/d  # conflict markers should be present
+       )
+'
+
+# Testcase 5d, Directory/file/file conflict due to directory rename
+#   Commit O: z/{b,c}
+#   Commit A: y/{b,c,d_1}
+#   Commit B: z/{b,c,d_2,f}, y/d/e
+#   Expected: y/{b,c,d/e,f}, z/d_2, CONFLICT(file/directory), y/d_1~HEAD
+#   Note: The fact that y/d/ exists in B makes us bail on directory rename
+#         detection for z/d_2, but that doesn't prevent us from applying the
+#         directory rename detection for z/f -> y/f.
+
+test_expect_success '5d-setup: Directory/file/file conflict due to directory rename' '
+       test_create_repo 5d &&
+       (
+               cd 5d &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               echo d1 >y/d &&
+               git add y/d &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               mkdir -p y/d &&
+               echo e >y/d/e &&
+               echo d2 >z/d &&
+               echo f >z/f &&
+               git add y/d/e z/d z/f &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '5d-check: Directory/file/file conflict due to directory rename' '
+       (
+               cd 5d &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT (file/directory).*y/d" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 6 out &&
+               git ls-files -u >out &&
+               test_line_count = 1 out &&
+               git ls-files -o >out &&
+               test_line_count = 2 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :0:y/c :0:z/d :0:y/f :2:y/d :0:y/d/e &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c  B:z/d  B:z/f  A:y/d  B:y/d/e &&
+               test_cmp expect actual &&
+
+               git hash-object y/d~HEAD >actual &&
+               git rev-parse A:y/d >expect &&
+               test_cmp expect actual
+       )
+'
+
+###########################################################################
+# Rules suggested by section 5:
+#
+#   If a subset of to-be-renamed files have a file or directory in the way,
+#   "turn off" the directory rename for those specific sub-paths, falling
+#   back to old handling.  But, sadly, see testcases 8a and 8b.
+###########################################################################
+
+
+###########################################################################
+# SECTION 6: Same side of the merge was the one that did the rename
+#
+# It may sound obvious that you only want to apply implicit directory
+# renames to directories if the _other_ side of history did the renaming.
+# If you did make an implementation that didn't explicitly enforce this
+# rule, the majority of cases that would fall under this section would
+# also be solved by following the rules from the above sections.  But
+# there are still a few that stick out, so this section covers them just
+# to make sure we also get them right.
+###########################################################################
+
+# Testcase 6a, Tricky rename/delete
+#   Commit O: z/{b,c,d}
+#   Commit A: z/b
+#   Commit B: y/{b,c}, z/d
+#   Expected: y/b, CONFLICT(rename/delete, z/c -> y/c vs. NULL)
+#   Note: We're just checking here that the rename of z/b and z/c to put
+#         them under y/ doesn't accidentally catch z/d and make it look like
+#         it is also involved in a rename/delete conflict.
+
+test_expect_success '6a-setup: Tricky rename/delete' '
+       test_create_repo 6a &&
+       (
+               cd 6a &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               echo d >z/d &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git rm z/c &&
+               git rm z/d &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               mkdir y &&
+               git mv z/b y/ &&
+               git mv z/c y/ &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '6a-check: Tricky rename/delete' '
+       (
+               cd 6a &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT (rename/delete).*z/c.*y/c" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 2 out &&
+               git ls-files -u >out &&
+               test_line_count = 1 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :3:y/c &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 6b, Same rename done on both sides
+#   (Related to testcases 6c and 8e)
+#   Commit O: z/{b,c}
+#   Commit A: y/{b,c}
+#   Commit B: y/{b,c}, z/d
+#   Expected: y/{b,c}, z/d
+#   Note: If we did directory rename detection here, we'd move z/d into y/,
+#         but B did that rename and still decided to put the file into z/,
+#         so we probably shouldn't apply directory rename detection for it.
+
+test_expect_success '6b-setup: Same rename done on both sides' '
+       test_create_repo 6b &&
+       (
+               cd 6b &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv z y &&
+               mkdir z &&
+               echo d >z/d &&
+               git add z/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '6b-check: Same rename done on both sides' '
+       (
+               cd 6b &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:z/d &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    B:z/d &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 6c, Rename only done on same side
+#   (Related to testcases 6b and 8e)
+#   Commit O: z/{b,c}
+#   Commit A: z/{b,c} (no change)
+#   Commit B: y/{b,c}, z/d
+#   Expected: y/{b,c}, z/d
+#   NOTE: Seems obvious, but just checking that the implementation doesn't
+#         "accidentally detect a rename" and give us y/{b,c,d}.
+
+test_expect_success '6c-setup: Rename only done on same side' '
+       test_create_repo 6c &&
+       (
+               cd 6c &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               test_tick &&
+               git commit --allow-empty -m "A" &&
+
+               git checkout B &&
+               git mv z y &&
+               mkdir z &&
+               echo d >z/d &&
+               git add z/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '6c-check: Rename only done on same side' '
+       (
+               cd 6c &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:z/d &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    B:z/d &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 6d, We don't always want transitive renaming
+#   (Related to testcase 1c)
+#   Commit O: z/{b,c}, x/d
+#   Commit A: z/{b,c}, x/d (no change)
+#   Commit B: y/{b,c}, z/d
+#   Expected: y/{b,c}, z/d
+#   NOTE: Again, this seems obvious but just checking that the implementation
+#         doesn't "accidentally detect a rename" and give us y/{b,c,d}.
+
+test_expect_success '6d-setup: We do not always want transitive renaming' '
+       test_create_repo 6d &&
+       (
+               cd 6d &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               mkdir x &&
+               echo d >x/d &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               test_tick &&
+               git commit --allow-empty -m "A" &&
+
+               git checkout B &&
+               git mv z y &&
+               git mv x z &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '6d-check: We do not always want transitive renaming' '
+       (
+               cd 6d &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:z/d &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    O:x/d &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 6e, Add/add from one-side
+#   Commit O: z/{b,c}
+#   Commit A: z/{b,c} (no change)
+#   Commit B: y/{b,c,d_1}, z/d_2
+#   Expected: y/{b,c,d_1}, z/d_2
+#   NOTE: Again, this seems obvious but just checking that the implementation
+#         doesn't "accidentally detect a rename" and give us y/{b,c} +
+#         add/add conflict on y/d_1 vs y/d_2.
+
+test_expect_success '6e-setup: Add/add from one side' '
+       test_create_repo 6e &&
+       (
+               cd 6e &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               test_tick &&
+               git commit --allow-empty -m "A" &&
+
+               git checkout B &&
+               git mv z y &&
+               echo d1 > y/d &&
+               mkdir z &&
+               echo d2 > z/d &&
+               git add y/d z/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '6e-check: Add/add from one side' '
+       (
+               cd 6e &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 4 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:y/d HEAD:z/d &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    B:y/d    B:z/d &&
+               test_cmp expect actual
+       )
+'
+
+###########################################################################
+# Rules suggested by section 6:
+#
+#   Only apply implicit directory renames to directories if the other
+#   side of history is the one doing the renaming.
+###########################################################################
+
+
+###########################################################################
+# SECTION 7: More involved Edge/Corner cases
+#
+# The ruleset we have generated in the above sections seems to provide
+# well-defined merges.  But can we find edge/corner cases that either (a)
+# are harder for users to understand, or (b) have a resolution that is
+# non-intuitive or suboptimal?
+#
+# The testcases in this section dive into cases that I've tried to craft in
+# a way to find some that might be surprising to users or difficult for
+# them to understand (the next section will look at non-intuitive or
+# suboptimal merge results).  Some of the testcases are similar to ones
+# from past sections, but have been simplified to try to highlight error
+# messages using a "modified" path (due to the directory rename).  Are
+# users okay with these?
+#
+# In my opinion, testcases that are difficult to understand from this
+# section is due to difficulty in the testcase rather than the directory
+# renaming (similar to how t6042 and t6036 have difficult resolutions due
+# to the problem setup itself being complex).  And I don't think the
+# error messages are a problem.
+#
+# On the other hand, the testcases in section 8 worry me slightly more...
+###########################################################################
+
+# Testcase 7a, rename-dir vs. rename-dir (NOT split evenly) PLUS add-other-file
+#   Commit O: z/{b,c}
+#   Commit A: y/{b,c}
+#   Commit B: w/b, x/c, z/d
+#   Expected: y/d, CONFLICT(rename/rename for both z/b and z/c)
+#   NOTE: There's a rename of z/ here, y/ has more renames, so z/d -> y/d.
+
+test_expect_success '7a-setup: rename-dir vs. rename-dir (NOT split evenly) PLUS add-other-file' '
+       test_create_repo 7a &&
+       (
+               cd 7a &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               mkdir w &&
+               mkdir x &&
+               git mv z/b w/ &&
+               git mv z/c x/ &&
+               echo d > z/d &&
+               git add z/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '7a-check: rename-dir vs. rename-dir (NOT split evenly) PLUS add-other-file' '
+       (
+               cd 7a &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT (rename/rename).*z/b.*y/b.*w/b" out &&
+               test_i18ngrep "CONFLICT (rename/rename).*z/c.*y/c.*x/c" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 7 out &&
+               git ls-files -u >out &&
+               test_line_count = 6 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       :1:z/b :2:y/b :3:w/b :1:z/c :2:y/c :3:x/c :0:y/d &&
+               git rev-parse >expect \
+                        O:z/b  O:z/b  O:z/b  O:z/c  O:z/c  O:z/c  B:z/d &&
+               test_cmp expect actual &&
+
+               git hash-object >actual \
+                       y/b   w/b   y/c   x/c &&
+               git rev-parse >expect \
+                       O:z/b O:z/b O:z/c O:z/c &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 7b, rename/rename(2to1), but only due to transitive rename
+#   (Related to testcase 1d)
+#   Commit O: z/{b,c},     x/d_1, w/d_2
+#   Commit A: y/{b,c,d_2}, x/d_1
+#   Commit B: z/{b,c,d_1},        w/d_2
+#   Expected: y/{b,c}, CONFLICT(rename/rename(2to1): x/d_1, w/d_2 -> y_d)
+
+test_expect_success '7b-setup: rename/rename(2to1), but only due to transitive rename' '
+       test_create_repo 7b &&
+       (
+               cd 7b &&
+
+               mkdir z &&
+               mkdir x &&
+               mkdir w &&
+               echo b >z/b &&
+               echo c >z/c &&
+               echo d1 > x/d &&
+               echo d2 > w/d &&
+               git add z x w &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               git mv w/d y/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv x/d z/ &&
+               rmdir x &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '7b-check: rename/rename(2to1), but only due to transitive rename' '
+       (
+               cd 7b &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT (rename/rename)" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 4 out &&
+               git ls-files -u >out &&
+               test_line_count = 2 out &&
+               git ls-files -o >out &&
+               test_line_count = 3 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :0:y/c :2:y/d :3:y/d &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c  O:w/d  O:x/d &&
+               test_cmp expect actual &&
+
+               test_path_is_missing y/d &&
+               test_path_is_file y/d~HEAD &&
+               test_path_is_file y/d~B^0 &&
+
+               git hash-object >actual \
+                       y/d~HEAD y/d~B^0 &&
+               git rev-parse >expect \
+                       O:w/d    O:x/d &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 7c, rename/rename(1to...2or3); transitive rename may add complexity
+#   (Related to testcases 3b and 5c)
+#   Commit O: z/{b,c}, x/d
+#   Commit A: y/{b,c}, w/d
+#   Commit B: z/{b,c,d}
+#   Expected: y/{b,c}, CONFLICT(x/d -> w/d vs. y/d)
+#   NOTE: z/ was renamed to y/ so we do want to report
+#         neither CONFLICT(x/d -> w/d vs. z/d)
+#         nor CONFLiCT x/d -> w/d vs. y/d vs. z/d)
+
+test_expect_success '7c-setup: rename/rename(1to...2or3); transitive rename may add complexity' '
+       test_create_repo 7c &&
+       (
+               cd 7c &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               mkdir x &&
+               echo d >x/d &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               git mv x w &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv x/d z/ &&
+               rmdir x &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '7c-check: rename/rename(1to...2or3); transitive rename may add complexity' '
+       (
+               cd 7c &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT (rename/rename).*x/d.*w/d.*y/d" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 5 out &&
+               git ls-files -u >out &&
+               test_line_count = 3 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :0:y/c :1:x/d :2:w/d :3:y/d &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c  O:x/d  O:x/d  O:x/d &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 7d, transitive rename involved in rename/delete; how is it reported?
+#   (Related somewhat to testcases 5b and 8d)
+#   Commit O: z/{b,c}, x/d
+#   Commit A: y/{b,c}
+#   Commit B: z/{b,c,d}
+#   Expected: y/{b,c}, CONFLICT(delete x/d vs rename to y/d)
+#   NOTE: z->y so NOT CONFLICT(delete x/d vs rename to z/d)
+
+test_expect_success '7d-setup: transitive rename involved in rename/delete; how is it reported?' '
+       test_create_repo 7d &&
+       (
+               cd 7d &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               mkdir x &&
+               echo d >x/d &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               git rm -rf x &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv x/d z/ &&
+               rmdir x &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '7d-check: transitive rename involved in rename/delete; how is it reported?' '
+       (
+               cd 7d &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT (rename/delete).*x/d.*y/d" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+               git ls-files -u >out &&
+               test_line_count = 1 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :0:y/c :3:y/d &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c  O:x/d &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 7e, transitive rename in rename/delete AND dirs in the way
+#   (Very similar to 'both rename source and destination involved in D/F conflict' from t6022-merge-rename.sh)
+#   (Also related to testcases 9c and 9d)
+#   Commit O: z/{b,c},     x/d_1
+#   Commit A: y/{b,c,d/g}, x/d/f
+#   Commit B: z/{b,c,d_1}
+#   Expected: rename/delete(x/d_1->y/d_1 vs. None) + D/F conflict on y/d
+#             y/{b,c,d/g}, y/d_1~B^0, x/d/f
+
+#   NOTE: The main path of interest here is d_1 and where it ends up, but
+#         this is actually a case that has two potential directory renames
+#         involved and D/F conflict(s), so it makes sense to walk through
+#         each step.
+#
+#         Commit A renames z/ -> y/.  Thus everything that B adds to z/
+#         should be instead moved to y/.  This gives us the D/F conflict on
+#         y/d because x/d_1 -> z/d_1 -> y/d_1 conflicts with y/d/g.
+#
+#         Further, commit B renames x/ -> z/, thus everything A adds to x/
+#         should instead be moved to z/...BUT we removed z/ and renamed it
+#         to y/, so maybe everything should move not from x/ to z/, but
+#         from x/ to z/ to y/.  Doing so might make sense from the logic so
+#         far, but note that commit A had both an x/ and a y/; it did the
+#         renaming of z/ to y/ and created x/d/f and it clearly made these
+#         things separate, so it doesn't make much sense to push these
+#         together.  Doing so is what I'd call a doubly transitive rename;
+#         see testcases 9c and 9d for further discussion of this issue and
+#         how it's resolved.
+
+test_expect_success '7e-setup: transitive rename in rename/delete AND dirs in the way' '
+       test_create_repo 7e &&
+       (
+               cd 7e &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               mkdir x &&
+               echo d1 >x/d &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               git rm x/d &&
+               mkdir -p x/d &&
+               mkdir -p y/d &&
+               echo f >x/d/f &&
+               echo g >y/d/g &&
+               git add x/d/f y/d/g &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv x/d z/ &&
+               rmdir x &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '7e-check: transitive rename in rename/delete AND dirs in the way' '
+       (
+               cd 7e &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT (rename/delete).*x/d.*y/d" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 5 out &&
+               git ls-files -u >out &&
+               test_line_count = 1 out &&
+               git ls-files -o >out &&
+               test_line_count = 2 out &&
+
+               git rev-parse >actual \
+                       :0:x/d/f :0:y/d/g :0:y/b :0:y/c :3:y/d &&
+               git rev-parse >expect \
+                        A:x/d/f  A:y/d/g  O:z/b  O:z/c  O:x/d &&
+               test_cmp expect actual &&
+
+               git hash-object y/d~B^0 >actual &&
+               git rev-parse O:x/d >expect &&
+               test_cmp expect actual
+       )
+'
+
+###########################################################################
+# SECTION 8: Suboptimal merges
+#
+# As alluded to in the last section, the ruleset we have built up for
+# detecting directory renames unfortunately has some special cases where it
+# results in slightly suboptimal or non-intuitive behavior.  This section
+# explores these cases.
+#
+# To be fair, we already had non-intuitive or suboptimal behavior for most
+# of these cases in git before introducing implicit directory rename
+# detection, but it'd be nice if there was a modified ruleset out there
+# that handled these cases a bit better.
+###########################################################################
+
+# Testcase 8a, Dual-directory rename, one into the others' way
+#   Commit O. x/{a,b},   y/{c,d}
+#   Commit A. x/{a,b,e}, y/{c,d,f}
+#   Commit B. y/{a,b},   z/{c,d}
+#
+# Possible Resolutions:
+#   w/o dir-rename detection: y/{a,b,f},   z/{c,d},   x/e
+#   Currently expected:       y/{a,b,e,f}, z/{c,d}
+#   Optimal:                  y/{a,b,e},   z/{c,d,f}
+#
+# Note: Both x and y got renamed and it'd be nice to detect both, and we do
+# better with directory rename detection than git did without, but the
+# simple rule from section 5 prevents me from handling this as optimally as
+# we potentially could.
+
+test_expect_success '8a-setup: Dual-directory rename, one into the others way' '
+       test_create_repo 8a &&
+       (
+               cd 8a &&
+
+               mkdir x &&
+               mkdir y &&
+               echo a >x/a &&
+               echo b >x/b &&
+               echo c >y/c &&
+               echo d >y/d &&
+               git add x y &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               echo e >x/e &&
+               echo f >y/f &&
+               git add x/e y/f &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv y z &&
+               git mv x y &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '8a-check: Dual-directory rename, one into the others way' '
+       (
+               cd 8a &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 6 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/a HEAD:y/b HEAD:y/e HEAD:y/f HEAD:z/c HEAD:z/d &&
+               git rev-parse >expect \
+                       O:x/a    O:x/b    A:x/e    A:y/f    O:y/c    O:y/d &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 8b, Dual-directory rename, one into the others' way, with conflicting filenames
+#   Commit O. x/{a_1,b_1},     y/{a_2,b_2}
+#   Commit A. x/{a_1,b_1,e_1}, y/{a_2,b_2,e_2}
+#   Commit B. y/{a_1,b_1},     z/{a_2,b_2}
+#
+#   w/o dir-rename detection: y/{a_1,b_1,e_2}, z/{a_2,b_2}, x/e_1
+#   Currently expected:       <same>
+#   Scary:                    y/{a_1,b_1},     z/{a_2,b_2}, CONFLICT(add/add, e_1 vs. e_2)
+#   Optimal:                  y/{a_1,b_1,e_1}, z/{a_2,b_2,e_2}
+#
+# Note: Very similar to 8a, except instead of 'e' and 'f' in directories x and
+# y, both are named 'e'.  Without directory rename detection, neither file
+# moves directories.  Implement directory rename detection suboptimally, and
+# you get an add/add conflict, but both files were added in commit A, so this
+# is an add/add conflict where one side of history added both files --
+# something we can't represent in the index.  Obviously, we'd prefer the last
+# resolution, but our previous rules are too coarse to allow it.  Using both
+# the rules from section 4 and section 5 save us from the Scary resolution,
+# making us fall back to pre-directory-rename-detection behavior for both
+# e_1 and e_2.
+
+test_expect_success '8b-setup: Dual-directory rename, one into the others way, with conflicting filenames' '
+       test_create_repo 8b &&
+       (
+               cd 8b &&
+
+               mkdir x &&
+               mkdir y &&
+               echo a1 >x/a &&
+               echo b1 >x/b &&
+               echo a2 >y/a &&
+               echo b2 >y/b &&
+               git add x y &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               echo e1 >x/e &&
+               echo e2 >y/e &&
+               git add x/e y/e &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv y z &&
+               git mv x y &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '8b-check: Dual-directory rename, one into the others way, with conflicting filenames' '
+       (
+               cd 8b &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 6 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/a HEAD:y/b HEAD:z/a HEAD:z/b HEAD:x/e HEAD:y/e &&
+               git rev-parse >expect \
+                       O:x/a    O:x/b    O:y/a    O:y/b    A:x/e    A:y/e &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 8c, modify/delete or rename+modify/delete?
+#   (Related to testcases 5b, 8d, and 9h)
+#   Commit O: z/{b,c,d}
+#   Commit A: y/{b,c}
+#   Commit B: z/{b,c,d_modified,e}
+#   Expected: y/{b,c,e}, CONFLICT(modify/delete: on z/d)
+#
+#   Note: It could easily be argued that the correct resolution here is
+#         y/{b,c,e}, CONFLICT(rename/delete: z/d -> y/d vs deleted)
+#         and that the modifed version of d should be present in y/ after
+#         the merge, just marked as conflicted.  Indeed, I previously did
+#         argue that.  But applying directory renames to the side of
+#         history where a file is merely modified results in spurious
+#         rename/rename(1to2) conflicts -- see testcase 9h.  See also
+#         notes in 8d.
+
+test_expect_success '8c-setup: modify/delete or rename+modify/delete?' '
+       test_create_repo 8c &&
+       (
+               cd 8c &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               test_seq 1 10 >z/d &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git rm z/d &&
+               git mv z y &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo 11 >z/d &&
+               test_chmod +x z/d &&
+               echo e >z/e &&
+               git add z/d z/e &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '8c-check: modify/delete or rename+modify/delete' '
+       (
+               cd 8c &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               test_i18ngrep "CONFLICT (modify/delete).* z/d" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 5 out &&
+               git ls-files -u >out &&
+               test_line_count = 2 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :0:y/c :0:y/e :1:z/d :3:z/d &&
+               git rev-parse >expect \
+                        O:z/b  O:z/c  B:z/e  O:z/d  B:z/d &&
+               test_cmp expect actual &&
+
+               test_must_fail git rev-parse :2:z/d &&
+               git ls-files -s z/d | grep ^100755 &&
+               test_path_is_file z/d &&
+               test_path_is_missing y/d
+       )
+'
+
+# Testcase 8d, rename/delete...or not?
+#   (Related to testcase 5b; these may appear slightly inconsistent to users;
+#    Also related to testcases 7d and 7e)
+#   Commit O: z/{b,c,d}
+#   Commit A: y/{b,c}
+#   Commit B: z/{b,c,d,e}
+#   Expected: y/{b,c,e}
+#
+#   Note: It would also be somewhat reasonable to resolve this as
+#             y/{b,c,e}, CONFLICT(rename/delete: x/d -> y/d or deleted)
+#
+#   In this case, I'm leaning towards: commit A was the one that deleted z/d
+#   and it did the rename of z to y, so the two "conflicts" (rename vs.
+#   delete) are both coming from commit A, which is illogical.  Conflicts
+#   during merging are supposed to be about opposite sides doing things
+#   differently.
+
+test_expect_success '8d-setup: rename/delete...or not?' '
+       test_create_repo 8d &&
+       (
+               cd 8d &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               test_seq 1 10 >z/d &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git rm z/d &&
+               git mv z y &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo e >z/e &&
+               git add z/e &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '8d-check: rename/delete...or not?' '
+       (
+               cd 8d &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:y/e &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    B:z/e &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 8e, Both sides rename, one side adds to original directory
+#   Commit O: z/{b,c}
+#   Commit A: y/{b,c}
+#   Commit B: w/{b,c}, z/d
+#
+# Possible Resolutions:
+#   w/o dir-rename detection: z/d, CONFLICT(z/b -> y/b vs. w/b),
+#                                  CONFLICT(z/c -> y/c vs. w/c)
+#   Currently expected:       y/d, CONFLICT(z/b -> y/b vs. w/b),
+#                                  CONFLICT(z/c -> y/c vs. w/c)
+#   Optimal:                  ??
+#
+# Notes: In commit A, directory z got renamed to y.  In commit B, directory z
+#        did NOT get renamed; the directory is still present; instead it is
+#        considered to have just renamed a subset of paths in directory z
+#        elsewhere.  Therefore, the directory rename done in commit A to z/
+#        applies to z/d and maps it to y/d.
+#
+#        It's possible that users would get confused about this, but what
+#        should we do instead?  Silently leaving at z/d seems just as bad or
+#        maybe even worse.  Perhaps we could print a big warning about z/d
+#        and how we're moving to y/d in this case, but when I started thinking
+#        about the ramifications of doing that, I didn't know how to rule out
+#        that opening other weird edge and corner cases so I just punted.
+
+test_expect_success '8e-setup: Both sides rename, one side adds to original directory' '
+       test_create_repo 8e &&
+       (
+               cd 8e &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv z w &&
+               mkdir z &&
+               echo d >z/d &&
+               git add z/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '8e-check: Both sides rename, one side adds to original directory' '
+       (
+               cd 8e &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out 2>err &&
+               test_i18ngrep CONFLICT.*rename/rename.*z/c.*y/c.*w/c out &&
+               test_i18ngrep CONFLICT.*rename/rename.*z/b.*y/b.*w/b out &&
+
+               git ls-files -s >out &&
+               test_line_count = 7 out &&
+               git ls-files -u >out &&
+               test_line_count = 6 out &&
+               git ls-files -o >out &&
+               test_line_count = 2 out &&
+
+               git rev-parse >actual \
+                       :1:z/b :2:y/b :3:w/b :1:z/c :2:y/c :3:w/c :0:y/d &&
+               git rev-parse >expect \
+                        O:z/b  O:z/b  O:z/b  O:z/c  O:z/c  O:z/c  B:z/d &&
+               test_cmp expect actual &&
+
+               git hash-object >actual \
+                       y/b   w/b   y/c   w/c &&
+               git rev-parse >expect \
+                       O:z/b O:z/b O:z/c O:z/c &&
+               test_cmp expect actual &&
+
+               test_path_is_missing z/b &&
+               test_path_is_missing z/c
+       )
+'
+
+###########################################################################
+# SECTION 9: Other testcases
+#
+# This section consists of miscellaneous testcases I thought of during
+# the implementation which round out the testing.
+###########################################################################
+
+# Testcase 9a, Inner renamed directory within outer renamed directory
+#   (Related to testcase 1f)
+#   Commit O: z/{b,c,d/{e,f,g}}
+#   Commit A: y/{b,c}, x/w/{e,f,g}
+#   Commit B: z/{b,c,d/{e,f,g,h},i}
+#   Expected: y/{b,c,i}, x/w/{e,f,g,h}
+#   NOTE: The only reason this one is interesting is because when a directory
+#         is split into multiple other directories, we determine by the weight
+#         of which one had the most paths going to it.  A naive implementation
+#         of that could take the new file in commit B at z/i to x/w/i or x/i.
+
+test_expect_success '9a-setup: Inner renamed directory within outer renamed directory' '
+       test_create_repo 9a &&
+       (
+               cd 9a &&
+
+               mkdir -p z/d &&
+               echo b >z/b &&
+               echo c >z/c &&
+               echo e >z/d/e &&
+               echo f >z/d/f &&
+               echo g >z/d/g &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               mkdir x &&
+               git mv z/d x/w &&
+               git mv z y &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo h >z/d/h &&
+               echo i >z/i &&
+               git add z &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '9a-check: Inner renamed directory within outer renamed directory' '
+       (
+               cd 9a &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 7 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:y/i &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    B:z/i &&
+               test_cmp expect actual &&
+
+               git rev-parse >actual \
+                       HEAD:x/w/e HEAD:x/w/f HEAD:x/w/g HEAD:x/w/h &&
+               git rev-parse >expect \
+                       O:z/d/e    O:z/d/f    O:z/d/g    B:z/d/h &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 9b, Transitive rename with content merge
+#   (Related to testcase 1c)
+#   Commit O: z/{b,c},   x/d_1
+#   Commit A: y/{b,c},   x/d_2
+#   Commit B: z/{b,c,d_3}
+#   Expected: y/{b,c,d_merged}
+
+test_expect_success '9b-setup: Transitive rename with content merge' '
+       test_create_repo 9b &&
+       (
+               cd 9b &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               mkdir x &&
+               test_seq 1 10 >x/d &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               test_seq 1 11 >x/d &&
+               git add x/d &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               test_seq 0 10 >x/d &&
+               git mv x/d z/d &&
+               git add z/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '9b-check: Transitive rename with content merge' '
+       (
+               cd 9b &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+
+               test_seq 0 11 >expected &&
+               test_cmp expected y/d &&
+               git add expected &&
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:y/d &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    :0:expected &&
+               test_cmp expect actual &&
+               test_must_fail git rev-parse HEAD:x/d &&
+               test_must_fail git rev-parse HEAD:z/d &&
+               test_path_is_missing z/d &&
+
+               test $(git rev-parse HEAD:y/d) != $(git rev-parse O:x/d) &&
+               test $(git rev-parse HEAD:y/d) != $(git rev-parse A:x/d) &&
+               test $(git rev-parse HEAD:y/d) != $(git rev-parse B:z/d)
+       )
+'
+
+# Testcase 9c, Doubly transitive rename?
+#   (Related to testcase 1c, 7e, and 9d)
+#   Commit O: z/{b,c},     x/{d,e},    w/f
+#   Commit A: y/{b,c},     x/{d,e,f,g}
+#   Commit B: z/{b,c,d,e},             w/f
+#   Expected: y/{b,c,d,e}, x/{f,g}
+#
+#   NOTE: x/f and x/g may be slightly confusing here.  The rename from w/f to
+#         x/f is clear.  Let's look beyond that.  Here's the logic:
+#            Commit B renamed x/ -> z/
+#            Commit A renamed z/ -> y/
+#         So, we could possibly further rename x/f to z/f to y/f, a doubly
+#         transient rename.  However, where does it end?  We can chain these
+#         indefinitely (see testcase 9d).  What if there is a D/F conflict
+#         at z/f/ or y/f/?  Or just another file conflict at one of those
+#         paths?  In the case of an N-long chain of transient renamings,
+#         where do we "abort" the rename at?  Can the user make sense of
+#         the resulting conflict and resolve it?
+#
+#         To avoid this confusion I use the simple rule that if the other side
+#         of history did a directory rename to a path that your side renamed
+#         away, then ignore that particular rename from the other side of
+#         history for any implicit directory renames.
+
+test_expect_success '9c-setup: Doubly transitive rename?' '
+       test_create_repo 9c &&
+       (
+               cd 9c &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               mkdir x &&
+               echo d >x/d &&
+               echo e >x/e &&
+               mkdir w &&
+               echo f >w/f &&
+               git add z x w &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               git mv w/f x/ &&
+               echo g >x/g &&
+               git add x/g &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv x/d z/d &&
+               git mv x/e z/e &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '9c-check: Doubly transitive rename?' '
+       (
+               cd 9c &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 >out &&
+               test_i18ngrep "WARNING: Avoiding applying x -> z rename to x/f" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 6 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:y/d HEAD:y/e HEAD:x/f HEAD:x/g &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    O:x/d    O:x/e    O:w/f    A:x/g &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 9d, N-fold transitive rename?
+#   (Related to testcase 9c...and 1c and 7e)
+#   Commit O: z/a, y/b, x/c, w/d, v/e, u/f
+#   Commit A:  y/{a,b},  w/{c,d},  u/{e,f}
+#   Commit B: z/{a,t}, x/{b,c}, v/{d,e}, u/f
+#   Expected: <see NOTE first>
+#
+#   NOTE: z/ -> y/ (in commit A)
+#         y/ -> x/ (in commit B)
+#         x/ -> w/ (in commit A)
+#         w/ -> v/ (in commit B)
+#         v/ -> u/ (in commit A)
+#         So, if we add a file to z, say z/t, where should it end up?  In u?
+#         What if there's another file or directory named 't' in one of the
+#         intervening directories and/or in u itself?  Also, shouldn't the
+#         same logic that places 't' in u/ also move ALL other files to u/?
+#         What if there are file or directory conflicts in any of them?  If
+#         we attempted to do N-way (N-fold? N-ary? N-uple?) transitive renames
+#         like this, would the user have any hope of understanding any
+#         conflicts or how their working tree ended up?  I think not, so I'm
+#         ruling out N-ary transitive renames for N>1.
+#
+#   Therefore our expected result is:
+#     z/t, y/a, x/b, w/c, u/d, u/e, u/f
+#   The reason that v/d DOES get transitively renamed to u/d is that u/ isn't
+#   renamed somewhere.  A slightly sub-optimal result, but it uses fairly
+#   simple rules that are consistent with what we need for all the other
+#   testcases and simplifies things for the user.
+
+test_expect_success '9d-setup: N-way transitive rename?' '
+       test_create_repo 9d &&
+       (
+               cd 9d &&
+
+               mkdir z y x w v u &&
+               echo a >z/a &&
+               echo b >y/b &&
+               echo c >x/c &&
+               echo d >w/d &&
+               echo e >v/e &&
+               echo f >u/f &&
+               git add z y x w v u &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z/a y/ &&
+               git mv x/c w/ &&
+               git mv v/e u/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo t >z/t &&
+               git mv y/b x/ &&
+               git mv w/d v/ &&
+               git add z/t &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '9d-check: N-way transitive rename?' '
+       (
+               cd 9d &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 >out &&
+               test_i18ngrep "WARNING: Avoiding applying z -> y rename to z/t" out &&
+               test_i18ngrep "WARNING: Avoiding applying y -> x rename to y/a" out &&
+               test_i18ngrep "WARNING: Avoiding applying x -> w rename to x/b" out &&
+               test_i18ngrep "WARNING: Avoiding applying w -> v rename to w/c" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 7 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       HEAD:z/t \
+                       HEAD:y/a HEAD:x/b HEAD:w/c \
+                       HEAD:u/d HEAD:u/e HEAD:u/f &&
+               git rev-parse >expect \
+                       B:z/t    \
+                       O:z/a    O:y/b    O:x/c    \
+                       O:w/d    O:v/e    A:u/f &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 9e, N-to-1 whammo
+#   (Related to testcase 9c...and 1c and 7e)
+#   Commit O: dir1/{a,b}, dir2/{d,e}, dir3/{g,h}, dirN/{j,k}
+#   Commit A: dir1/{a,b,c,yo}, dir2/{d,e,f,yo}, dir3/{g,h,i,yo}, dirN/{j,k,l,yo}
+#   Commit B: combined/{a,b,d,e,g,h,j,k}
+#   Expected: combined/{a,b,c,d,e,f,g,h,i,j,k,l}, CONFLICT(Nto1) warnings,
+#             dir1/yo, dir2/yo, dir3/yo, dirN/yo
+
+test_expect_success '9e-setup: N-to-1 whammo' '
+       test_create_repo 9e &&
+       (
+               cd 9e &&
+
+               mkdir dir1 dir2 dir3 dirN &&
+               echo a >dir1/a &&
+               echo b >dir1/b &&
+               echo d >dir2/d &&
+               echo e >dir2/e &&
+               echo g >dir3/g &&
+               echo h >dir3/h &&
+               echo j >dirN/j &&
+               echo k >dirN/k &&
+               git add dir* &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               echo c  >dir1/c &&
+               echo yo >dir1/yo &&
+               echo f  >dir2/f &&
+               echo yo >dir2/yo &&
+               echo i  >dir3/i &&
+               echo yo >dir3/yo &&
+               echo l  >dirN/l &&
+               echo yo >dirN/yo &&
+               git add dir* &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv dir1 combined &&
+               git mv dir2/* combined/ &&
+               git mv dir3/* combined/ &&
+               git mv dirN/* combined/ &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success C_LOCALE_OUTPUT '9e-check: N-to-1 whammo' '
+       (
+               cd 9e &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 >out &&
+               grep "CONFLICT (implicit dir rename): Cannot map more than one path to combined/yo" out >error_line &&
+               grep -q dir1/yo error_line &&
+               grep -q dir2/yo error_line &&
+               grep -q dir3/yo error_line &&
+               grep -q dirN/yo error_line &&
+
+               git ls-files -s >out &&
+               test_line_count = 16 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 2 out &&
+
+               git rev-parse >actual \
+                       :0:combined/a :0:combined/b :0:combined/c \
+                       :0:combined/d :0:combined/e :0:combined/f \
+                       :0:combined/g :0:combined/h :0:combined/i \
+                       :0:combined/j :0:combined/k :0:combined/l &&
+               git rev-parse >expect \
+                        O:dir1/a      O:dir1/b      A:dir1/c \
+                        O:dir2/d      O:dir2/e      A:dir2/f \
+                        O:dir3/g      O:dir3/h      A:dir3/i \
+                        O:dirN/j      O:dirN/k      A:dirN/l &&
+               test_cmp expect actual &&
+
+               git rev-parse >actual \
+                       :0:dir1/yo :0:dir2/yo :0:dir3/yo :0:dirN/yo &&
+               git rev-parse >expect \
+                        A:dir1/yo  A:dir2/yo  A:dir3/yo  A:dirN/yo &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 9f, Renamed directory that only contained immediate subdirs
+#   (Related to testcases 1e & 9g)
+#   Commit O: goal/{a,b}/$more_files
+#   Commit A: priority/{a,b}/$more_files
+#   Commit B: goal/{a,b}/$more_files, goal/c
+#   Expected: priority/{a,b}/$more_files, priority/c
+
+test_expect_success '9f-setup: Renamed directory that only contained immediate subdirs' '
+       test_create_repo 9f &&
+       (
+               cd 9f &&
+
+               mkdir -p goal/a &&
+               mkdir -p goal/b &&
+               echo foo >goal/a/foo &&
+               echo bar >goal/b/bar &&
+               echo baz >goal/b/baz &&
+               git add goal &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv goal/ priority &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo c >goal/c &&
+               git add goal/c &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '9f-check: Renamed directory that only contained immediate subdirs' '
+       (
+               cd 9f &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 4 out &&
+
+               git rev-parse >actual \
+                       HEAD:priority/a/foo \
+                       HEAD:priority/b/bar \
+                       HEAD:priority/b/baz \
+                       HEAD:priority/c &&
+               git rev-parse >expect \
+                       O:goal/a/foo \
+                       O:goal/b/bar \
+                       O:goal/b/baz \
+                       B:goal/c &&
+               test_cmp expect actual &&
+               test_must_fail git rev-parse HEAD:goal/c
+       )
+'
+
+# Testcase 9g, Renamed directory that only contained immediate subdirs, immediate subdirs renamed
+#   (Related to testcases 1e & 9f)
+#   Commit O: goal/{a,b}/$more_files
+#   Commit A: priority/{alpha,bravo}/$more_files
+#   Commit B: goal/{a,b}/$more_files, goal/c
+#   Expected: priority/{alpha,bravo}/$more_files, priority/c
+
+test_expect_success '9g-setup: Renamed directory that only contained immediate subdirs, immediate subdirs renamed' '
+       test_create_repo 9g &&
+       (
+               cd 9g &&
+
+               mkdir -p goal/a &&
+               mkdir -p goal/b &&
+               echo foo >goal/a/foo &&
+               echo bar >goal/b/bar &&
+               echo baz >goal/b/baz &&
+               git add goal &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               mkdir priority &&
+               git mv goal/a/ priority/alpha &&
+               git mv goal/b/ priority/beta &&
+               rmdir goal/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo c >goal/c &&
+               git add goal/c &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_failure '9g-check: Renamed directory that only contained immediate subdirs, immediate subdirs renamed' '
+       (
+               cd 9g &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 4 out &&
+
+               git rev-parse >actual \
+                       HEAD:priority/alpha/foo \
+                       HEAD:priority/beta/bar  \
+                       HEAD:priority/beta/baz  \
+                       HEAD:priority/c &&
+               git rev-parse >expect \
+                       O:goal/a/foo \
+                       O:goal/b/bar \
+                       O:goal/b/baz \
+                       B:goal/c &&
+               test_cmp expect actual &&
+               test_must_fail git rev-parse HEAD:goal/c
+       )
+'
+
+# Testcase 9h, Avoid implicit rename if involved as source on other side
+#   (Extremely closely related to testcase 3a)
+#   Commit O: z/{b,c,d_1}
+#   Commit A: z/{b,c,d_2}
+#   Commit B: y/{b,c}, x/d_1
+#   Expected: y/{b,c}, x/d_2
+#   NOTE: If we applied the z/ -> y/ rename to z/d, then we'd end up with
+#         a rename/rename(1to2) conflict (z/d -> y/d vs. x/d)
+test_expect_success '9h-setup: Avoid dir rename on merely modified path' '
+       test_create_repo 9h &&
+       (
+               cd 9h &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               printf "1\n2\n3\n4\n5\n6\n7\n8\nd\n" >z/d &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               test_tick &&
+               echo more >>z/d &&
+               git add z/d &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               mkdir y &&
+               mkdir x &&
+               git mv z/b y/ &&
+               git mv z/c y/ &&
+               git mv z/d x/ &&
+               rmdir z &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '9h-check: Avoid dir rename on merely modified path' '
+       (
+               cd 9h &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:x/d &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    A:z/d &&
+               test_cmp expect actual
+       )
+'
+
+###########################################################################
+# Rules suggested by section 9:
+#
+#   If the other side of history did a directory rename to a path that your
+#   side renamed away, then ignore that particular rename from the other
+#   side of history for any implicit directory renames.
+###########################################################################
+
+###########################################################################
+# SECTION 10: Handling untracked files
+#
+# unpack_trees(), upon which the recursive merge algorithm is based, aborts
+# the operation if untracked or dirty files would be deleted or overwritten
+# by the merge.  Unfortunately, unpack_trees() does not understand renames,
+# and if it doesn't abort, then it muddies up the working directory before
+# we even get to the point of detecting renames, so we need some special
+# handling, at least in the case of directory renames.
+###########################################################################
+
+# Testcase 10a, Overwrite untracked: normal rename/delete
+#   Commit O: z/{b,c_1}
+#   Commit A: z/b + untracked z/c + untracked z/d
+#   Commit B: z/{b,d_1}
+#   Expected: Aborted Merge +
+#       ERROR_MSG(untracked working tree files would be overwritten by merge)
+
+test_expect_success '10a-setup: Overwrite untracked with normal rename/delete' '
+       test_create_repo 10a &&
+       (
+               cd 10a &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git rm z/c &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv z/c z/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '10a-check: Overwrite untracked with normal rename/delete' '
+       (
+               cd 10a &&
+
+               git checkout A^0 &&
+               echo very >z/c &&
+               echo important >z/d &&
+
+               test_must_fail git merge -s recursive B^0 >out 2>err &&
+               test_i18ngrep "The following untracked working tree files would be overwritten by merge" err &&
+
+               git ls-files -s >out &&
+               test_line_count = 1 out &&
+               git ls-files -o >out &&
+               test_line_count = 4 out &&
+
+               echo very >expect &&
+               test_cmp expect z/c &&
+
+               echo important >expect &&
+               test_cmp expect z/d &&
+
+               git rev-parse HEAD:z/b >actual &&
+               git rev-parse O:z/b >expect &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 10b, Overwrite untracked: dir rename + delete
+#   Commit O: z/{b,c_1}
+#   Commit A: y/b + untracked y/{c,d,e}
+#   Commit B: z/{b,d_1,e}
+#   Expected: Failed Merge; y/b + untracked y/c + untracked y/d on disk +
+#             z/c_1 -> z/d_1 rename recorded at stage 3 for y/d +
+#       ERROR_MSG(refusing to lose untracked file at 'y/d')
+
+test_expect_success '10b-setup: Overwrite untracked with dir rename + delete' '
+       test_create_repo 10b &&
+       (
+               cd 10b &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git rm z/c &&
+               git mv z/ y/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv z/c z/d &&
+               echo e >z/e &&
+               git add z/e &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '10b-check: Overwrite untracked with dir rename + delete' '
+       (
+               cd 10b &&
+
+               git checkout A^0 &&
+               echo very >y/c &&
+               echo important >y/d &&
+               echo contents >y/e &&
+
+               test_must_fail git merge -s recursive B^0 >out 2>err &&
+               test_i18ngrep "CONFLICT (rename/delete).*Version B\^0 of y/d left in tree at y/d~B\^0" out &&
+               test_i18ngrep "Error: Refusing to lose untracked file at y/e; writing to y/e~B\^0 instead" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+               git ls-files -u >out &&
+               test_line_count = 2 out &&
+               git ls-files -o >out &&
+               test_line_count = 5 out &&
+
+               git rev-parse >actual \
+                       :0:y/b :3:y/d :3:y/e &&
+               git rev-parse >expect \
+                       O:z/b  O:z/c  B:z/e &&
+               test_cmp expect actual &&
+
+               echo very >expect &&
+               test_cmp expect y/c &&
+
+               echo important >expect &&
+               test_cmp expect y/d &&
+
+               echo contents >expect &&
+               test_cmp expect y/e
+       )
+'
+
+# Testcase 10c, Overwrite untracked: dir rename/rename(1to2)
+#   Commit O: z/{a,b}, x/{c,d}
+#   Commit A: y/{a,b}, w/c, x/d + different untracked y/c
+#   Commit B: z/{a,b,c}, x/d
+#   Expected: Failed Merge; y/{a,b} + x/d + untracked y/c +
+#             CONFLICT(rename/rename) x/c -> w/c vs y/c +
+#             y/c~B^0 +
+#             ERROR_MSG(Refusing to lose untracked file at y/c)
+
+test_expect_success '10c-setup: Overwrite untracked with dir rename/rename(1to2)' '
+       test_create_repo 10c &&
+       (
+               cd 10c &&
+
+               mkdir z x &&
+               echo a >z/a &&
+               echo b >z/b &&
+               echo c >x/c &&
+               echo d >x/d &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               mkdir w &&
+               git mv x/c w/c &&
+               git mv z/ y/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv x/c z/ &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2)' '
+       (
+               cd 10c &&
+
+               git checkout A^0 &&
+               echo important >y/c &&
+
+               test_must_fail git merge -s recursive B^0 >out 2>err &&
+               test_i18ngrep "CONFLICT (rename/rename)" out &&
+               test_i18ngrep "Refusing to lose untracked file at y/c; adding as y/c~B\^0 instead" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 6 out &&
+               git ls-files -u >out &&
+               test_line_count = 3 out &&
+               git ls-files -o >out &&
+               test_line_count = 3 out &&
+
+               git rev-parse >actual \
+                       :0:y/a :0:y/b :0:x/d :1:x/c :2:w/c :3:y/c &&
+               git rev-parse >expect \
+                        O:z/a  O:z/b  O:x/d  O:x/c  O:x/c  O:x/c &&
+               test_cmp expect actual &&
+
+               git hash-object y/c~B^0 >actual &&
+               git rev-parse O:x/c >expect &&
+               test_cmp expect actual &&
+
+               echo important >expect &&
+               test_cmp expect y/c
+       )
+'
+
+# Testcase 10d, Delete untracked w/ dir rename/rename(2to1)
+#   Commit O: z/{a,b,c_1},        x/{d,e,f_2}
+#   Commit A: y/{a,b},            x/{d,e,f_2,wham_1} + untracked y/wham
+#   Commit B: z/{a,b,c_1,wham_2}, y/{d,e}
+#   Expected: Failed Merge; y/{a,b,d,e} + untracked y/{wham,wham~B^0,wham~HEAD}+
+#             CONFLICT(rename/rename) z/c_1 vs x/f_2 -> y/wham
+#             ERROR_MSG(Refusing to lose untracked file at y/wham)
+
+test_expect_success '10d-setup: Delete untracked with dir rename/rename(2to1)' '
+       test_create_repo 10d &&
+       (
+               cd 10d &&
+
+               mkdir z x &&
+               echo a >z/a &&
+               echo b >z/b &&
+               echo c >z/c &&
+               echo d >x/d &&
+               echo e >x/e &&
+               echo f >x/f &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z/c x/wham &&
+               git mv z/ y/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv x/f z/wham &&
+               git mv x/ y/ &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' '
+       (
+               cd 10d &&
+
+               git checkout A^0 &&
+               echo important >y/wham &&
+
+               test_must_fail git merge -s recursive B^0 >out 2>err &&
+               test_i18ngrep "CONFLICT (rename/rename)" out &&
+               test_i18ngrep "Refusing to lose untracked file at y/wham" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 6 out &&
+               git ls-files -u >out &&
+               test_line_count = 2 out &&
+               git ls-files -o >out &&
+               test_line_count = 4 out &&
+
+               git rev-parse >actual \
+                       :0:y/a :0:y/b :0:y/d :0:y/e :2:y/wham :3:y/wham &&
+               git rev-parse >expect \
+                        O:z/a  O:z/b  O:x/d  O:x/e  O:z/c     O:x/f &&
+               test_cmp expect actual &&
+
+               test_must_fail git rev-parse :1:y/wham &&
+
+               echo important >expect &&
+               test_cmp expect y/wham &&
+
+               git hash-object >actual \
+                       y/wham~B^0 y/wham~HEAD &&
+               git rev-parse >expect \
+                       O:x/f      O:z/c &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 10e, Does git complain about untracked file that's not in the way?
+#   Commit O: z/{a,b}
+#   Commit A: y/{a,b} + untracked z/c
+#   Commit B: z/{a,b,c}
+#   Expected: y/{a,b,c} + untracked z/c
+
+test_expect_success '10e-setup: Does git complain about untracked file that is not really in the way?' '
+       test_create_repo 10e &&
+       (
+               cd 10e &&
+
+               mkdir z &&
+               echo a >z/a &&
+               echo b >z/b &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z/ y/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo c >z/c &&
+               git add z/c &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_failure '10e-check: Does git complain about untracked file that is not really in the way?' '
+       (
+               cd 10e &&
+
+               git checkout A^0 &&
+               mkdir z &&
+               echo random >z/c &&
+
+               git merge -s recursive B^0 >out 2>err &&
+               test_i18ngrep ! "following untracked working tree files would be overwritten by merge" err &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 3 out &&
+
+               git rev-parse >actual \
+                       :0:y/a :0:y/b :0:y/c &&
+               git rev-parse >expect \
+                        O:z/a  O:z/b  B:z/c &&
+               test_cmp expect actual &&
+
+               echo random >expect &&
+               test_cmp expect z/c
+       )
+'
+
+###########################################################################
+# SECTION 11: Handling dirty (not up-to-date) files
+#
+# unpack_trees(), upon which the recursive merge algorithm is based, aborts
+# the operation if untracked or dirty files would be deleted or overwritten
+# by the merge.  Unfortunately, unpack_trees() does not understand renames,
+# and if it doesn't abort, then it muddies up the working directory before
+# we even get to the point of detecting renames, so we need some special
+# handling.  This was true even of normal renames, but there are additional
+# codepaths that need special handling with directory renames.  Add
+# testcases for both renamed-by-directory-rename-detection and standard
+# rename cases.
+###########################################################################
+
+# Testcase 11a, Avoid losing dirty contents with simple rename
+#   Commit O: z/{a,b_v1},
+#   Commit A: z/{a,c_v1}, and z/c_v1 has uncommitted mods
+#   Commit B: z/{a,b_v2}
+#   Expected: ERROR_MSG(Refusing to lose dirty file at z/c) +
+#             z/a, staged version of z/c has sha1sum matching B:z/b_v2,
+#             z/c~HEAD with contents of B:z/b_v2,
+#             z/c with uncommitted mods on top of A:z/c_v1
+
+test_expect_success '11a-setup: Avoid losing dirty contents with simple rename' '
+       test_create_repo 11a &&
+       (
+               cd 11a &&
+
+               mkdir z &&
+               echo a >z/a &&
+               test_seq 1 10 >z/b &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z/b z/c &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo 11 >>z/b &&
+               git add z/b &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '11a-check: Avoid losing dirty contents with simple rename' '
+       (
+               cd 11a &&
+
+               git checkout A^0 &&
+               echo stuff >>z/c &&
+
+               test_must_fail git merge -s recursive B^0 >out 2>err &&
+               test_i18ngrep "Refusing to lose dirty file at z/c" out &&
+
+               test_seq 1 10 >expected &&
+               echo stuff >>expected &&
+               test_cmp expected z/c &&
+
+               git ls-files -s >out &&
+               test_line_count = 2 out &&
+               git ls-files -u >out &&
+               test_line_count = 1 out &&
+               git ls-files -o >out &&
+               test_line_count = 4 out &&
+
+               git rev-parse >actual \
+                       :0:z/a :2:z/c &&
+               git rev-parse >expect \
+                        O:z/a  B:z/b &&
+               test_cmp expect actual &&
+
+               git hash-object z/c~HEAD >actual &&
+               git rev-parse B:z/b >expect &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 11b, Avoid losing dirty file involved in directory rename
+#   Commit O: z/a,         x/{b,c_v1}
+#   Commit A: z/{a,c_v1},  x/b,       and z/c_v1 has uncommitted mods
+#   Commit B: y/a,         x/{b,c_v2}
+#   Expected: y/{a,c_v2}, x/b, z/c_v1 with uncommitted mods untracked,
+#             ERROR_MSG(Refusing to lose dirty file at z/c)
+
+
+test_expect_success '11b-setup: Avoid losing dirty file involved in directory rename' '
+       test_create_repo 11b &&
+       (
+               cd 11b &&
+
+               mkdir z x &&
+               echo a >z/a &&
+               echo b >x/b &&
+               test_seq 1 10 >x/c &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv x/c z/c &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv z y &&
+               echo 11 >>x/c &&
+               git add x/c &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '11b-check: Avoid losing dirty file involved in directory rename' '
+       (
+               cd 11b &&
+
+               git checkout A^0 &&
+               echo stuff >>z/c &&
+
+               git merge -s recursive B^0 >out 2>err &&
+               test_i18ngrep "Refusing to lose dirty file at z/c" out &&
+
+               grep -q stuff z/c &&
+               test_seq 1 10 >expected &&
+               echo stuff >>expected &&
+               test_cmp expected z/c &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -m >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 4 out &&
+
+               git rev-parse >actual \
+                       :0:x/b :0:y/a :0:y/c &&
+               git rev-parse >expect \
+                        O:x/b  O:z/a  B:x/c &&
+               test_cmp expect actual &&
+
+               git hash-object y/c >actual &&
+               git rev-parse B:x/c >expect &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 11c, Avoid losing not-up-to-date with rename + D/F conflict
+#   Commit O: y/a,         x/{b,c_v1}
+#   Commit A: y/{a,c_v1},  x/b,       and y/c_v1 has uncommitted mods
+#   Commit B: y/{a,c/d},   x/{b,c_v2}
+#   Expected: Abort_msg("following files would be overwritten by merge") +
+#             y/c left untouched (still has uncommitted mods)
+
+test_expect_success '11c-setup: Avoid losing not-uptodate with rename + D/F conflict' '
+       test_create_repo 11c &&
+       (
+               cd 11c &&
+
+               mkdir y x &&
+               echo a >y/a &&
+               echo b >x/b &&
+               test_seq 1 10 >x/c &&
+               git add y x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv x/c y/c &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               mkdir y/c &&
+               echo d >y/c/d &&
+               echo 11 >>x/c &&
+               git add x/c y/c/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '11c-check: Avoid losing not-uptodate with rename + D/F conflict' '
+       (
+               cd 11c &&
+
+               git checkout A^0 &&
+               echo stuff >>y/c &&
+
+               test_must_fail git merge -s recursive B^0 >out 2>err &&
+               test_i18ngrep "following files would be overwritten by merge" err &&
+
+               grep -q stuff y/c &&
+               test_seq 1 10 >expected &&
+               echo stuff >>expected &&
+               test_cmp expected y/c &&
+
+               git ls-files -s >out &&
+               test_line_count = 3 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -m >out &&
+               test_line_count = 1 out &&
+               git ls-files -o >out &&
+               test_line_count = 3 out
+       )
+'
+
+# Testcase 11d, Avoid losing not-up-to-date with rename + D/F conflict
+#   Commit O: z/a,         x/{b,c_v1}
+#   Commit A: z/{a,c_v1},  x/b,       and z/c_v1 has uncommitted mods
+#   Commit B: y/{a,c/d},   x/{b,c_v2}
+#   Expected: D/F: y/c_v2 vs y/c/d) +
+#             Warning_Msg("Refusing to lose dirty file at z/c) +
+#             y/{a,c~HEAD,c/d}, x/b, now-untracked z/c_v1 with uncommitted mods
+
+test_expect_success '11d-setup: Avoid losing not-uptodate with rename + D/F conflict' '
+       test_create_repo 11d &&
+       (
+               cd 11d &&
+
+               mkdir z x &&
+               echo a >z/a &&
+               echo b >x/b &&
+               test_seq 1 10 >x/c &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv x/c z/c &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv z y &&
+               mkdir y/c &&
+               echo d >y/c/d &&
+               echo 11 >>x/c &&
+               git add x/c y/c/d &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '11d-check: Avoid losing not-uptodate with rename + D/F conflict' '
+       (
+               cd 11d &&
+
+               git checkout A^0 &&
+               echo stuff >>z/c &&
+
+               test_must_fail git merge -s recursive B^0 >out 2>err &&
+               test_i18ngrep "Refusing to lose dirty file at z/c" out &&
+
+               grep -q stuff z/c &&
+               test_seq 1 10 >expected &&
+               echo stuff >>expected &&
+               test_cmp expected z/c
+
+               git ls-files -s >out &&
+               test_line_count = 4 out &&
+               git ls-files -u >out &&
+               test_line_count = 1 out &&
+               git ls-files -o >out &&
+               test_line_count = 5 out &&
+
+               git rev-parse >actual \
+                       :0:x/b :0:y/a :0:y/c/d :3:y/c &&
+               git rev-parse >expect \
+                        O:x/b  O:z/a  B:y/c/d  B:x/c &&
+               test_cmp expect actual &&
+
+               git hash-object y/c~HEAD >actual &&
+               git rev-parse B:x/c >expect &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 11e, Avoid deleting not-up-to-date with dir rename/rename(1to2)/add
+#   Commit O: z/{a,b},      x/{c_1,d}
+#   Commit A: y/{a,b,c_2},  x/d, w/c_1, and y/c_2 has uncommitted mods
+#   Commit B: z/{a,b,c_1},  x/d
+#   Expected: Failed Merge; y/{a,b} + x/d +
+#             CONFLICT(rename/rename) x/c_1 -> w/c_1 vs y/c_1 +
+#             ERROR_MSG(Refusing to lose dirty file at y/c)
+#             y/c~B^0 has O:x/c_1 contents
+#             y/c~HEAD has A:y/c_2 contents
+#             y/c has dirty file from before merge
+
+test_expect_success '11e-setup: Avoid deleting not-uptodate with dir rename/rename(1to2)/add' '
+       test_create_repo 11e &&
+       (
+               cd 11e &&
+
+               mkdir z x &&
+               echo a >z/a &&
+               echo b >z/b &&
+               echo c >x/c &&
+               echo d >x/d &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z/ y/ &&
+               echo different >y/c &&
+               mkdir w &&
+               git mv x/c w/ &&
+               git add y/c &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv x/c z/ &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rename(1to2)/add' '
+       (
+               cd 11e &&
+
+               git checkout A^0 &&
+               echo mods >>y/c &&
+
+               test_must_fail git merge -s recursive B^0 >out 2>err &&
+               test_i18ngrep "CONFLICT (rename/rename)" out &&
+               test_i18ngrep "Refusing to lose dirty file at y/c" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 7 out &&
+               git ls-files -u >out &&
+               test_line_count = 4 out &&
+               git ls-files -o >out &&
+               test_line_count = 4 out &&
+
+               echo different >expected &&
+               echo mods >>expected &&
+               test_cmp expected y/c &&
+
+               git rev-parse >actual \
+                       :0:y/a :0:y/b :0:x/d :1:x/c :2:w/c :2:y/c :3:y/c &&
+               git rev-parse >expect \
+                        O:z/a  O:z/b  O:x/d  O:x/c  O:x/c  A:y/c  O:x/c &&
+               test_cmp expect actual &&
+
+               git hash-object >actual \
+                       y/c~B^0 y/c~HEAD &&
+               git rev-parse >expect \
+                       O:x/c   A:y/c &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 11f, Avoid deleting not-up-to-date w/ dir rename/rename(2to1)
+#   Commit O: z/{a,b},        x/{c_1,d_2}
+#   Commit A: y/{a,b,wham_1}, x/d_2, except y/wham has uncommitted mods
+#   Commit B: z/{a,b,wham_2}, x/c_1
+#   Expected: Failed Merge; y/{a,b} + untracked y/{wham~B^0,wham~B^HEAD} +
+#             y/wham with dirty changes from before merge +
+#             CONFLICT(rename/rename) x/c vs x/d -> y/wham
+#             ERROR_MSG(Refusing to lose dirty file at y/wham)
+
+test_expect_success '11f-setup: Avoid deleting not-uptodate with dir rename/rename(2to1)' '
+       test_create_repo 11f &&
+       (
+               cd 11f &&
+
+               mkdir z x &&
+               echo a >z/a &&
+               echo b >z/b &&
+               test_seq 1 10 >x/c &&
+               echo d >x/d &&
+               git add z x &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z/ y/ &&
+               git mv x/c y/wham &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv x/d z/wham &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '11f-check: Avoid deleting not-uptodate with dir rename/rename(2to1)' '
+       (
+               cd 11f &&
+
+               git checkout A^0 &&
+               echo important >>y/wham &&
+
+               test_must_fail git merge -s recursive B^0 >out 2>err &&
+               test_i18ngrep "CONFLICT (rename/rename)" out &&
+               test_i18ngrep "Refusing to lose dirty file at y/wham" out &&
+
+               git ls-files -s >out &&
+               test_line_count = 4 out &&
+               git ls-files -u >out &&
+               test_line_count = 2 out &&
+               git ls-files -o >out &&
+               test_line_count = 4 out &&
+
+               test_seq 1 10 >expected &&
+               echo important >>expected &&
+               test_cmp expected y/wham &&
+
+               test_must_fail git rev-parse :1:y/wham &&
+               git hash-object >actual \
+                       y/wham~B^0 y/wham~HEAD &&
+               git rev-parse >expect \
+                       O:x/d      O:x/c &&
+               test_cmp expect actual &&
+
+               git rev-parse >actual \
+                       :0:y/a :0:y/b :2:y/wham :3:y/wham &&
+               git rev-parse >expect \
+                        O:z/a  O:z/b  O:x/c     O:x/d &&
+               test_cmp expect actual
+       )
+'
+
+###########################################################################
+# SECTION 12: Everything else
+#
+# Tests suggested by others.  Tests added after implementation completed
+# and submitted.  Grab bag.
+###########################################################################
+
+# Testcase 12a, Moving one directory hierarchy into another
+#   (Related to testcase 9a)
+#   Commit O: node1/{leaf1,leaf2}, node2/{leaf3,leaf4}
+#   Commit A: node1/{leaf1,leaf2,node2/{leaf3,leaf4}}
+#   Commit B: node1/{leaf1,leaf2,leaf5}, node2/{leaf3,leaf4,leaf6}
+#   Expected: node1/{leaf1,leaf2,leaf5,node2/{leaf3,leaf4,leaf6}}
+
+test_expect_success '12a-setup: Moving one directory hierarchy into another' '
+       test_create_repo 12a &&
+       (
+               cd 12a &&
+
+               mkdir -p node1 node2 &&
+               echo leaf1 >node1/leaf1 &&
+               echo leaf2 >node1/leaf2 &&
+               echo leaf3 >node2/leaf3 &&
+               echo leaf4 >node2/leaf4 &&
+               git add node1 node2 &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv node2/ node1/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               echo leaf5 >node1/leaf5 &&
+               echo leaf6 >node2/leaf6 &&
+               git add node1 node2 &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '12a-check: Moving one directory hierarchy into another' '
+       (
+               cd 12a &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 6 out &&
+
+               git rev-parse >actual \
+                       HEAD:node1/leaf1 HEAD:node1/leaf2 HEAD:node1/leaf5 \
+                       HEAD:node1/node2/leaf3 \
+                       HEAD:node1/node2/leaf4 \
+                       HEAD:node1/node2/leaf6 &&
+               git rev-parse >expect \
+                       O:node1/leaf1    O:node1/leaf2    B:node1/leaf5 \
+                       O:node2/leaf3 \
+                       O:node2/leaf4 \
+                       B:node2/leaf6 &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 12b, Moving two directory hierarchies into each other
+#   (Related to testcases 1c and 12c)
+#   Commit O: node1/{leaf1, leaf2}, node2/{leaf3, leaf4}
+#   Commit A: node1/{leaf1, leaf2, node2/{leaf3, leaf4}}
+#   Commit B: node2/{leaf3, leaf4, node1/{leaf1, leaf2}}
+#   Expected: node1/node2/node1/{leaf1, leaf2},
+#             node2/node1/node2/{leaf3, leaf4}
+#   NOTE: Without directory renames, we would expect
+#                   node2/node1/{leaf1, leaf2},
+#                   node1/node2/{leaf3, leaf4}
+#         with directory rename detection, we note that
+#             commit A renames node2/ -> node1/node2/
+#             commit B renames node1/ -> node2/node1/
+#         therefore, applying those directory renames to the initial result
+#         (making all four paths experience a transitive renaming), yields
+#         the expected result.
+#
+#         You may ask, is it weird to have two directories rename each other?
+#         To which, I can do no more than shrug my shoulders and say that
+#         even simple rules give weird results when given weird inputs.
+
+test_expect_success '12b-setup: Moving one directory hierarchy into another' '
+       test_create_repo 12b &&
+       (
+               cd 12b &&
+
+               mkdir -p node1 node2 &&
+               echo leaf1 >node1/leaf1 &&
+               echo leaf2 >node1/leaf2 &&
+               echo leaf3 >node2/leaf3 &&
+               echo leaf4 >node2/leaf4 &&
+               git add node1 node2 &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv node2/ node1/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv node1/ node2/ &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '12b-check: Moving one directory hierarchy into another' '
+       (
+               cd 12b &&
+
+               git checkout A^0 &&
+
+               git merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 4 out &&
+
+               git rev-parse >actual \
+                       HEAD:node1/node2/node1/leaf1 \
+                       HEAD:node1/node2/node1/leaf2 \
+                       HEAD:node2/node1/node2/leaf3 \
+                       HEAD:node2/node1/node2/leaf4 &&
+               git rev-parse >expect \
+                       O:node1/leaf1 \
+                       O:node1/leaf2 \
+                       O:node2/leaf3 \
+                       O:node2/leaf4 &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 12c, Moving two directory hierarchies into each other w/ content merge
+#   (Related to testcase 12b)
+#   Commit O: node1/{       leaf1_1, leaf2_1}, node2/{leaf3_1, leaf4_1}
+#   Commit A: node1/{       leaf1_2, leaf2_2,  node2/{leaf3_2, leaf4_2}}
+#   Commit B: node2/{node1/{leaf1_3, leaf2_3},        leaf3_3, leaf4_3}
+#   Expected: Content merge conflicts for each of:
+#               node1/node2/node1/{leaf1, leaf2},
+#               node2/node1/node2/{leaf3, leaf4}
+#   NOTE: This is *exactly* like 12c, except that every path is modified on
+#         each side of the merge.
+
+test_expect_success '12c-setup: Moving one directory hierarchy into another w/ content merge' '
+       test_create_repo 12c &&
+       (
+               cd 12c &&
+
+               mkdir -p node1 node2 &&
+               printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf1\n" >node1/leaf1 &&
+               printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf2\n" >node1/leaf2 &&
+               printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf3\n" >node2/leaf3 &&
+               printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf4\n" >node2/leaf4 &&
+               git add node1 node2 &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv node2/ node1/ &&
+               for i in `git ls-files`; do echo side A >>$i; done &&
+               git add -u &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv node1/ node2/ &&
+               for i in `git ls-files`; do echo side B >>$i; done &&
+               git add -u &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '12c-check: Moving one directory hierarchy into another w/ content merge' '
+       (
+               cd 12c &&
+
+               git checkout A^0 &&
+
+               test_must_fail git merge -s recursive B^0 &&
+
+               git ls-files -u >out &&
+               test_line_count = 12 out &&
+
+               git rev-parse >actual \
+                       :1:node1/node2/node1/leaf1 \
+                       :1:node1/node2/node1/leaf2 \
+                       :1:node2/node1/node2/leaf3 \
+                       :1:node2/node1/node2/leaf4 \
+                       :2:node1/node2/node1/leaf1 \
+                       :2:node1/node2/node1/leaf2 \
+                       :2:node2/node1/node2/leaf3 \
+                       :2:node2/node1/node2/leaf4 \
+                       :3:node1/node2/node1/leaf1 \
+                       :3:node1/node2/node1/leaf2 \
+                       :3:node2/node1/node2/leaf3 \
+                       :3:node2/node1/node2/leaf4 &&
+               git rev-parse >expect \
+                       O:node1/leaf1 \
+                       O:node1/leaf2 \
+                       O:node2/leaf3 \
+                       O:node2/leaf4 \
+                       A:node1/leaf1 \
+                       A:node1/leaf2 \
+                       A:node1/node2/leaf3 \
+                       A:node1/node2/leaf4 \
+                       B:node2/node1/leaf1 \
+                       B:node2/node1/leaf2 \
+                       B:node2/leaf3 \
+                       B:node2/leaf4 &&
+               test_cmp expect actual
+       )
+'
+
+test_done
diff --git a/t/t6046-merge-skip-unneeded-updates.sh b/t/t6046-merge-skip-unneeded-updates.sh
new file mode 100755 (executable)
index 0000000..fcefffc
--- /dev/null
@@ -0,0 +1,761 @@
+#!/bin/sh
+
+test_description="merge cases"
+
+# The setup for all of them, pictorially, is:
+#
+#      A
+#      o
+#     / \
+#  O o   ?
+#     \ /
+#      o
+#      B
+#
+# To help make it easier to follow the flow of tests, they have been
+# divided into sections and each test will start with a quick explanation
+# of what commits O, A, and B contain.
+#
+# Notation:
+#    z/{b,c}   means  files z/b and z/c both exist
+#    x/d_1     means  file x/d exists with content d1.  (Purpose of the
+#                     underscore notation is to differentiate different
+#                     files that might be renamed into each other's paths.)
+
+. ./test-lib.sh
+
+
+###########################################################################
+# SECTION 1: Cases involving no renames (one side has subset of changes of
+#            the other side)
+###########################################################################
+
+# Testcase 1a, Changes on A, subset of changes on B
+#   Commit O: b_1
+#   Commit A: b_2
+#   Commit B: b_3
+#   Expected: b_2
+
+test_expect_success '1a-setup: Modify(A)/Modify(B), change on B subset of A' '
+       test_create_repo 1a &&
+       (
+               cd 1a &&
+
+               test_write_lines 1 2 3 4 5 6 7 8 9 10 >b &&
+               git add b &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b &&
+               git add b &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b &&
+               git add b &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '1a-check-L: Modify(A)/Modify(B), change on B subset of A' '
+       test_when_finished "git -C 1a reset --hard" &&
+       test_when_finished "git -C 1a clean -fd" &&
+       (
+               cd 1a &&
+
+               git checkout A^0 &&
+
+               test-tool chmtime =31337 b &&
+               test-tool chmtime -v +0 b >expected-mtime &&
+
+               GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
+
+               test_i18ngrep "Skipped b" out &&
+               test_must_be_empty err &&
+
+               test-tool chmtime -v +0 b >actual-mtime &&
+               test_cmp expected-mtime actual-mtime &&
+
+               git ls-files -s >index_files &&
+               test_line_count = 1 index_files &&
+
+               git rev-parse >actual HEAD:b &&
+               git rev-parse >expect A:b &&
+               test_cmp expect actual &&
+
+               git hash-object b   >actual &&
+               git rev-parse   A:b >expect &&
+               test_cmp expect actual
+       )
+'
+
+test_expect_success '1a-check-R: Modify(A)/Modify(B), change on B subset of A' '
+       test_when_finished "git -C 1a reset --hard" &&
+       test_when_finished "git -C 1a clean -fd" &&
+       (
+               cd 1a &&
+
+               git checkout B^0 &&
+
+               GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err &&
+
+               test_i18ngrep "Auto-merging b" out &&
+               test_must_be_empty err &&
+
+               git ls-files -s >index_files &&
+               test_line_count = 1 index_files &&
+
+               git rev-parse >actual HEAD:b &&
+               git rev-parse >expect A:b &&
+               test_cmp expect actual &&
+
+               git hash-object b   >actual &&
+               git rev-parse   A:b >expect &&
+               test_cmp expect actual
+       )
+'
+
+
+###########################################################################
+# SECTION 2: Cases involving basic renames
+###########################################################################
+
+# Testcase 2a, Changes on A, rename on B
+#   Commit O: b_1
+#   Commit A: b_2
+#   Commit B: c_1
+#   Expected: c_2
+
+test_expect_success '2a-setup: Modify(A)/rename(B)' '
+       test_create_repo 2a &&
+       (
+               cd 2a &&
+
+               test_seq 1 10 >b &&
+               git add b &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               test_seq 1 11 >b &&
+               git add b &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv b c &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '2a-check-L: Modify/rename, merge into modify side' '
+       test_when_finished "git -C 2a reset --hard" &&
+       test_when_finished "git -C 2a clean -fd" &&
+       (
+               cd 2a &&
+
+               git checkout A^0 &&
+
+               GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
+
+               test_i18ngrep ! "Skipped c" out &&
+               test_must_be_empty err &&
+
+               git ls-files -s >index_files &&
+               test_line_count = 1 index_files &&
+
+               git rev-parse >actual HEAD:c &&
+               git rev-parse >expect A:b &&
+               test_cmp expect actual &&
+
+               git hash-object c   >actual &&
+               git rev-parse   A:b >expect &&
+               test_cmp expect actual &&
+
+               test_must_fail git rev-parse HEAD:b &&
+               test_path_is_missing b
+       )
+'
+
+test_expect_success '2a-check-R: Modify/rename, merge into rename side' '
+       test_when_finished "git -C 2a reset --hard" &&
+       test_when_finished "git -C 2a clean -fd" &&
+       (
+               cd 2a &&
+
+               git checkout B^0 &&
+
+               GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err &&
+
+               test_i18ngrep ! "Skipped c" out &&
+               test_must_be_empty err &&
+
+               git ls-files -s >index_files &&
+               test_line_count = 1 index_files &&
+
+               git rev-parse >actual HEAD:c &&
+               git rev-parse >expect A:b &&
+               test_cmp expect actual &&
+
+               git hash-object c   >actual &&
+               git rev-parse   A:b >expect &&
+               test_cmp expect actual &&
+
+               test_must_fail git rev-parse HEAD:b &&
+               test_path_is_missing b
+       )
+'
+
+# Testcase 2b, Changed and renamed on A, subset of changes on B
+#   Commit O: b_1
+#   Commit A: c_2
+#   Commit B: b_3
+#   Expected: c_2
+
+test_expect_success '2b-setup: Rename+Mod(A)/Mod(B), B mods subset of A' '
+       test_create_repo 2b &&
+       (
+               cd 2b &&
+
+               test_write_lines 1 2 3 4 5 6 7 8 9 10 >b &&
+               git add b &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b &&
+               git add b &&
+               git mv b c &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b &&
+               git add b &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '2b-check-L: Rename+Mod(A)/Mod(B), B mods subset of A' '
+       test_when_finished "git -C 2b reset --hard" &&
+       test_when_finished "git -C 2b clean -fd" &&
+       (
+               cd 2b &&
+
+               git checkout A^0 &&
+
+               test-tool chmtime =31337 c &&
+               test-tool chmtime -v +0 c >expected-mtime &&
+
+               GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
+
+               test_i18ngrep "Skipped c" out &&
+               test_must_be_empty err &&
+
+               test-tool chmtime -v +0 c >actual-mtime &&
+               test_cmp expected-mtime actual-mtime &&
+
+               git ls-files -s >index_files &&
+               test_line_count = 1 index_files &&
+
+               git rev-parse >actual HEAD:c &&
+               git rev-parse >expect A:c &&
+               test_cmp expect actual &&
+
+               git hash-object c   >actual &&
+               git rev-parse   A:c >expect &&
+               test_cmp expect actual &&
+
+               test_must_fail git rev-parse HEAD:b &&
+               test_path_is_missing b
+       )
+'
+
+test_expect_success '2b-check-R: Rename+Mod(A)/Mod(B), B mods subset of A' '
+       test_when_finished "git -C 2b reset --hard" &&
+       test_when_finished "git -C 2b clean -fd" &&
+       (
+               cd 2b &&
+
+               git checkout B^0 &&
+
+               GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err &&
+
+               test_i18ngrep "Auto-merging c" out &&
+               test_must_be_empty err &&
+
+               git ls-files -s >index_files &&
+               test_line_count = 1 index_files &&
+
+               git rev-parse >actual HEAD:c &&
+               git rev-parse >expect A:c &&
+               test_cmp expect actual &&
+
+               git hash-object c   >actual &&
+               git rev-parse   A:c >expect &&
+               test_cmp expect actual &&
+
+               test_must_fail git rev-parse HEAD:b &&
+               test_path_is_missing b
+       )
+'
+
+# Testcase 2c, Changes on A, rename on B
+#   Commit O: b_1
+#   Commit A: b_2, c_3
+#   Commit B: c_1
+#   Expected: rename/add conflict c_2 vs c_3
+#
+#   NOTE: Since A modified b_1->b_2, and B renamed b_1->c_1, the threeway
+#         merge of those files should result in c_2.  We then should have a
+#         rename/add conflict between c_2 and c_3.  However, if we note in
+#         merge_content() that A had the right contents (b_2 has same
+#         contents as c_2, just at a different name), and that A had the
+#         right path present (c_3 existed) and thus decides that it can
+#         skip the update, then we're in trouble.  This test verifies we do
+#         not make that particular mistake.
+
+test_expect_success '2c-setup: Modify b & add c VS rename b->c' '
+       test_create_repo 2c &&
+       (
+               cd 2c &&
+
+               test_seq 1 10 >b &&
+               git add b &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               test_seq 1 11 >b &&
+               echo whatever >c &&
+               git add b c &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv b c &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '2c-check: Modify b & add c VS rename b->c' '
+       (
+               cd 2c &&
+
+               git checkout A^0 &&
+
+               GIT_MERGE_VERBOSITY=3 test_must_fail git merge -s recursive B^0 >out 2>err &&
+
+               test_i18ngrep "CONFLICT (rename/add): Rename b->c" out &&
+               test_i18ngrep ! "Skipped c" out &&
+               test_must_be_empty err
+
+               # FIXME: rename/add conflicts are horribly broken right now;
+               # when I get back to my patch series fixing it and
+               # rename/rename(2to1) conflicts to bring them in line with
+               # how add/add conflicts behave, then checks like the below
+               # could be added.  But that patch series is waiting until
+               # the rename-directory-detection series lands, which this
+               # is part of.  And in the mean time, I do not want to further
+               # enforce broken behavior.  So for now, the main test is the
+               # one above that err is an empty file.
+
+               #git ls-files -s >index_files &&
+               #test_line_count = 2 index_files &&
+
+               #git rev-parse >actual :2:c :3:c &&
+               #git rev-parse >expect A:b  A:c  &&
+               #test_cmp expect actual &&
+
+               #git cat-file -p A:b >>merged &&
+               #git cat-file -p A:c >>merge-me &&
+               #>empty &&
+               #test_must_fail git merge-file \
+               #       -L "Temporary merge branch 1" \
+               #       -L "" \
+               #       -L "Temporary merge branch 2" \
+               #       merged empty merge-me &&
+               #sed -e "s/^\([<=>]\)/\1\1\1/" merged >merged-internal &&
+
+               #git hash-object c               >actual &&
+               #git hash-object merged-internal >expect &&
+               #test_cmp expect actual &&
+
+               #test_path_is_missing b
+       )
+'
+
+
+###########################################################################
+# SECTION 3: Cases involving directory renames
+#
+# NOTE:
+#   Directory renames only apply when one side renames a directory, and the
+#   other side adds or renames a path into that directory.  Applying the
+#   directory rename to that new path creates a new pathname that didn't
+#   exist on either side of history.  Thus, it is impossible for the
+#   merge contents to already be at the right path, so all of these checks
+#   exist just to make sure that updates are not skipped.
+###########################################################################
+
+# Testcase 3a, Change + rename into dir foo on A, dir rename foo->bar on B
+#   Commit O: bq_1, foo/whatever
+#   Commit A: foo/{bq_2, whatever}
+#   Commit B: bq_1, bar/whatever
+#   Expected: bar/{bq_2, whatever}
+
+test_expect_success '3a-setup: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
+       test_create_repo 3a &&
+       (
+               cd 3a &&
+
+               mkdir foo &&
+               test_seq 1 10 >bq &&
+               test_write_lines a b c d e f g h i j k >foo/whatever &&
+               git add bq foo/whatever &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               test_seq 1 11 >bq &&
+               git add bq &&
+               git mv bq foo/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv foo/ bar/ &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '3a-check-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
+       test_when_finished "git -C 3a reset --hard" &&
+       test_when_finished "git -C 3a clean -fd" &&
+       (
+               cd 3a &&
+
+               git checkout A^0 &&
+
+               GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
+
+               test_i18ngrep ! "Skipped bar/bq" out &&
+               test_must_be_empty err &&
+
+               git ls-files -s >index_files &&
+               test_line_count = 2 index_files &&
+
+               git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever &&
+               git rev-parse >expect A:foo/bq    A:foo/whatever &&
+               test_cmp expect actual &&
+
+               git hash-object bar/bq   bar/whatever   >actual &&
+               git rev-parse   A:foo/bq A:foo/whatever >expect &&
+               test_cmp expect actual &&
+
+               test_must_fail git rev-parse HEAD:bq HEAD:foo/bq &&
+               test_path_is_missing bq foo/bq foo/whatever
+       )
+'
+
+test_expect_success '3a-check-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
+       test_when_finished "git -C 3a reset --hard" &&
+       test_when_finished "git -C 3a clean -fd" &&
+       (
+               cd 3a &&
+
+               git checkout B^0 &&
+
+               GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err &&
+
+               test_i18ngrep ! "Skipped bar/bq" out &&
+               test_must_be_empty err &&
+
+               git ls-files -s >index_files &&
+               test_line_count = 2 index_files &&
+
+               git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever &&
+               git rev-parse >expect A:foo/bq    A:foo/whatever &&
+               test_cmp expect actual &&
+
+               git hash-object bar/bq   bar/whatever   >actual &&
+               git rev-parse   A:foo/bq A:foo/whatever >expect &&
+               test_cmp expect actual &&
+
+               test_must_fail git rev-parse HEAD:bq HEAD:foo/bq &&
+               test_path_is_missing bq foo/bq foo/whatever
+       )
+'
+
+# Testcase 3b, rename into dir foo on A, dir rename foo->bar + change on B
+#   Commit O: bq_1, foo/whatever
+#   Commit A: foo/{bq_1, whatever}
+#   Commit B: bq_2, bar/whatever
+#   Expected: bar/{bq_2, whatever}
+
+test_expect_success '3b-setup: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
+       test_create_repo 3b &&
+       (
+               cd 3b &&
+
+               mkdir foo &&
+               test_seq 1 10 >bq &&
+               test_write_lines a b c d e f g h i j k >foo/whatever &&
+               git add bq foo/whatever &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv bq foo/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               test_seq 1 11 >bq &&
+               git add bq &&
+               git mv foo/ bar/ &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '3b-check-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
+       test_when_finished "git -C 3b reset --hard" &&
+       test_when_finished "git -C 3b clean -fd" &&
+       (
+               cd 3b &&
+
+               git checkout A^0 &&
+
+               GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
+
+               test_i18ngrep ! "Skipped bar/bq" out &&
+               test_must_be_empty err &&
+
+               git ls-files -s >index_files &&
+               test_line_count = 2 index_files &&
+
+               git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever &&
+               git rev-parse >expect B:bq        A:foo/whatever &&
+               test_cmp expect actual &&
+
+               git hash-object bar/bq bar/whatever   >actual &&
+               git rev-parse   B:bq   A:foo/whatever >expect &&
+               test_cmp expect actual &&
+
+               test_must_fail git rev-parse HEAD:bq HEAD:foo/bq &&
+               test_path_is_missing bq foo/bq foo/whatever
+       )
+'
+
+test_expect_success '3b-check-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
+       test_when_finished "git -C 3b reset --hard" &&
+       test_when_finished "git -C 3b clean -fd" &&
+       (
+               cd 3b &&
+
+               git checkout B^0 &&
+
+               GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err &&
+
+               test_i18ngrep ! "Skipped bar/bq" out &&
+               test_must_be_empty err &&
+
+               git ls-files -s >index_files &&
+               test_line_count = 2 index_files &&
+
+               git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever &&
+               git rev-parse >expect B:bq        A:foo/whatever &&
+               test_cmp expect actual &&
+
+               git hash-object bar/bq bar/whatever   >actual &&
+               git rev-parse   B:bq   A:foo/whatever >expect &&
+               test_cmp expect actual &&
+
+               test_must_fail git rev-parse HEAD:bq HEAD:foo/bq &&
+               test_path_is_missing bq foo/bq foo/whatever
+       )
+'
+
+###########################################################################
+# SECTION 4: Cases involving dirty changes
+###########################################################################
+
+# Testcase 4a, Changed on A, subset of changes on B, locally modified
+#   Commit O: b_1
+#   Commit A: b_2
+#   Commit B: b_3
+#   Working copy: b_4
+#   Expected: b_2 for merge, b_4 in working copy
+
+test_expect_success '4a-setup: Change on A, change on B subset of A, dirty mods present' '
+       test_create_repo 4a &&
+       (
+               cd 4a &&
+
+               test_write_lines 1 2 3 4 5 6 7 8 9 10 >b &&
+               git add b &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b &&
+               git add b &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b &&
+               git add b &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+# NOTE: For as long as we continue using unpack_trees() without index_only
+#   set to true, it will error out on a case like this claiming the the locally
+#   modified file would be overwritten by the merge.  Getting this testcase
+#   correct requires doing the merge in-memory first, then realizing that no
+#   updates to the file are necessary, and thus that we can just leave the path
+#   alone.
+test_expect_failure '4a-check: Change on A, change on B subset of A, dirty mods present' '
+       test_when_finished "git -C 4a reset --hard" &&
+       test_when_finished "git -C 4a clean -fd" &&
+       (
+               cd 4a &&
+
+               git checkout A^0 &&
+               echo "File rewritten" >b &&
+
+               test-tool chmtime =31337 b &&
+               test-tool chmtime -v +0 b >expected-mtime &&
+
+               GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
+
+               test_i18ngrep "Skipped b" out &&
+               test_must_be_empty err &&
+
+               test-tool chmtime -v +0 b >actual-mtime &&
+               test_cmp expected-mtime actual-mtime &&
+
+               git ls-files -s >index_files &&
+               test_line_count = 1 index_files &&
+
+               git rev-parse >actual :0:b &&
+               git rev-parse >expect A:b &&
+               test_cmp expect actual &&
+
+               git hash-object b >actual &&
+               echo "File rewritten" | git hash-object --stdin >expect &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 4b, Changed+renamed on A, subset of changes on B, locally modified
+#   Commit O: b_1
+#   Commit A: c_2
+#   Commit B: b_3
+#   Working copy: c_4
+#   Expected: c_2
+
+test_expect_success '4b-setup: Rename+Mod(A)/Mod(B), change on B subset of A, dirty mods present' '
+       test_create_repo 4b &&
+       (
+               cd 4b &&
+
+               test_write_lines 1 2 3 4 5 6 7 8 9 10 >b &&
+               git add b &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b &&
+               git add b &&
+               git mv b c &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b &&
+               git add b &&
+               test_tick &&
+               git commit -m "B"
+       )
+'
+
+test_expect_success '4b-check: Rename+Mod(A)/Mod(B), change on B subset of A, dirty mods present' '
+       test_when_finished "git -C 4b reset --hard" &&
+       test_when_finished "git -C 4b clean -fd" &&
+       (
+               cd 4b &&
+
+               git checkout A^0 &&
+               echo "File rewritten" >c &&
+
+               test-tool chmtime =31337 c &&
+               test-tool chmtime -v +0 c >expected-mtime &&
+
+               GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
+
+               test_i18ngrep "Skipped c" out &&
+               test_must_be_empty err &&
+
+               test-tool chmtime -v +0 c >actual-mtime &&
+               test_cmp expected-mtime actual-mtime &&
+
+               git ls-files -s >index_files &&
+               test_line_count = 1 index_files &&
+
+               git rev-parse >actual :0:c &&
+               git rev-parse >expect A:c &&
+               test_cmp expect actual &&
+
+               git hash-object c >actual &&
+               echo "File rewritten" | git hash-object --stdin >expect &&
+               test_cmp expect actual &&
+
+               test_must_fail git rev-parse HEAD:b &&
+               test_path_is_missing b
+       )
+'
+
+test_done
index c630aba657e9c78b6dee4a72be34ed3bd03c01ee..aa3e24963967ba4be2394ceeab26f8317fb31b4b 100755 (executable)
@@ -4,8 +4,6 @@
 #
 test_description='Tests replace refs functionality'
 
-exec </dev/null
-
 . ./test-lib.sh
 . "$TEST_DIRECTORY/lib-gpg.sh"
 
@@ -444,4 +442,32 @@ test_expect_success GPG '--graft on a commit with a mergetag' '
        git replace -d $HASH10
 '
 
+test_expect_success '--convert-graft-file' '
+       git checkout -b with-graft-file &&
+       test_commit root2 &&
+       git reset --hard root2^ &&
+       test_commit root1 &&
+       test_commit after-root1 &&
+       test_tick &&
+       git merge -m merge-root2 root2 &&
+
+       : add and convert graft file &&
+       printf "%s\n%s %s\n\n# comment\n%s\n" \
+               $(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \
+               >.git/info/grafts &&
+       git replace --convert-graft-file &&
+       test_path_is_missing .git/info/grafts &&
+
+       : verify that the history is now "grafted" &&
+       git rev-list HEAD >out &&
+       test_line_count = 4 out &&
+
+       : create invalid graft file and verify that it is not deleted &&
+       test_when_finished "rm -f .git/info/grafts" &&
+       echo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts &&
+       test_must_fail git replace --convert-graft-file 2>err &&
+       test_i18ngrep "$EMPTY_BLOB $EMPTY_TREE" err &&
+       test_i18ngrep "$EMPTY_BLOB $EMPTY_TREE" .git/info/grafts
+'
+
 test_done
index 8c617981a3a06c1ff49f03a2914bfdc31c0e293c..7683e4a1142a6ee31a1f1fd51b774b19fc2c7401 100755 (executable)
@@ -214,4 +214,12 @@ test_expect_success 'rev-list merge^-1x (garbage after ^-1)' '
        test_must_fail git rev-list merge^-1x
 '
 
+test_expect_success 'rev-parse $garbage^@ does not segfault' '
+       test_must_fail git rev-parse $EMPTY_TREE^@
+'
+
+test_expect_success 'rev-parse $garbage...$garbage does not segfault' '
+       test_must_fail git rev-parse $EMPTY_TREE...$EMPTY_BLOB
+'
+
 test_done
index 32474c23d3e53beda92477c5f126322901f68c53..4244638285bbf97a7e5f47e4efcd4bd3717cf49f 100755 (executable)
@@ -20,7 +20,7 @@ note () {
 }
 
 unnote () {
-       git name-rev --tags --stdin | sed -e "s|$_x40 (tags/\([^)]*\))\([       ]\)|\1\2|g"
+       git name-rev --tags --stdin | sed -e "s|$OID_REGEX (tags/\([^)]*\))\([  ]\)|\1\2|g"
 }
 
 test_expect_success setup '
index bae78c4e89e2402d5ab37e3b7cd094dde41d69b3..84dd1cb6907d583ec4019f1bb5b949865beac6cb 100755 (executable)
@@ -383,7 +383,7 @@ test_expect_success 'describe complains about tree object' '
 '
 
 test_expect_success 'describe complains about missing object' '
-       test_must_fail git describe $_z40
+       test_must_fail git describe $ZERO_OID
 '
 
 test_done
index 295d1475bde0151df65e0098e6e4a169193906ed..48379aa0ee874b0250e2348d47eded09713a0a79 100755 (executable)
@@ -310,7 +310,7 @@ 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" &&
+       long="long format -- $ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID" &&
        echo $long >expected &&
        git for-each-ref --format="%(authordate:format:$long)" refs/heads >actual &&
        test_cmp expected actual
index c734ce2388a79d15692875eae81e323c63ad2300..49cc65bb58dd2b81de8f6eebd39ed1649752709d 100755 (executable)
@@ -4,7 +4,7 @@ test_description='for-each-ref errors for broken refs'
 
 . ./test-lib.sh
 
-ZEROS=$_z40
+ZEROS=$ZERO_OID
 MISSING=abababababababababababababababababababab
 
 test_expect_success setup '
index d5255dd5760389fbbabf1c2ffc787500e4184758..818435f04e49b50965cd674bf046eb3d494fa939 100755 (executable)
@@ -5,6 +5,13 @@ test_description='basic git gc tests
 
 . ./test-lib.sh
 
+test_expect_success 'setup' '
+       # do not let the amount of physical memory affects gc
+       # behavior, make sure we always pack everything to one pack by
+       # default
+       git config gc.bigPackThreshold 2g
+'
+
 test_expect_success 'gc empty repository' '
        git gc
 '
@@ -43,6 +50,31 @@ test_expect_success 'gc is not aborted due to a stale symref' '
        )
 '
 
+test_expect_success 'gc --keep-largest-pack' '
+       test_create_repo keep-pack &&
+       (
+               cd keep-pack &&
+               test_commit one &&
+               test_commit two &&
+               test_commit three &&
+               git gc &&
+               ( cd .git/objects/pack && ls *.pack ) >pack-list &&
+               test_line_count = 1 pack-list &&
+               BASE_PACK=.git/objects/pack/pack-*.pack &&
+               test_commit four &&
+               git repack -d &&
+               test_commit five &&
+               git repack -d &&
+               ( cd .git/objects/pack && ls *.pack ) >pack-list &&
+               test_line_count = 3 pack-list &&
+               git gc --keep-largest-pack &&
+               ( cd .git/objects/pack && ls *.pack ) >pack-list &&
+               test_line_count = 2 pack-list &&
+               test_path_is_file $BASE_PACK &&
+               git fsck
+       )
+'
+
 test_expect_success 'auto gc with too many loose objects does not attempt to create bitmaps' '
        test_config gc.auto 3 &&
        test_config gc.autodetach false &&
index e3f1e014aaa07444445cbb1d50d8fdc76e1b4aa5..d7b319e919c83ca677737840f70075c173364209 100755 (executable)
@@ -363,7 +363,7 @@ test_expect_success 'tag -l <pattern> -l <pattern> works, as our buggy documenta
 '
 
 test_expect_success 'listing tags in column' '
-       COLUMNS=40 git tag -l --column=row >actual &&
+       COLUMNS=41 git tag -l --column=row >actual &&
        cat >expected <<\EOF &&
 a1      aa1     cba     t210    t211
 v0.2.1  v1.0    v1.0.1  v1.1.3
index 29e5043b9452b9c32a0d8ad437fb33745b296ac6..b2ca77b3384c97954991eae9c5af89c4d8d8035e 100755 (executable)
@@ -111,14 +111,8 @@ do
        '
 done
 
-if echo 'echo space > "$1"' > "e space.sh"
-then
-       # FS supports spaces in filenames
-       test_set_prereq SPACES_IN_FILENAMES
-fi
-
-test_expect_success SPACES_IN_FILENAMES 'editor with a space' '
-
+test_expect_success 'editor with a space' '
+       echo "echo space >\$1" >"e space.sh" &&
        chmod a+x "e space.sh" &&
        GIT_EDITOR="./e\ space.sh" git commit --amend &&
        test space = "$(git show -s --pretty=format:%s)"
@@ -126,7 +120,7 @@ test_expect_success SPACES_IN_FILENAMES 'editor with a space' '
 '
 
 unset GIT_EDITOR
-test_expect_success SPACES_IN_FILENAMES 'core.editor with a space' '
+test_expect_success 'core.editor with a space' '
 
        git config core.editor \"./e\ space.sh\" &&
        git commit --amend &&
index a8d9ec498778b5a597fd5751f811c8bf0b0ab926..9ba9f24ad2fb4bd2c38a722e62577789fe36f09a 100755 (executable)
@@ -12,7 +12,7 @@ test_expect_success 'setup: base commits' '
 test_expect_success 'setup: a commit with a bogus null sha1 in the tree' '
        {
                git ls-tree HEAD &&
-               printf "160000 commit $_z40\\tbroken\\n"
+               printf "160000 commit $ZERO_OID\\tbroken\\n"
        } >broken-tree &&
        echo "add broken entry" >msg &&
 
index 84f41451ec4e34b6438313545ca886fbef75f34d..37525cae3af303dd60531226d885c72725b0ae53 100755 (executable)
@@ -118,7 +118,7 @@ test_expect_success 'grep with skip-worktree file' '
        test "$(git grep --no-ext-grep test)" = "1:test"
 '
 
-echo ":000000 100644 $_z40 $EMPTY_BLOB A       1" > expected
+echo ":000000 100644 $ZERO_OID $EMPTY_BLOB A   1" > expected
 test_expect_success 'diff-index does not examine skip-worktree absent entries' '
        setup_absent &&
        git diff-index HEAD -- 1 > result &&
index 8f795327a00f6c1b751b82e7e395c8387543d388..b9a86d3347acb4051a212e2c9e3573a2c6e9fe7a 100755 (executable)
@@ -46,11 +46,11 @@ test_expect_success 'before initial commit, things added' '
        cat >expect <<-EOF &&
        # branch.oid (initial)
        # branch.head master
-       1 A. N... 000000 100644 100644 $_z40 $OID_A dir1/file_a
-       1 A. N... 000000 100644 100644 $_z40 $OID_B dir1/file_b
-       1 A. N... 000000 100644 100644 $_z40 $OID_X file_x
-       1 A. N... 000000 100644 100644 $_z40 $OID_Y file_y
-       1 A. N... 000000 100644 100644 $_z40 $OID_Z file_z
+       1 A. N... 000000 100644 100644 $ZERO_OID $OID_A dir1/file_a
+       1 A. N... 000000 100644 100644 $ZERO_OID $OID_B dir1/file_b
+       1 A. N... 000000 100644 100644 $ZERO_OID $OID_X file_x
+       1 A. N... 000000 100644 100644 $ZERO_OID $OID_Y file_y
+       1 A. N... 000000 100644 100644 $ZERO_OID $OID_Z file_z
        ? actual
        ? expect
        EOF
@@ -63,11 +63,11 @@ test_expect_success 'before initial commit, things added (-z)' '
        lf_to_nul >expect <<-EOF &&
        # branch.oid (initial)
        # branch.head master
-       1 A. N... 000000 100644 100644 $_z40 $OID_A dir1/file_a
-       1 A. N... 000000 100644 100644 $_z40 $OID_B dir1/file_b
-       1 A. N... 000000 100644 100644 $_z40 $OID_X file_x
-       1 A. N... 000000 100644 100644 $_z40 $OID_Y file_y
-       1 A. N... 000000 100644 100644 $_z40 $OID_Z file_z
+       1 A. N... 000000 100644 100644 $ZERO_OID $OID_A dir1/file_a
+       1 A. N... 000000 100644 100644 $ZERO_OID $OID_B dir1/file_b
+       1 A. N... 000000 100644 100644 $ZERO_OID $OID_X file_x
+       1 A. N... 000000 100644 100644 $ZERO_OID $OID_Y file_y
+       1 A. N... 000000 100644 100644 $ZERO_OID $OID_Z file_z
        ? actual
        ? expect
        EOF
@@ -128,7 +128,7 @@ test_expect_success 'after first commit, stage existing changes' '
        # branch.oid $H0
        # branch.head master
        1 M. N... 100644 100644 100644 $OID_X $OID_X1 file_x
-       1 D. N... 100644 000000 000000 $OID_Z $_z40 file_z
+       1 D. N... 100644 000000 000000 $OID_Z $ZERO_OID file_z
        ? actual
        ? expect
        EOF
@@ -145,7 +145,7 @@ test_expect_success 'rename causes 2 path lines' '
        # branch.oid $H0
        # branch.head master
        1 M. N... 100644 100644 100644 $OID_X $OID_X1 file_x
-       1 D. N... 100644 000000 000000 $OID_Z $_z40 file_z
+       1 D. N... 100644 000000 000000 $OID_Z $ZERO_OID file_z
        2 R. N... 100644 100644 100644 $OID_Y $OID_Y R100 renamed_yQfile_y
        ? actual
        ? expect
@@ -163,7 +163,7 @@ test_expect_success 'rename causes 2 path lines (-z)' '
        # branch.oid $H0
        # branch.head master
        1 M. N... 100644 100644 100644 $OID_X $OID_X1 file_x
-       1 D. N... 100644 000000 000000 $OID_Z $_z40 file_z
+       1 D. N... 100644 000000 000000 $OID_Z $ZERO_OID file_z
        2 R. N... 100644 100644 100644 $OID_Y $OID_Y R100 renamed_yQfile_y
        ? actual
        ? expect
@@ -246,8 +246,8 @@ test_expect_success 'verify --intent-to-add output' '
        git add --intent-to-add intent1.add intent2.add &&
 
        cat >expect <<-EOF &&
-       1 .A N... 000000 000000 100644 $_z40 $_z40 intent1.add
-       1 .A N... 000000 000000 100644 $_z40 $_z40 intent2.add
+       1 .A N... 000000 000000 100644 $ZERO_OID $ZERO_OID intent1.add
+       1 .A N... 000000 000000 100644 $ZERO_OID $ZERO_OID intent2.add
        EOF
 
        git status --porcelain=v2 >actual &&
@@ -280,7 +280,7 @@ test_expect_success 'verify AA (add-add) conflict' '
        cat >expect <<-EOF &&
        # branch.oid $HM
        # branch.head AA_M
-       u AA N... 000000 100644 100644 100644 $_z40 $OID_AA_B $OID_AA_A conflict.txt
+       u AA N... 000000 100644 100644 100644 $ZERO_OID $OID_AA_B $OID_AA_A conflict.txt
        EOF
 
        git status --porcelain=v2 --branch --untracked-files=all >actual &&
@@ -373,7 +373,7 @@ test_expect_success 'verify upstream fields in branch header' '
                ## Test upstream-gone case. Fake this by pointing origin/master at
                ## a non-existing commit.
                OLD=$(git rev-parse origin/master) &&
-               NEW=$_z40 &&
+               NEW=$ZERO_OID &&
                mv .git/packed-refs .git/old-packed-refs &&
                sed "s/$OLD/$NEW/g" <.git/old-packed-refs >.git/packed-refs &&
 
@@ -469,8 +469,8 @@ test_expect_success 'create and add submodule, submodule appears clean (A. S...)
                # branch.head master
                # branch.upstream origin/master
                # branch.ab +0 -0
-               1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules
-               1 A. S... 000000 160000 160000 $_z40 $HSUB sub1
+               1 A. N... 000000 100644 100644 $ZERO_OID $HMOD .gitmodules
+               1 A. S... 000000 160000 160000 $ZERO_OID $HSUB sub1
                EOF
 
                git status --porcelain=v2 --branch --untracked-files=all >actual &&
@@ -494,8 +494,8 @@ test_expect_success 'untracked changes in added submodule (AM S..U)' '
                # branch.head master
                # branch.upstream origin/master
                # branch.ab +0 -0
-               1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules
-               1 AM S..U 000000 160000 160000 $_z40 $HSUB sub1
+               1 A. N... 000000 100644 100644 $ZERO_OID $HMOD .gitmodules
+               1 AM S..U 000000 160000 160000 $ZERO_OID $HSUB sub1
                EOF
 
                git status --porcelain=v2 --branch --untracked-files=all >actual &&
@@ -519,8 +519,8 @@ test_expect_success 'staged changes in added submodule (AM S.M.)' '
                # branch.head master
                # branch.upstream origin/master
                # branch.ab +0 -0
-               1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules
-               1 AM S.M. 000000 160000 160000 $_z40 $HSUB sub1
+               1 A. N... 000000 100644 100644 $ZERO_OID $HMOD .gitmodules
+               1 AM S.M. 000000 160000 160000 $ZERO_OID $HSUB sub1
                EOF
 
                git status --porcelain=v2 --branch --untracked-files=all >actual &&
@@ -546,8 +546,8 @@ test_expect_success 'staged and unstaged changes in added (AM S.M.)' '
                # branch.head master
                # branch.upstream origin/master
                # branch.ab +0 -0
-               1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules
-               1 AM S.M. 000000 160000 160000 $_z40 $HSUB sub1
+               1 A. N... 000000 100644 100644 $ZERO_OID $HMOD .gitmodules
+               1 AM S.M. 000000 160000 160000 $ZERO_OID $HSUB sub1
                EOF
 
                git status --porcelain=v2 --branch --untracked-files=all >actual &&
@@ -573,8 +573,8 @@ test_expect_success 'staged and untracked changes in added submodule (AM S.MU)'
                # branch.head master
                # branch.upstream origin/master
                # branch.ab +0 -0
-               1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules
-               1 AM S.MU 000000 160000 160000 $_z40 $HSUB sub1
+               1 A. N... 000000 100644 100644 $ZERO_OID $HMOD .gitmodules
+               1 AM S.MU 000000 160000 160000 $ZERO_OID $HSUB sub1
                EOF
 
                git status --porcelain=v2 --branch --untracked-files=all >actual &&
@@ -600,8 +600,8 @@ test_expect_success 'commit within the submodule appears as new commit in super
                # branch.head master
                # branch.upstream origin/master
                # branch.ab +0 -0
-               1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules
-               1 AM SC.. 000000 160000 160000 $_z40 $HSUB sub1
+               1 A. N... 000000 100644 100644 $ZERO_OID $HMOD .gitmodules
+               1 AM SC.. 000000 160000 160000 $ZERO_OID $HSUB sub1
                EOF
 
                git status --porcelain=v2 --branch --untracked-files=all >actual &&
index 76c223c9675cd1531ac078fa9e0c1f8cc4e09781..ab9da61da391f1b6040076dda2454f602adef7d2 100755 (executable)
@@ -65,7 +65,7 @@ test_expect_success setup '
 test_expect_success "checkout from non-existing branch" '
 
        git checkout -b delete-me master &&
-       rm .git/refs/heads/delete-me &&
+       git update-ref -d --no-deref refs/heads/delete-me &&
        test refs/heads/delete-me = "$(git symbolic-ref HEAD)" &&
        git checkout master &&
        test refs/heads/master = "$(git symbolic-ref HEAD)"
index 152104412f212190c18279b64e80bb9f58242d47..2f532529b82a847f81d4bdda17f9f07c115c2d86 100755 (executable)
@@ -126,6 +126,22 @@ test_expect_success 'submodule add' '
        test_cmp empty untracked
 '
 
+test_create_repo parent &&
+test_commit -C parent one
+
+test_expect_success 'redirected submodule add does not show progress' '
+       git -C addtest submodule add "file://$submodurl/parent" submod-redirected \
+               2>err &&
+       ! grep % err &&
+       test_i18ngrep ! "Checking connectivity" err
+'
+
+test_expect_success 'redirected submodule add --progress does show progress' '
+       git -C addtest submodule add --progress "file://$submodurl/parent" \
+               submod-redirected-progress 2>err && \
+       grep % err
+'
+
 test_expect_success 'submodule add to .gitignored path fails' '
        (
                cd addtest-ignore &&
index e159fc5035d08eee2c1577afd2ac792ddce2934c..08d9add05e0868980f1b59412997e4598e5f9576 100755 (executable)
@@ -59,6 +59,16 @@ test_expect_success 'submodule add --reference uses alternates' '
        test_alternate_is_used super/.git/modules/sub/objects/info/alternates super/sub
 '
 
+test_expect_success 'submodule add --reference with --dissociate does not use alternates' '
+       (
+               cd super &&
+               git submodule add --reference ../B --dissociate "file://$base_dir/A" sub-dissociate &&
+               git commit -m B-super-added &&
+               git repack -ad
+       ) &&
+       test_path_is_missing super/.git/modules/sub-dissociate/objects/info/alternates
+'
+
 test_expect_success 'that reference gets used with add' '
        (
                cd super/sub &&
@@ -82,6 +92,13 @@ test_expect_success 'updating superproject keeps alternates' '
        test_alternate_is_used super-clone/.git/modules/sub/objects/info/alternates super-clone/sub
 '
 
+test_expect_success 'updating superproject with --dissociate does not keep alternates' '
+       test_when_finished "rm -rf super-clone" &&
+       git clone super super-clone &&
+       git -C super-clone submodule update --init --reference ../B --dissociate &&
+       test_path_is_missing super-clone/.git/modules/sub/objects/info/alternates
+'
+
 test_expect_success 'submodules use alternates when cloning a superproject' '
        test_when_finished "rm -rf super-clone" &&
        git clone --reference super --recursive super super-clone &&
diff --git a/t/t7415-submodule-names.sh b/t/t7415-submodule-names.sh
new file mode 100755 (executable)
index 0000000..a770d92
--- /dev/null
@@ -0,0 +1,154 @@
+#!/bin/sh
+
+test_description='check handling of .. in submodule names
+
+Exercise the name-checking function on a variety of names, and then give a
+real-world setup that confirms we catch this in practice.
+'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-pack.sh
+
+test_expect_success 'check names' '
+       cat >expect <<-\EOF &&
+       valid
+       valid/with/paths
+       EOF
+
+       git submodule--helper check-name >actual <<-\EOF &&
+       valid
+       valid/with/paths
+
+       ../foo
+       /../foo
+       ..\foo
+       \..\foo
+       foo/..
+       foo/../
+       foo\..
+       foo\..\
+       foo/../bar
+       EOF
+
+       test_cmp expect actual
+'
+
+test_expect_success 'create innocent subrepo' '
+       git init innocent &&
+       git -C innocent commit --allow-empty -m foo
+'
+
+test_expect_success 'submodule add refuses invalid names' '
+       test_must_fail \
+               git submodule add --name ../../modules/evil "$PWD/innocent" evil
+'
+
+test_expect_success 'add evil submodule' '
+       git submodule add "$PWD/innocent" evil &&
+
+       mkdir modules &&
+       cp -r .git/modules/evil modules &&
+       write_script modules/evil/hooks/post-checkout <<-\EOF &&
+       echo >&2 "RUNNING POST CHECKOUT"
+       EOF
+
+       git config -f .gitmodules submodule.evil.update checkout &&
+       git config -f .gitmodules --rename-section \
+               submodule.evil submodule.../../modules/evil &&
+       git add modules &&
+       git commit -am evil
+'
+
+# This step seems like it shouldn't be necessary, since the payload is
+# contained entirely in the evil submodule. But due to the vagaries of the
+# submodule code, checking out the evil module will fail unless ".git/modules"
+# exists. Adding another submodule (with a name that sorts before "evil") is an
+# easy way to make sure this is the case in the victim clone.
+test_expect_success 'add other submodule' '
+       git submodule add "$PWD/innocent" another-module &&
+       git add another-module &&
+       git commit -am another
+'
+
+test_expect_success 'clone evil superproject' '
+       git clone --recurse-submodules . victim >output 2>&1 &&
+       ! grep "RUNNING POST CHECKOUT" output
+'
+
+test_expect_success 'fsck detects evil superproject' '
+       test_must_fail git fsck
+'
+
+test_expect_success 'transfer.fsckObjects detects evil superproject (unpack)' '
+       rm -rf dst.git &&
+       git init --bare dst.git &&
+       git -C dst.git config transfer.fsckObjects true &&
+       test_must_fail git push dst.git HEAD
+'
+
+test_expect_success 'transfer.fsckObjects detects evil superproject (index)' '
+       rm -rf dst.git &&
+       git init --bare dst.git &&
+       git -C dst.git config transfer.fsckObjects true &&
+       git -C dst.git config transfer.unpackLimit 1 &&
+       test_must_fail git push dst.git HEAD
+'
+
+# Normally our packs contain commits followed by trees followed by blobs. This
+# reverses the order, which requires backtracking to find the context of a
+# blob. We'll start with a fresh gitmodules-only tree to make it simpler.
+test_expect_success 'create oddly ordered pack' '
+       git checkout --orphan odd &&
+       git rm -rf --cached . &&
+       git add .gitmodules &&
+       git commit -m odd &&
+       {
+               pack_header 3 &&
+               pack_obj $(git rev-parse HEAD:.gitmodules) &&
+               pack_obj $(git rev-parse HEAD^{tree}) &&
+               pack_obj $(git rev-parse HEAD)
+       } >odd.pack &&
+       pack_trailer odd.pack
+'
+
+test_expect_success 'transfer.fsckObjects handles odd pack (unpack)' '
+       rm -rf dst.git &&
+       git init --bare dst.git &&
+       test_must_fail git -C dst.git unpack-objects --strict <odd.pack
+'
+
+test_expect_success 'transfer.fsckObjects handles odd pack (index)' '
+       rm -rf dst.git &&
+       git init --bare dst.git &&
+       test_must_fail git -C dst.git index-pack --strict --stdin <odd.pack
+'
+
+test_expect_success 'fsck detects symlinked .gitmodules file' '
+       git init symlink &&
+       (
+               cd symlink &&
+
+               # Make the tree directly to avoid index restrictions.
+               #
+               # Because symlinks store the target as a blob, choose
+               # a pathname that could be parsed as a .gitmodules file
+               # to trick naive non-symlink-aware checking.
+               tricky="[foo]bar=true" &&
+               content=$(git hash-object -w ../.gitmodules) &&
+               target=$(printf "$tricky" | git hash-object -w --stdin) &&
+               tree=$(
+                       {
+                               printf "100644 blob $content\t$tricky\n" &&
+                               printf "120000 blob $target\t.gitmodules\n"
+                       } | git mktree
+               ) &&
+               commit=$(git commit-tree $tree) &&
+
+               # Check not only that we fail, but that it is due to the
+               # symlink detector; this grep string comes from the config
+               # variable name and will not be translated.
+               test_must_fail git fsck 2>output &&
+               grep gitmodulesSymlink output
+       )
+'
+
+test_done
index 9edf6572ed3b8f04ef1169f545043fcb2e09fe21..b4b74dbe29dd53ae8029cec20be4b6fef2cd6a1e 100755 (executable)
@@ -18,7 +18,7 @@ test_create_repo_with_commit () {
 }
 
 sanitize_output () {
-       sed -e "s/$_x40/HASH/" -e "s/$_x40/HASH/" output >output2 &&
+       sed -e "s/$OID_REGEX/HASH/" -e "s/$OID_REGEX/HASH/" output >output2 &&
        mv output2 output
 }
 
diff --git a/t/t7525-status-rename.sh b/t/t7525-status-rename.sh
new file mode 100755 (executable)
index 0000000..ef8b1b3
--- /dev/null
@@ -0,0 +1,113 @@
+#!/bin/sh
+
+test_description='git status rename detection options'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       echo 1 >original &&
+       git add . &&
+       git commit -m"Adding original file." &&
+       mv original renamed &&
+       echo 2 >> renamed &&
+       git add . &&
+       cat >.gitignore <<-\EOF
+       .gitignore
+       expect*
+       actual*
+       EOF
+'
+
+test_expect_success 'status no-options' '
+       git status >actual &&
+       test_i18ngrep "renamed:" actual
+'
+
+test_expect_success 'status --no-renames' '
+       git status --no-renames >actual &&
+       test_i18ngrep "deleted:" actual &&
+       test_i18ngrep "new file:" actual
+'
+
+test_expect_success 'status.renames inherits from diff.renames false' '
+       git -c diff.renames=false status >actual &&
+       test_i18ngrep "deleted:" actual &&
+       test_i18ngrep "new file:" actual
+'
+
+test_expect_success 'status.renames inherits from diff.renames true' '
+       git -c diff.renames=true status >actual &&
+       test_i18ngrep "renamed:" actual
+'
+
+test_expect_success 'status.renames overrides diff.renames false' '
+       git -c diff.renames=true -c status.renames=false status >actual &&
+       test_i18ngrep "deleted:" actual &&
+       test_i18ngrep "new file:" actual
+'
+
+test_expect_success 'status.renames overrides from diff.renames true' '
+       git -c diff.renames=false -c status.renames=true status >actual &&
+       test_i18ngrep "renamed:" actual
+'
+
+test_expect_success 'status status.renames=false' '
+       git -c status.renames=false status >actual &&
+       test_i18ngrep "deleted:" actual &&
+       test_i18ngrep "new file:" actual
+'
+
+test_expect_success 'status status.renames=true' '
+       git -c status.renames=true status >actual &&
+       test_i18ngrep "renamed:" actual
+'
+
+test_expect_success 'commit honors status.renames=false' '
+       git -c status.renames=false commit --dry-run >actual &&
+       test_i18ngrep "deleted:" actual &&
+       test_i18ngrep "new file:" actual
+'
+
+test_expect_success 'commit honors status.renames=true' '
+       git -c status.renames=true commit --dry-run >actual &&
+       test_i18ngrep "renamed:" actual
+'
+
+test_expect_success 'status config overridden' '
+       git -c status.renames=true status --no-renames >actual &&
+       test_i18ngrep "deleted:" actual &&
+       test_i18ngrep "new file:" actual
+'
+
+test_expect_success 'status score=100%' '
+       git status -M=100% >actual &&
+       test_i18ngrep "deleted:" actual &&
+       test_i18ngrep "new file:" actual &&
+
+       git status --find-rename=100% >actual &&
+       test_i18ngrep "deleted:" actual &&
+       test_i18ngrep "new file:" actual
+'
+
+test_expect_success 'status score=01%' '
+       git status -M=01% >actual &&
+       test_i18ngrep "renamed:" actual &&
+
+       git status --find-rename=01% >actual &&
+       test_i18ngrep "renamed:" actual
+'
+
+test_expect_success 'copies not overridden by find-rename' '
+       cp renamed copy &&
+       git add copy &&
+
+       git -c status.renames=copies status -M=01% >actual &&
+       test_i18ngrep "copied:" actual &&
+       test_i18ngrep "renamed:" actual &&
+
+       git -c status.renames=copies status --find-rename=01% >actual &&
+       test_i18ngrep "copied:" actual &&
+       test_i18ngrep "renamed:" actual
+'
+
+test_done
index 9c422bcd7cc8c1b2525579ca3a7ffa6ee412ba42..dd8ab7ede182fc3c3da840fee083bf23a94ce13c 100755 (executable)
@@ -92,7 +92,7 @@ test_expect_success 'will not overwrite removed file with staged changes' '
        test_cmp important c1.c
 '
 
-test_expect_failure 'will not overwrite unstaged changes in renamed file' '
+test_expect_success 'will not overwrite unstaged changes in renamed file' '
        git reset --hard c1 &&
        git mv c1.c other.c &&
        git commit -m rename &&
index 6061a04147a06dba0d049cbb5d31f8049c2210d0..6162e2a8e66f6f0e42e0a8ba6ee3b728e8e84918 100755 (executable)
@@ -4,6 +4,12 @@ test_description='git repack works correctly'
 
 . ./test-lib.sh
 
+commit_and_pack() {
+       test_commit "$@" >/dev/null &&
+       SHA1=$(git pack-objects --all --unpacked --incremental .git/objects/pack/pack </dev/null) &&
+       echo pack-${SHA1}.pack
+}
+
 test_expect_success 'objects in packs marked .keep are not repacked' '
        echo content1 > file1 &&
        echo content2 > file2 &&
@@ -194,7 +200,26 @@ test_expect_success 'objects made unreachable by grafts only are kept' '
        git reflog expire --expire=$test_tick --expire-unreachable=$test_tick --all &&
        git repack -a -d &&
        git cat-file -t $H1
-       '
+'
+
+test_expect_success 'repack --keep-pack' '
+       test_create_repo keep-pack &&
+       (
+               cd keep-pack &&
+               P1=$(commit_and_pack 1) &&
+               P2=$(commit_and_pack 2) &&
+               P3=$(commit_and_pack 3) &&
+               P4=$(commit_and_pack 4) &&
+               ls .git/objects/pack/*.pack >old-counts &&
+               test_line_count = 4 old-counts &&
+               git repack -a -d --keep-pack $P1 --keep-pack $P4 &&
+               ls .git/objects/pack/*.pack >new-counts &&
+               grep -q $P1 new-counts &&
+               grep -q $P4 new-counts &&
+               test_line_count = 3 new-counts &&
+               git fsck
+       )
+'
 
 test_done
 
diff --git a/t/t8012-blame-colors.sh b/t/t8012-blame-colors.sh
new file mode 100755 (executable)
index 0000000..ed38f74
--- /dev/null
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+test_description='colored git blame'
+. ./test-lib.sh
+
+PROG='git blame -c'
+. "$TEST_DIRECTORY"/annotate-tests.sh
+
+test_expect_success 'colored blame colors contiguous lines' '
+       git -c color.blame.repeatedLines=yellow blame --color-lines --abbrev=12 hello.c >actual.raw &&
+       git -c color.blame.repeatedLines=yellow -c blame.coloring=repeatedLines blame --abbrev=12 hello.c >actual.raw.2 &&
+       test_cmp actual.raw actual.raw.2 &&
+       test_decode_color <actual.raw >actual &&
+       grep "<YELLOW>" <actual >darkened &&
+       grep "(F" darkened > F.expect &&
+       grep "(H" darkened > H.expect &&
+       test_line_count = 2 F.expect &&
+       test_line_count = 3 H.expect
+'
+
+test_expect_success 'color by age consistently colors old code' '
+       git blame --color-by-age hello.c >actual.raw &&
+       git -c blame.coloring=highlightRecent blame hello.c >actual.raw.2 &&
+       test_cmp actual.raw actual.raw.2 &&
+       test_decode_color <actual.raw >actual &&
+       grep "<BLUE>" <actual >colored &&
+       test_line_count = 10 colored
+'
+
+test_expect_success 'blame color by age: new code is different' '
+       cat >>hello.c <<-EOF &&
+               void qfunc();
+       EOF
+       git add hello.c &&
+       GIT_AUTHOR_DATE="" git commit -m "new commit" &&
+
+       git -c color.blame.highlightRecent="yellow,1 month ago, cyan" blame --color-by-age hello.c >actual.raw &&
+       test_decode_color <actual.raw >actual &&
+
+       grep "<YELLOW>" <actual >colored &&
+       test_line_count = 10 colored &&
+
+       grep "<CYAN>" <actual >colored &&
+       test_line_count = 1 colored &&
+       grep qfunc colored
+'
+
+test_done
index 8eaaca6f9906879f16f3658b0e04796086d4c435..0b20b07e689897326c0060ec2181f6a09fca9797 100755 (executable)
@@ -473,7 +473,7 @@ test_expect_failure 'change file mode but keep old content' '
        {
                git rev-list HEAD |
                git diff-tree --root --stdin |
-               sed "s/$_x40/OBJID/g"
+               sed "s/$OID_REGEX/OBJID/g"
        } >actual &&
        git show HEAD:greeting >actual.blob &&
        git show HEAD^:greeting >actual.target &&
@@ -573,7 +573,7 @@ test_expect_success 'NUL in log message, file content, and property name' '
        {
                git rev-list HEAD |
                git diff-tree --root --stdin |
-               sed "s/$_x40/OBJID/g"
+               sed "s/$OID_REGEX/OBJID/g"
        } >actual &&
        {
                git cat-file commit HEAD | nul_to_q &&
@@ -659,7 +659,7 @@ test_expect_success 'change file mode and reiterate content' '
        {
                git rev-list HEAD |
                git diff-tree --root --stdin |
-               sed "s/$_x40/OBJID/g"
+               sed "s/$OID_REGEX/OBJID/g"
        } >actual &&
        git show HEAD:greeting >actual.blob &&
        git show HEAD^:greeting >actual.target &&
@@ -792,7 +792,7 @@ test_expect_success 'property deltas supported' '
        {
                git rev-list HEAD |
                git diff-tree --stdin |
-               sed "s/$_x40/OBJID/g"
+               sed "s/$OID_REGEX/OBJID/g"
        } >actual &&
        test_cmp expect actual
 '
@@ -846,7 +846,7 @@ test_expect_success 'properties on /' '
        {
                git rev-list HEAD |
                git diff-tree --root --always --stdin |
-               sed "s/$_x40/OBJID/g"
+               sed "s/$OID_REGEX/OBJID/g"
        } >actual &&
        test_cmp expect actual
 '
@@ -931,7 +931,7 @@ test_expect_success 'deltas for typechange' '
        {
                git rev-list HEAD |
                git diff-tree --root --stdin |
-               sed "s/$_x40/OBJID/g"
+               sed "s/$OID_REGEX/OBJID/g"
        } >actual &&
        test_cmp expect actual
 '
@@ -1030,7 +1030,7 @@ test_expect_success 'deltas need not consume the whole preimage' '
        {
                git rev-list HEAD |
                git diff-tree --root --stdin |
-               sed "s/$_x40/OBJID/g"
+               sed "s/$OID_REGEX/OBJID/g"
        } >actual &&
        test_cmp expect actual &&
        git show HEAD:postimage >actual.3 &&
index a735fa37170fca27b48fba218a408cb2ea6bac8a..9c49b6c1fe24335c6bcae777d60e14cdf96fd306 100755 (executable)
@@ -215,7 +215,8 @@ test_expect_success "multi-fetch continues to work" "
        "
 
 test_expect_success "multi-fetch works off a 'clean' repository" '
-       rm -r "$GIT_DIR/svn" "$GIT_DIR/refs/remotes" "$GIT_DIR/logs" &&
+       rm -rf "$GIT_DIR/svn" "$GIT_DIR/refs/remotes" &&
+       git reflog expire --all --expire=all &&
        mkdir "$GIT_DIR/svn" &&
        git svn multi-fetch
        '
index dc79df7b042b75ca192ff1a860eb0c22a9059a8e..9e7f96223dc2222c48b172711b4b469ff08190c8 100755 (executable)
@@ -1185,7 +1185,7 @@ test_expect_success PIPE 'N: empty directory reads as missing' '
        test_cmp expect.response response &&
        git rev-list read-empty |
        git diff-tree -r --root --stdin |
-       sed "s/$_x40/OBJNAME/g" >actual &&
+       sed "s/$OID_REGEX/OBJNAME/g" >actual &&
        test_cmp expect actual
 '
 
@@ -1271,7 +1271,7 @@ test_expect_success 'N: delete directory by copying' '
        git fast-import <input &&
        git rev-list N-delete |
                git diff-tree -r --stdin --root --always |
-               sed -e "s/$_x40/OBJID/g" >actual &&
+               sed -e "s/$OID_REGEX/OBJID/g" >actual &&
        test_cmp expect actual
 '
 
@@ -2602,7 +2602,7 @@ test_expect_success 'R: terminating "done" within commit' '
        EOF
        git rev-list done-ends |
        git diff-tree -r --stdin --root --always |
-       sed -e "s/$_x40/OBJID/g" >actual &&
+       sed -e "s/$OID_REGEX/OBJID/g" >actual &&
        test_cmp expect actual
 '
 
diff --git a/t/t9832-unshelve.sh b/t/t9832-unshelve.sh
new file mode 100755 (executable)
index 0000000..48ec767
--- /dev/null
@@ -0,0 +1,138 @@
+#!/bin/sh
+
+last_shelved_change () {
+       p4 changes -s shelved -m1 | cut -d " " -f 2
+}
+
+test_description='git p4 unshelve'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+test_expect_success 'init depot' '
+       (
+               cd "$cli" &&
+               echo file1 >file1 &&
+               p4 add file1 &&
+               p4 submit -d "change 1" &&
+               : >file_to_delete &&
+               p4 add file_to_delete &&
+               p4 submit -d "file to delete"
+       )
+'
+
+test_expect_success 'initial clone' '
+       git p4 clone --dest="$git" //depot/@all
+'
+
+test_expect_success 'create shelved changelist' '
+       (
+               cd "$cli" &&
+               p4 edit file1 &&
+               echo "a change" >>file1 &&
+               echo "new file" >file2 &&
+               p4 add file2 &&
+               p4 delete file_to_delete &&
+               p4 opened &&
+               p4 shelve -i <<EOF
+Change: new
+Description:
+       Test commit
+
+       Further description
+Files:
+       //depot/file1
+       //depot/file2
+       //depot/file_to_delete
+EOF
+
+       ) &&
+       (
+               cd "$git" &&
+               change=$(last_shelved_change) &&
+               git p4 unshelve $change &&
+               git show refs/remotes/p4/unshelved/$change | grep -q "Further description" &&
+               git cherry-pick refs/remotes/p4/unshelved/$change &&
+               test_path_is_file file2 &&
+               test_cmp file1 "$cli"/file1 &&
+               test_cmp file2 "$cli"/file2 &&
+               test_path_is_missing file_to_delete
+       )
+'
+
+test_expect_success 'update shelved changelist and re-unshelve' '
+       test_when_finished cleanup_git &&
+       (
+               cd "$cli" &&
+               change=$(last_shelved_change) &&
+               echo "file3" >file3 &&
+               p4 add -c $change file3 &&
+               p4 shelve -i -r <<EOF &&
+Change: $change
+Description:
+       Test commit
+
+       Further description
+Files:
+       //depot/file1
+       //depot/file2
+       //depot/file3
+       //depot/file_to_delete
+EOF
+               p4 describe $change
+       ) &&
+       (
+               cd "$git" &&
+               change=$(last_shelved_change) &&
+               git p4 unshelve $change &&
+               git diff refs/remotes/p4/unshelved/$change.0 refs/remotes/p4/unshelved/$change | grep -q file3
+       )
+'
+
+# This is the tricky case where the shelved changelist base revision doesn't
+# match git-p4's idea of the base revision
+#
+# We will attempt to unshelve a change that is based on a change one commit
+# ahead of p4/master
+
+test_expect_success 'create shelved changelist based on p4 change ahead of p4/master' '
+       git p4 clone --dest="$git" //depot/@all &&
+       (
+               cd "$cli" &&
+               p4 revert ... &&
+               p4 edit file1 &&
+               echo "foo" >>file1 &&
+               p4 submit -d "change:foo" &&
+               p4 edit file1 &&
+               echo "bar" >>file1 &&
+               p4 shelve -i <<EOF &&
+Change: new
+Description:
+       Change to be unshelved
+Files:
+       //depot/file1
+EOF
+               change=$(last_shelved_change) &&
+               p4 describe -S $change | grep -q "Change to be unshelved"
+       )
+'
+
+# Now try to unshelve it. git-p4 should refuse to do so.
+test_expect_success 'try to unshelve the change' '
+       test_when_finished cleanup_git &&
+       (
+               change=$(last_shelved_change) &&
+               cd "$git" &&
+               test_must_fail git p4 unshelve $change 2>out.txt &&
+               grep -q "cannot unshelve" out.txt
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
index 1b34caa1e1a5e86cd2edccbb1559009b35022e10..a28640ce1accd604bd32fdc95fd172119f6c3741 100755 (executable)
@@ -13,7 +13,7 @@ complete ()
        return 0
 }
 
-# Be careful when updating this list:
+# Be careful when updating these lists:
 #
 # (1) The build tree may have build artifact from different branch, or
 #     the user's $PATH may have a random executable that may begin
@@ -30,7 +30,8 @@ complete ()
 #     completion for "git <TAB>", and a plumbing is excluded.  "add",
 #     "filter-branch" and "ls-files" are listed for this.
 
-GIT_TESTING_COMMAND_COMPLETION='add checkout check-attr filter-branch ls-files'
+GIT_TESTING_ALL_COMMAND_LIST='add checkout check-attr filter-branch ls-files'
+GIT_TESTING_PORCELAIN_COMMAND_LIST='add checkout filter-branch'
 
 . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash"
 
@@ -84,10 +85,11 @@ test_completion ()
        then
                printf '%s\n' "$2" >expected
        else
-               sed -e 's/Z$//' >expected
+               sed -e 's/Z$//' |sort >expected
        fi &&
        run_completion "$1" &&
-       test_cmp expected out
+       sort out >out_sorted &&
+       test_cmp expected out_sorted
 }
 
 # Test __gitcomp.
@@ -179,7 +181,7 @@ test_expect_success '__git_find_repo_path - cwd is a .git directory' '
 test_expect_success '__git_find_repo_path - parent is a .git directory' '
        echo "$ROOT/.git" >expected &&
        (
-               cd .git/refs/heads &&
+               cd .git/objects &&
                __git_find_repo_path &&
                echo "$__git_repo_path" >"$actual"
        ) &&
@@ -400,6 +402,46 @@ test_expect_success '__gitdir - remote as argument' '
        test_cmp expected "$actual"
 '
 
+
+test_expect_success '__git_dequote - plain unquoted word' '
+       __git_dequote unquoted-word &&
+       verbose test unquoted-word = "$dequoted_word"
+'
+
+# input:    b\a\c\k\'\\\"s\l\a\s\h\es
+# expected: back'\"slashes
+test_expect_success '__git_dequote - backslash escaped' '
+       __git_dequote "b\a\c\k\\'\''\\\\\\\"s\l\a\s\h\es" &&
+       verbose test "back'\''\\\"slashes" = "$dequoted_word"
+'
+
+# input:    sin'gle\' '"quo'ted
+# expected: single\ "quoted
+test_expect_success '__git_dequote - single quoted' '
+       __git_dequote "'"sin'gle\\\\' '\\\"quo'ted"'" &&
+       verbose test '\''single\ "quoted'\'' = "$dequoted_word"
+'
+
+# input:    dou"ble\\" "\"\quot"ed
+# expected: double\ "\quoted
+test_expect_success '__git_dequote - double quoted' '
+       __git_dequote '\''dou"ble\\" "\"\quot"ed'\'' &&
+       verbose test '\''double\ "\quoted'\'' = "$dequoted_word"
+'
+
+# input: 'open single quote
+test_expect_success '__git_dequote - open single quote' '
+       __git_dequote "'\''open single quote" &&
+       verbose test "open single quote" = "$dequoted_word"
+'
+
+# input: "open double quote
+test_expect_success '__git_dequote - open double quote' '
+       __git_dequote "\"open double quote" &&
+       verbose test "open double quote" = "$dequoted_word"
+'
+
+
 test_expect_success '__gitcomp_direct - puts everything into COMPREPLY as-is' '
        sed -e "s/Z$//g" >expected <<-EOF &&
        with-trailing-space Z
@@ -1168,6 +1210,124 @@ test_expect_success 'teardown after ref completion' '
        git remote remove other
 '
 
+
+test_path_completion ()
+{
+       test $# = 2 || error "bug in the test script: not 2 parameters to test_path_completion"
+
+       local cur="$1" expected="$2"
+       echo "$expected" >expected &&
+       (
+               # In the following tests calling this function we only
+               # care about how __git_complete_index_file() deals with
+               # unusual characters in path names.  By requesting only
+               # untracked files we dont have to bother adding any
+               # paths to the index in those tests.
+               __git_complete_index_file --others &&
+               print_comp
+       ) &&
+       test_cmp expected out
+}
+
+test_expect_success 'setup for path completion tests' '
+       mkdir simple-dir \
+             "spaces in dir" \
+             árvíztűrő &&
+       touch simple-dir/simple-file \
+             "spaces in dir/spaces in file" \
+             "árvíztűrő/Сайн яваарай" &&
+       if test_have_prereq !MINGW &&
+          mkdir BS\\dir \
+                '$'separators\034in\035dir'' &&
+          touch BS\\dir/DQ\"file \
+                '$'separators\034in\035dir/sep\036in\037file''
+       then
+               test_set_prereq FUNNYNAMES
+       else
+               rm -rf BS\\dir '$'separators\034in\035dir''
+       fi
+'
+
+test_expect_success '__git_complete_index_file - simple' '
+       test_path_completion simple simple-dir &&  # Bash is supposed to
+                                                  # add the trailing /.
+       test_path_completion simple-dir/simple simple-dir/simple-file
+'
+
+test_expect_success \
+    '__git_complete_index_file - escaped characters on cmdline' '
+       test_path_completion spac "spaces in dir" &&  # Bash will turn this
+                                                     # into "spaces\ in\ dir"
+       test_path_completion "spaces\\ i" \
+                            "spaces in dir" &&
+       test_path_completion "spaces\\ in\\ dir/s" \
+                            "spaces in dir/spaces in file" &&
+       test_path_completion "spaces\\ in\\ dir/spaces\\ i" \
+                            "spaces in dir/spaces in file"
+'
+
+test_expect_success \
+    '__git_complete_index_file - quoted characters on cmdline' '
+       # Testing with an opening but without a corresponding closing
+       # double quote is important.
+       test_path_completion \"spac "spaces in dir" &&
+       test_path_completion "\"spaces i" \
+                            "spaces in dir" &&
+       test_path_completion "\"spaces in dir/s" \
+                            "spaces in dir/spaces in file" &&
+       test_path_completion "\"spaces in dir/spaces i" \
+                            "spaces in dir/spaces in file"
+'
+
+test_expect_success '__git_complete_index_file - UTF-8 in ls-files output' '
+       test_path_completion á árvíztűrő &&
+       test_path_completion árvíztűrő/С "árvíztűrő/Сайн яваарай"
+'
+
+test_expect_success FUNNYNAMES \
+    '__git_complete_index_file - C-style escapes in ls-files output' '
+       test_path_completion BS \
+                            BS\\dir &&
+       test_path_completion BS\\\\d \
+                            BS\\dir &&
+       test_path_completion BS\\\\dir/DQ \
+                            BS\\dir/DQ\"file &&
+       test_path_completion BS\\\\dir/DQ\\\"f \
+                            BS\\dir/DQ\"file
+'
+
+test_expect_success FUNNYNAMES \
+    '__git_complete_index_file - \nnn-escaped characters in ls-files output' '
+       test_path_completion sep '$'separators\034in\035dir'' &&
+       test_path_completion '$'separators\034i'' \
+                            '$'separators\034in\035dir'' &&
+       test_path_completion '$'separators\034in\035dir/sep'' \
+                            '$'separators\034in\035dir/sep\036in\037file'' &&
+       test_path_completion '$'separators\034in\035dir/sep\036i'' \
+                            '$'separators\034in\035dir/sep\036in\037file''
+'
+
+test_expect_success FUNNYNAMES \
+    '__git_complete_index_file - removing repeated quoted path components' '
+       test_when_finished rm -r repeated-quoted &&
+       mkdir repeated-quoted &&      # A directory whose name in itself
+                                     # would not be quoted ...
+       >repeated-quoted/0-file &&
+       >repeated-quoted/1\"file &&   # ... but here the file makes the
+                                     # dirname quoted ...
+       >repeated-quoted/2-file &&
+       >repeated-quoted/3\"file &&   # ... and here, too.
+
+       # Still, we shold only list the directory name only once.
+       test_path_completion repeated repeated-quoted
+'
+
+test_expect_success 'teardown after path completion tests' '
+       rm -rf simple-dir "spaces in dir" árvíztűrő \
+              BS\\dir '$'separators\034in\035dir''
+'
+
+
 test_expect_success '__git_get_config_variables' '
        cat >expect <<-EOF &&
        name-1
@@ -1191,17 +1351,6 @@ test_expect_success '__git_pretty_aliases' '
        test_cmp expect actual
 '
 
-test_expect_success '__git_aliases' '
-       cat >expect <<-EOF &&
-       ci
-       co
-       EOF
-       test_config alias.ci commit &&
-       test_config alias.co checkout &&
-       __git_aliases >actual &&
-       test_cmp expect actual
-'
-
 test_expect_success 'basic' '
        run_completion "git " &&
        # built-in
@@ -1365,6 +1514,7 @@ test_expect_success 'complete files' '
 
        echo "expected" > .gitignore &&
        echo "out" >> .gitignore &&
+       echo "out_sorted" >> .gitignore &&
 
        git add .gitignore &&
        test_completion "git commit " ".gitignore" &&
@@ -1510,13 +1660,6 @@ test_expect_success 'sourcing the completion script clears cached commands' '
        verbose test -z "$__git_all_commands"
 '
 
-test_expect_success 'sourcing the completion script clears cached porcelain commands' '
-       __git_compute_porcelain_commands &&
-       verbose test -n "$__git_porcelain_commands" &&
-       . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
-       verbose test -z "$__git_porcelain_commands"
-'
-
 test_expect_success !GETTEXT_POISON 'sourcing the completion script clears cached merge strategies' '
        __git_compute_merge_strategies &&
        verbose test -n "$__git_merge_strategies" &&
index 8f5c811dd7d627ea5a0611be5161cea98deedb95..c3b89ae783d0b5511718be6d6b9d9978dd9131cd 100755 (executable)
@@ -148,7 +148,7 @@ test_expect_success 'prompt - inside .git directory' '
 test_expect_success 'prompt - deep inside .git directory' '
        printf " (GIT_DIR!)" >expected &&
        (
-               cd .git/refs/heads &&
+               cd .git/objects &&
                __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
index 7d620bf2a9a26c325de035c8d9d85323d5088357..2b2181dca09089ed36d10ee8f6f67eedda8cf352 100644 (file)
@@ -145,12 +145,28 @@ test_pause () {
        "$SHELL_PATH" <&6 >&5 2>&7
 }
 
-# Wrap git in gdb. Adding this to a command can make it easier to
-# understand what is going on in a failing test.
+# Wrap git with a debugger. Adding this to a command can make it easier
+# to understand what is going on in a failing test.
 #
-# Example: "debug git checkout master".
+# Examples:
+#     debug git checkout master
+#     debug --debugger=nemiver git $ARGS
+#     debug -d "valgrind --tool=memcheck --track-origins=yes" git $ARGS
 debug () {
-        GIT_TEST_GDB=1 "$@" <&6 >&5 2>&7
+       case "$1" in
+       -d)
+               GIT_DEBUGGER="$2" &&
+               shift 2
+               ;;
+       --debugger=*)
+               GIT_DEBUGGER="${1#*=}" &&
+               shift 1
+               ;;
+       *)
+               GIT_DEBUGGER=1
+               ;;
+       esac &&
+       GIT_DEBUGGER="${GIT_DEBUGGER}" "$@" <&6 >&5 2>&7
 }
 
 # Call test_commit with the arguments
@@ -278,8 +294,20 @@ write_script () {
 # The single parameter is the prerequisite tag (a simple word, in all
 # capital letters by convention).
 
+test_unset_prereq () {
+       ! test_have_prereq "$1" ||
+       satisfied_prereq="${satisfied_prereq% $1 *} ${satisfied_prereq#* $1 }"
+}
+
 test_set_prereq () {
-       satisfied_prereq="$satisfied_prereq$1 "
+       case "$1" in
+       !*)
+               test_unset_prereq "${1#!}"
+               ;;
+       *)
+               satisfied_prereq="$satisfied_prereq$1 "
+               ;;
+       esac
 }
 satisfied_prereq=" "
 lazily_testable_prereq= lazily_tested_prereq=
index ea2bbaaa7ab4dc77d0ef13cafbe45d1b12bd67e8..28315706be709d28c85785e389b381a8617337e3 100644 (file)
@@ -184,6 +184,8 @@ _x40="$_x35$_x05"
 # Zero SHA-1
 _z40=0000000000000000000000000000000000000000
 
+OID_REGEX="$_x40"
+ZERO_OID=$_z40
 EMPTY_TREE=4b825dc642cb6eb9a060e54bf8d69288fbee4904
 EMPTY_BLOB=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
 
@@ -195,7 +197,7 @@ LF='
 # when case-folding filenames
 u200c=$(printf '\342\200\214')
 
-export _x05 _x35 _x40 _z40 LF u200c EMPTY_TREE EMPTY_BLOB
+export _x05 _x35 _x40 _z40 LF u200c EMPTY_TREE EMPTY_BLOB ZERO_OID OID_REGEX
 
 # Each test should start with something like this, after copyright notices:
 #
@@ -1106,12 +1108,7 @@ test_lazy_prereq UTF8_NFD_TO_NFC '
        auml=$(printf "\303\244")
        aumlcdiar=$(printf "\141\314\210")
        >"$auml" &&
-       case "$(echo *)" in
-       "$aumlcdiar")
-               true ;;
-       *)
-               false ;;
-       esac
+       test -f "$aumlcdiar"
 '
 
 test_lazy_prereq AUTOIDENT '
@@ -1212,3 +1209,10 @@ test_lazy_prereq TIME_T_IS_64BIT 'test-tool date time_t-is64bit'
 test_lazy_prereq CURL '
        curl --version
 '
+
+# SHA1 is a test if the hash algorithm in use is SHA-1.  This is both for tests
+# which will not work with other hash algorithms and tests that work but don't
+# test anything meaningful (e.g. special values which cause short collisions).
+test_lazy_prereq SHA1 '
+       test $(git hash-object /dev/null) = e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
+'
diff --git a/tag.c b/tag.c
index 86b1dcbb8270e944ac643a39835d490bbaf35b20..3d37c1bd251c5f8c5eb06ede72ab57b323888709 100644 (file)
--- a/tag.c
+++ b/tag.c
@@ -41,7 +41,7 @@ int gpg_verify_tag(const struct object_id *oid, const char *name_to_report,
        unsigned long size;
        int ret;
 
-       type = oid_object_info(oid, NULL);
+       type = oid_object_info(the_repository, oid, NULL);
        if (type != OBJ_TAG)
                return error("%s: cannot verify a non-tag object of type %s.",
                                name_to_report ?
index 68d62d5446d963dc69006ad9a83907c6690f92c1..6a756416384c210ada2631f17862f5c01fffa478 100755 (executable)
@@ -12,7 +12,7 @@ then
        against=HEAD
 else
        # Initial commit: diff against an empty tree object
-       against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+       against=$(git hash-object -t tree /dev/null)
 fi
 
 # If you want to allow non-ASCII filenames set this variable to true.
index fea3f55545cbf193f823db468c7640bfd48d9bbd..91c00567f4d63367d6766808b0ff90f6faa08765 100644 (file)
@@ -127,7 +127,7 @@ struct tmp_objdir *tmp_objdir_create(void)
        struct tmp_objdir *t;
 
        if (the_tmp_objdir)
-               die("BUG: only one tmp_objdir can be used at a time");
+               BUG("only one tmp_objdir can be used at a time");
 
        t = xmalloc(sizeof(*t));
        strbuf_init(&t->path, 0);
index c508c9b7521b48c18f1b373ada9b9f2e5b02681d..4e309460d1367a7b35b61c2391525a165a3645e3 100644 (file)
--- a/trailer.c
+++ b/trailer.c
@@ -298,7 +298,7 @@ static void apply_arg_if_exists(struct trailer_item *in_tok,
                        free_arg_item(arg_tok);
                break;
        default:
-               die("BUG: trailer.c: unhandled value %d",
+               BUG("trailer.c: unhandled value %d",
                    arg_tok->conf.if_exists);
        }
 }
@@ -323,7 +323,7 @@ static void apply_arg_if_missing(struct list_head *head,
                        list_add(&to_add->list, head);
                break;
        default:
-               die("BUG: trailer.c: unhandled value %d",
+               BUG("trailer.c: unhandled value %d",
                    arg_tok->conf.if_missing);
        }
 }
@@ -557,7 +557,7 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)
                        warning(_("unknown value '%s' for key '%s'"), value, conf_key);
                break;
        default:
-               die("BUG: trailer.c: unhandled type %d", type);
+               BUG("trailer.c: unhandled type %d", type);
        }
        return 0;
 }
index cbf0044c3ecc4c78e2d94aaeb1aab7bca2423d87..a32da30dee6f4e38433b68d7c9f7e9404aa58858 100644 (file)
@@ -269,7 +269,7 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus
        switch (data->version) {
        case protocol_v2:
                get_remote_refs(data->fd[1], &reader, &refs, for_push,
-                               ref_prefixes);
+                               ref_prefixes, transport->server_options);
                break;
        case protocol_v1:
        case protocol_v0:
@@ -317,6 +317,7 @@ static int fetch_refs_via_pack(struct transport *transport,
        args.no_dependents = data->options.no_dependents;
        args.filter_options = data->options.filter_options;
        args.stateless_rpc = transport->stateless_rpc;
+       args.server_options = transport->server_options;
 
        if (!data->got_remote_heads)
                refs_tmp = get_refs_via_connect(transport, 0, NULL);
@@ -714,7 +715,7 @@ void transport_take_over(struct transport *transport,
        struct git_transport_data *data;
 
        if (!transport->smart_options)
-               die("BUG: taking over transport requires non-NULL "
+               BUG("taking over transport requires non-NULL "
                    "smart_options field.");
 
        data = xcalloc(1, sizeof(*data));
@@ -839,7 +840,7 @@ int is_transport_allowed(const char *type, int from_user)
                return from_user;
        }
 
-       die("BUG: invalid protocol_allow_config type");
+       BUG("invalid protocol_allow_config type");
 }
 
 void transport_check_allowed(const char *type)
index bac085ce0e9a00cafba2a1edcd39a70acd13d300..7792b08582c132e18b495a21b700d9113727c7eb 100644 (file)
@@ -71,6 +71,12 @@ struct transport {
         */
        const struct string_list *push_options;
 
+       /*
+        * These strings will be passed to the remote side on each command
+        * request, if both sides support the server-option capability.
+        */
+       const struct string_list *server_options;
+
        char *pack_lockfile;
        signed verbose : 3;
        /**
index e11b3063afa610239162dc45c24528dd144c4759..8f5090862b5193db135f18854974b21e75eb8ae4 100644 (file)
@@ -105,7 +105,7 @@ static void entry_extract(struct tree_desc *t, struct name_entry *a)
 static int update_tree_entry_internal(struct tree_desc *desc, struct strbuf *err)
 {
        const void *buf = desc->buffer;
-       const unsigned char *end = desc->entry.oid->hash + 20;
+       const unsigned char *end = desc->entry.oid->hash + the_hash_algo->rawsz;
        unsigned long size = desc->size;
        unsigned long len = end - (const unsigned char *)buf;
 
@@ -488,7 +488,7 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
 struct dir_state {
        void *tree;
        unsigned long size;
-       unsigned char sha1[20];
+       struct object_id oid;
 };
 
 static int find_tree_entry(struct tree_desc *t, const char *name, struct object_id *result, unsigned *mode)
@@ -576,7 +576,7 @@ int get_tree_entry(const struct object_id *tree_oid, const char *name, struct ob
  * See the code for enum follow_symlink_result for a description of
  * the return values.
  */
-enum follow_symlinks_result get_tree_entry_follow_symlinks(unsigned char *tree_sha1, const char *name, unsigned char *result, struct strbuf *result_path, unsigned *mode)
+enum follow_symlinks_result get_tree_entry_follow_symlinks(struct object_id *tree_oid, const char *name, struct object_id *result, struct strbuf *result_path, unsigned *mode)
 {
        int retval = MISSING_OBJECT;
        struct dir_state *parents = NULL;
@@ -589,7 +589,7 @@ enum follow_symlinks_result get_tree_entry_follow_symlinks(unsigned char *tree_s
 
        init_tree_desc(&t, NULL, 0UL);
        strbuf_addstr(&namebuf, name);
-       hashcpy(current_tree_oid.hash, tree_sha1);
+       oidcpy(&current_tree_oid, tree_oid);
 
        while (1) {
                int find_result;
@@ -609,11 +609,11 @@ enum follow_symlinks_result get_tree_entry_follow_symlinks(unsigned char *tree_s
                        ALLOC_GROW(parents, parents_nr + 1, parents_alloc);
                        parents[parents_nr].tree = tree;
                        parents[parents_nr].size = size;
-                       hashcpy(parents[parents_nr].sha1, root.hash);
+                       oidcpy(&parents[parents_nr].oid, &root);
                        parents_nr++;
 
                        if (namebuf.buf[0] == '\0') {
-                               hashcpy(result, root.hash);
+                               oidcpy(result, &root);
                                retval = FOUND;
                                goto done;
                        }
@@ -663,7 +663,7 @@ enum follow_symlinks_result get_tree_entry_follow_symlinks(unsigned char *tree_s
 
                /* We could end up here via a symlink to dir/.. */
                if (namebuf.buf[0] == '\0') {
-                       hashcpy(result, parents[parents_nr - 1].sha1);
+                       oidcpy(result, &parents[parents_nr - 1].oid);
                        retval = FOUND;
                        goto done;
                }
@@ -677,7 +677,7 @@ enum follow_symlinks_result get_tree_entry_follow_symlinks(unsigned char *tree_s
 
                if (S_ISDIR(*mode)) {
                        if (!remainder) {
-                               hashcpy(result, current_tree_oid.hash);
+                               oidcpy(result, &current_tree_oid);
                                retval = FOUND;
                                goto done;
                        }
@@ -687,7 +687,7 @@ enum follow_symlinks_result get_tree_entry_follow_symlinks(unsigned char *tree_s
                                      1 + first_slash - namebuf.buf);
                } else if (S_ISREG(*mode)) {
                        if (!remainder) {
-                               hashcpy(result, current_tree_oid.hash);
+                               oidcpy(result, &current_tree_oid);
                                retval = FOUND;
                        } else {
                                retval = NOT_DIR;
index 4617deeb0e09e71c7ba7231192ece055eccb2dde..805f58f00f6f0d4c9e6f6f05520bd1cd62d5f661 100644 (file)
@@ -64,7 +64,7 @@ enum follow_symlinks_result {
                       */
 };
 
-enum follow_symlinks_result get_tree_entry_follow_symlinks(unsigned char *tree_sha1, const char *name, unsigned char *result, struct strbuf *result_path, unsigned *mode);
+enum follow_symlinks_result get_tree_entry_follow_symlinks(struct object_id *tree_oid, const char *name, struct object_id *result, struct strbuf *result_path, unsigned *mode);
 
 struct traverse_info {
        const char *traverse_path;
diff --git a/tree.c b/tree.c
index 1c68ea586bd30d3e3389efa1c83f25ed607d1d80..244eb5e665e931a6b735366d74b5ed2bcbce4c9b 100644 (file)
--- a/tree.c
+++ b/tree.c
@@ -109,7 +109,7 @@ static int read_tree_1(struct tree *tree, struct strbuf *base,
                                    oid_to_hex(entry.oid),
                                    base->buf, entry.path);
 
-                       oidcpy(&oid, &commit->tree->object.oid);
+                       oidcpy(&oid, get_commit_tree_oid(commit));
                }
                else
                        continue;
@@ -248,7 +248,7 @@ struct tree *parse_tree_indirect(const struct object_id *oid)
                if (obj->type == OBJ_TREE)
                        return (struct tree *) obj;
                else if (obj->type == OBJ_COMMIT)
-                       obj = &(((struct commit *) obj)->tree->object);
+                       obj = &(get_commit_tree(((struct commit *)obj))->object);
                else if (obj->type == OBJ_TAG)
                        obj = ((struct tag *) obj)->tagged;
                else
index dec37ad1e7409aa6814cd22c914e05bcd9babf1d..3a85a02a7733aeeee86da1a324319d026c32d787 100644 (file)
@@ -1,5 +1,6 @@
 #define NO_THE_INDEX_COMPATIBILITY_MACROS
 #include "cache.h"
+#include "argv-array.h"
 #include "repository.h"
 #include "config.h"
 #include "dir.h"
@@ -103,6 +104,8 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
        const char **msgs = opts->msgs;
        const char *msg;
 
+       argv_array_init(&opts->msgs_to_free);
+
        if (!strcmp(cmd, "checkout"))
                msg = advice_commit_before_merge
                      ? _("Your local changes to the following files would be overwritten by checkout:\n%%s"
@@ -119,7 +122,7 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
                          "Please commit your changes or stash them before you %s.")
                      : _("Your local changes to the following files would be overwritten by %s:\n%%s");
        msgs[ERROR_WOULD_OVERWRITE] = msgs[ERROR_NOT_UPTODATE_FILE] =
-               xstrfmt(msg, cmd, cmd);
+               argv_array_pushf(&opts->msgs_to_free, msg, cmd, cmd);
 
        msgs[ERROR_NOT_UPTODATE_DIR] =
                _("Updating the following directories would lose untracked files in them:\n%s");
@@ -139,7 +142,8 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
                      ? _("The following untracked working tree files would be removed by %s:\n%%s"
                          "Please move or remove them before you %s.")
                      : _("The following untracked working tree files would be removed by %s:\n%%s");
-       msgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED] = xstrfmt(msg, cmd, cmd);
+       msgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED] =
+               argv_array_pushf(&opts->msgs_to_free, msg, cmd, cmd);
 
        if (!strcmp(cmd, "checkout"))
                msg = advice_commit_before_merge
@@ -156,7 +160,8 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
                      ? _("The following untracked working tree files would be overwritten by %s:\n%%s"
                          "Please move or remove them before you %s.")
                      : _("The following untracked working tree files would be overwritten by %s:\n%%s");
-       msgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] = xstrfmt(msg, cmd, cmd);
+       msgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] =
+               argv_array_pushf(&opts->msgs_to_free, msg, cmd, cmd);
 
        /*
         * Special case: ERROR_BIND_OVERLAP refers to a pair of paths, we
@@ -179,6 +184,12 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
                opts->unpack_rejects[i].strdup_strings = 1;
 }
 
+void clear_unpack_trees_porcelain(struct unpack_trees_options *opts)
+{
+       argv_array_clear(&opts->msgs_to_free);
+       memset(opts->msgs, 0, sizeof(opts->msgs));
+}
+
 static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
                         unsigned int set, unsigned int clear)
 {
@@ -398,7 +409,7 @@ static int check_updates(struct unpack_trees_options *o)
 
                if (ce->ce_flags & CE_UPDATE) {
                        if (ce->ce_flags & CE_WT_REMOVE)
-                               die("BUG: both update and delete flags are set on %s",
+                               BUG("both update and delete flags are set on %s",
                                    ce->name);
                        display_progress(progress, ++cnt);
                        ce->ce_flags &= ~CE_UPDATE;
@@ -1284,10 +1295,21 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
        o->result.timestamp.sec = o->src_index->timestamp.sec;
        o->result.timestamp.nsec = o->src_index->timestamp.nsec;
        o->result.version = o->src_index->version;
-       o->result.split_index = o->src_index->split_index;
-       if (o->result.split_index)
+       if (!o->src_index->split_index) {
+               o->result.split_index = NULL;
+       } else if (o->src_index == o->dst_index) {
+               /*
+                * o->dst_index (and thus o->src_index) will be discarded
+                * and overwritten with o->result at the end of this function,
+                * so just use src_index's split_index to avoid having to
+                * create a new one.
+                */
+               o->result.split_index = o->src_index->split_index;
                o->result.split_index->refcount++;
-       hashcpy(o->result.sha1, o->src_index->sha1);
+       } else {
+               o->result.split_index = init_split_index(&o->result);
+       }
+       oidcpy(&o->result.oid, &o->src_index->oid);
        o->merge_size = len;
        mark_all_ce_unused(o->src_index);
 
@@ -1401,7 +1423,6 @@ 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) {
@@ -1412,12 +1433,13 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
                                                  WRITE_TREE_SILENT |
                                                  WRITE_TREE_REPAIR);
                }
-               move_index_extensions(&o->result, o->dst_index);
+               move_index_extensions(&o->result, o->src_index);
                discard_index(o->dst_index);
                *o->dst_index = o->result;
        } else {
                discard_index(&o->result);
        }
+       o->src_index = NULL;
 
 done:
        clear_exclude_list(&el);
@@ -1509,8 +1531,8 @@ static int verify_uptodate_1(const struct cache_entry *ce,
                add_rejected_path(o, error_type, ce->name);
 }
 
-static int verify_uptodate(const struct cache_entry *ce,
-                          struct unpack_trees_options *o)
+int verify_uptodate(const struct cache_entry *ce,
+                   struct unpack_trees_options *o)
 {
        if (!o->skip_sparse_checkout && (ce->ce_flags & CE_NEW_SKIP_WORKTREE))
                return 0;
index 6c48117b845fbf7b983852be302e4472c5e6d651..c2b434c60648fb0e287c100a32d9346a765ab74f 100644 (file)
@@ -1,7 +1,8 @@
 #ifndef UNPACK_TREES_H
 #define UNPACK_TREES_H
 
-#include "string-list.h"
+#include "tree-walk.h"
+#include "argv-array.h"
 
 #define MAX_UNPACK_TREES 8
 
@@ -32,6 +33,11 @@ enum unpack_trees_error_types {
 void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
                                  const char *cmd);
 
+/*
+ * Frees resources allocated by setup_unpack_trees_porcelain().
+ */
+void clear_unpack_trees_porcelain(struct unpack_trees_options *opts);
+
 struct unpack_trees_options {
        unsigned int reset,
                     merge,
@@ -56,6 +62,7 @@ struct unpack_trees_options {
        struct pathspec *pathspec;
        merge_fn_t fn;
        const char *msgs[NB_UNPACK_TREES_ERROR_TYPES];
+       struct argv_array msgs_to_free;
        /*
         * Store error messages in an array, each case
         * corresponding to a error message type
@@ -78,6 +85,9 @@ struct unpack_trees_options {
 extern int unpack_trees(unsigned n, struct tree_desc *t,
                struct unpack_trees_options *options);
 
+int verify_uptodate(const struct cache_entry *ce,
+                   struct unpack_trees_options *o);
+
 int threeway_merge(const struct cache_entry * const *stages,
                   struct unpack_trees_options *o);
 int twoway_merge(const struct cache_entry * const *src,
index 87b4d32a6e23aed2416a9bb752dfa56b916b141c..87c6722ea58207a9b369e75f7cf7ac16a705330b 100644 (file)
@@ -444,7 +444,7 @@ static int get_common_commits(void)
                                break;
                        default:
                                got_common = 1;
-                               memcpy(last_hex, oid_to_hex(&oid), 41);
+                               oid_to_hex_r(last_hex, &oid);
                                if (multi_ack == 2)
                                        packet_write_fmt(1, "ACK %s common\n", last_hex);
                                else if (multi_ack)
@@ -486,7 +486,7 @@ static int do_reachable_revlist(struct child_process *cmd,
                "rev-list", "--stdin", NULL,
        };
        struct object *o;
-       char namebuf[42]; /* ^ + SHA-1 + LF */
+       char namebuf[GIT_MAX_HEXSZ + 2]; /* ^ + hash + LF */
        int i;
 
        cmd->argv = argv;
@@ -555,15 +555,17 @@ static int get_reachable_list(struct object_array *src,
        struct child_process cmd = CHILD_PROCESS_INIT;
        int i;
        struct object *o;
-       char namebuf[42]; /* ^ + SHA-1 + LF */
+       char namebuf[GIT_MAX_HEXSZ + 2]; /* ^ + hash + LF */
+       const unsigned hexsz = the_hash_algo->hexsz;
 
        if (do_reachable_revlist(&cmd, src, reachable) < 0)
                return -1;
 
-       while ((i = read_in_full(cmd.out, namebuf, 41)) == 41) {
+       while ((i = read_in_full(cmd.out, namebuf, hexsz + 1)) == hexsz + 1) {
                struct object_id sha1;
+               const char *p;
 
-               if (namebuf[40] != '\n' || get_oid_hex(namebuf, &sha1))
+               if (parse_oid_hex(namebuf, &sha1, &p) || *p != '\n')
                        break;
 
                o = lookup_object(sha1.hash);
@@ -895,11 +897,9 @@ static void receive_needs(void)
                }
 
                if (!skip_prefix(line, "want ", &arg) ||
-                   get_oid_hex(arg, &oid_buf))
+                   parse_oid_hex(arg, &oid_buf, &features))
                        die("git upload-pack: protocol error, "
-                           "expected to get sha, not '%s'", line);
-
-               features = arg + 40;
+                           "expected to get object ID, not '%s'", line);
 
                if (parse_feature_request(features, "deepen-relative"))
                        deepen_relative = 1;
@@ -1205,6 +1205,7 @@ static void process_args(struct packet_reader *request,
 {
        while (packet_reader_read(request) != PACKET_READ_FLUSH) {
                const char *arg = request->line;
+               const char *p;
 
                /* process want */
                if (parse_want(arg))
@@ -1251,8 +1252,13 @@ static void process_args(struct packet_reader *request,
                        continue;
                }
 
+               if (allow_filter && skip_prefix(arg, "filter ", &p)) {
+                       parse_list_objects_filter(&filter_options, p);
+                       continue;
+               }
+
                /* ignore unknown lines maybe? */
-               die("unexpect line: '%s'", arg);
+               die("unexpected line: '%s'", arg);
        }
 }
 
@@ -1376,6 +1382,8 @@ int upload_pack_v2(struct repository *r, struct argv_array *keys,
        enum fetch_state state = FETCH_PROCESS_ARGS;
        struct upload_pack_data data;
 
+       git_config(upload_pack_config, NULL);
+
        upload_pack_data_init(&data);
        use_sideband = LARGE_PACKET_MAX;
 
@@ -1428,7 +1436,14 @@ int upload_pack_v2(struct repository *r, struct argv_array *keys,
 int upload_pack_advertise(struct repository *r,
                          struct strbuf *value)
 {
-       if (value)
+       if (value) {
+               int allow_filter_value;
                strbuf_addstr(value, "shallow");
+               if (!repo_config_get_bool(the_repository,
+                                        "uploadpack.allowfilter",
+                                        &allow_filter_value) &&
+                   allow_filter_value)
+                       strbuf_addstr(value, " filter");
+       }
        return 1;
 }
diff --git a/usage.c b/usage.c
index cdd534c9dfc4bd38ce112da62643ab2dc7b4fbe9..cc803336bd5e6761b7fd50c33dba5aa9f734c4ba 100644 (file)
--- a/usage.c
+++ b/usage.c
@@ -148,6 +148,7 @@ static const char *fmt_with_err(char *buf, int n, const char *fmt)
                }
        }
        str_error[j] = 0;
+       /* Truncation is acceptable here */
        snprintf(buf, n, "%s: %s", fmt, str_error);
        return buf;
 }
@@ -210,6 +211,9 @@ void warning(const char *warn, ...)
        va_end(params);
 }
 
+/* Only set this, ever, from t/helper/, when verifying that bugs are caught. */
+int BUG_exit_code;
+
 static NORETURN void BUG_vfl(const char *file, int line, const char *fmt, va_list params)
 {
        char prefix[256];
@@ -221,6 +225,8 @@ static NORETURN void BUG_vfl(const char *file, int line, const char *fmt, va_lis
                snprintf(prefix, sizeof(prefix), "BUG: ");
 
        vreportf(prefix, fmt, params);
+       if (BUG_exit_code)
+               exit(BUG_exit_code);
        abort();
 }
 
diff --git a/utf8.c b/utf8.c
index 0fcc6487e3d8b4a4af81c92148fb4edb9574f524..d55e20c6415cb53ef66e1edd0a5e7dc470e18052 100644 (file)
--- a/utf8.c
+++ b/utf8.c
@@ -681,28 +681,33 @@ static ucs_char_t next_hfs_char(const char **in)
        }
 }
 
-int is_hfs_dotgit(const char *path)
+static int is_hfs_dot_generic(const char *path,
+                             const char *needle, size_t needle_len)
 {
        ucs_char_t c;
 
        c = next_hfs_char(&path);
        if (c != '.')
                return 0;
-       c = next_hfs_char(&path);
 
        /*
         * there's a great deal of other case-folding that occurs
-        * in HFS+, but this is enough to catch anything that will
-        * convert to ".git"
+        * in HFS+, but this is enough to catch our fairly vanilla
+        * hard-coded needles.
         */
-       if (c != 'g' && c != 'G')
-               return 0;
-       c = next_hfs_char(&path);
-       if (c != 'i' && c != 'I')
-               return 0;
-       c = next_hfs_char(&path);
-       if (c != 't' && c != 'T')
-               return 0;
+       for (; needle_len > 0; needle++, needle_len--) {
+               c = next_hfs_char(&path);
+
+               /*
+                * We know our needles contain only ASCII, so we clamp here to
+                * make the results of tolower() sane.
+                */
+               if (c > 127)
+                       return 0;
+               if (tolower(c) != *needle)
+                       return 0;
+       }
+
        c = next_hfs_char(&path);
        if (c && !is_dir_sep(c))
                return 0;
@@ -710,6 +715,35 @@ int is_hfs_dotgit(const char *path)
        return 1;
 }
 
+/*
+ * Inline wrapper to make sure the compiler resolves strlen() on literals at
+ * compile time.
+ */
+static inline int is_hfs_dot_str(const char *path, const char *needle)
+{
+       return is_hfs_dot_generic(path, needle, strlen(needle));
+}
+
+int is_hfs_dotgit(const char *path)
+{
+       return is_hfs_dot_str(path, "git");
+}
+
+int is_hfs_dotgitmodules(const char *path)
+{
+       return is_hfs_dot_str(path, "gitmodules");
+}
+
+int is_hfs_dotgitignore(const char *path)
+{
+       return is_hfs_dot_str(path, "gitignore");
+}
+
+int is_hfs_dotgitattributes(const char *path)
+{
+       return is_hfs_dot_str(path, "gitattributes");
+}
+
 const char utf8_bom[] = "\357\273\277";
 
 int skip_utf8_bom(char **text, size_t len)
diff --git a/utf8.h b/utf8.h
index cce654a64a2012bd8a6c9d1ba2070500b5b3ba8a..db73a2d8d374e7570471627693ecddc87d998f10 100644 (file)
--- a/utf8.h
+++ b/utf8.h
@@ -52,8 +52,13 @@ int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding);
  * The path should be NUL-terminated, but we will match variants of both ".git\0"
  * and ".git/..." (but _not_ ".../.git"). This makes it suitable for both fsck
  * and verify_path().
+ *
+ * Likewise, the is_hfs_dotgitfoo() variants look for ".gitfoo".
  */
 int is_hfs_dotgit(const char *path);
+int is_hfs_dotgitmodules(const char *path);
+int is_hfs_dotgitignore(const char *path);
+int is_hfs_dotgitattributes(const char *path);
 
 typedef enum {
        ALIGN_LEFT,
index 3fd047a8b82b1c2a6b32ec0a5ded3d67913b5be0..b5b8913cb0f2ceae60c18123d979e2948c568151 100644 (file)
@@ -320,7 +320,8 @@ const char *fast_export_read_path(const char *path, uint32_t *mode_out)
        err = fast_export_ls(path, mode_out, &buf);
        if (err) {
                if (errno != ENOENT)
-                       die_errno("BUG: unexpected fast_export_ls error");
+                       BUG("unexpected fast_export_ls error: %s",
+                           strerror(errno));
                /* Treat missing paths as directories. */
                *mode_out = S_IFDIR;
                return NULL;
@@ -338,7 +339,8 @@ void fast_export_copy(uint32_t revision, const char *src, const char *dst)
        err = fast_export_ls_rev(revision, src, &mode, &data);
        if (err) {
                if (errno != ENOENT)
-                       die_errno("BUG: unexpected fast_export_ls_rev error");
+                       BUG("unexpected fast_export_ls_rev error: %s",
+                           strerror(errno));
                fast_export_delete(dst);
                return;
        }
index 8eb7db7a52782c9862718979e5270724fd982d1f..0b162a09b95a3eadeef47f3e6d314d386215cc9f 100644 (file)
--- a/walker.c
+++ b/walker.c
@@ -88,7 +88,7 @@ static int process_commit(struct walker *walker, struct commit *commit)
 
        walker_say(walker, "walk %s\n", oid_to_hex(&commit->object.oid));
 
-       if (process(walker, &commit->tree->object))
+       if (process(walker, &get_commit_tree(commit)->object))
                return -1;
 
        for (parents = commit->parents; parents; parents = parents->next) {
index 28989cf06ef4bd15b3b97f86ae9fd32c4b78401f..97cda5f97bbc10b2f5690b945ab42d2beef74e2e 100644 (file)
@@ -338,7 +338,7 @@ void update_worktree_location(struct worktree *wt, const char *path_)
        struct strbuf path = STRBUF_INIT;
 
        if (is_main_worktree(wt))
-               die("BUG: can't relocate main worktree");
+               BUG("can't relocate main worktree");
 
        strbuf_realpath(&path, path_, 1);
        if (fspathcmp(wt->path, path.buf)) {
index 5842408817aa7e5c584f244a7625cf458882d568..95851b85b6b7181130f0cd441c2bd7ac0bfb89da 100644 (file)
@@ -20,10 +20,17 @@ PATH='@@BUILD_DIR@@/bin-wrappers:'"$PATH"
 
 export GIT_EXEC_PATH GITPERLLIB PATH GIT_TEXTDOMAINDIR
 
-if test -n "$GIT_TEST_GDB"
-then
-       unset GIT_TEST_GDB
-       exec gdb --args "${GIT_EXEC_PATH}/@@PROG@@" "$@"
-else
+case "$GIT_DEBUGGER" in
+'')
        exec "${GIT_EXEC_PATH}/@@PROG@@" "$@"
-fi
+       ;;
+1)
+       unset GIT_DEBUGGER
+       exec gdb --args "${GIT_EXEC_PATH}/@@PROG@@" "$@"
+       ;;
+*)
+       GIT_DEBUGGER_ARGS="$GIT_DEBUGGER"
+       unset GIT_DEBUGGER
+       exec ${GIT_DEBUGGER_ARGS} "${GIT_EXEC_PATH}/@@PROG@@" "$@"
+       ;;
+esac
index 1fd5e33ea8f5c8cb4e6720b0bec74acf09232e3c..e4fa9d84cd076770306114ff4f284155a5cdd2a1 100644 (file)
--- a/wrapper.c
+++ b/wrapper.c
@@ -643,9 +643,9 @@ int xsnprintf(char *dst, size_t max, const char *fmt, ...)
        va_end(ap);
 
        if (len < 0)
-               die("BUG: your snprintf is broken");
+               BUG("your snprintf is broken");
        if (len >= max)
-               die("BUG: attempt to snprintf into too-small buffer");
+               BUG("attempt to snprintf into too-small buffer");
        return len;
 }
 
index 50815e5faffba0e9df861dbc4881e9fbffd7a941..d1c05145a4bded06a7fccc60543193d2f5181953 100644 (file)
@@ -138,6 +138,9 @@ void wt_status_prepare(struct wt_status *s)
        s->show_stash = 0;
        s->ahead_behind_flags = AHEAD_BEHIND_UNSPECIFIED;
        s->display_comment_prefix = 0;
+       s->detect_rename = -1;
+       s->rename_score = -1;
+       s->rename_limit = -1;
 }
 
 static void wt_longstatus_print_unmerged_header(struct wt_status *s)
@@ -264,7 +267,7 @@ static const char *wt_status_unmerged_status_string(int stagemask)
        case 7:
                return _("both modified:");
        default:
-               die("BUG: unhandled unmerged status %x", stagemask);
+               BUG("unhandled unmerged status %x", stagemask);
        }
 }
 
@@ -377,7 +380,7 @@ static void wt_longstatus_print_change_data(struct wt_status *s,
                status = d->worktree_status;
                break;
        default:
-               die("BUG: unhandled change_type %d in wt_longstatus_print_change_data",
+               BUG("unhandled change_type %d in wt_longstatus_print_change_data",
                    change_type);
        }
 
@@ -395,7 +398,7 @@ static void wt_longstatus_print_change_data(struct wt_status *s,
        status_printf(s, color(WT_STATUS_HEADER, s), "\t");
        what = wt_status_diff_status_string(status);
        if (!what)
-               die("BUG: unhandled diff status %c", status);
+               BUG("unhandled diff status %c", status);
        len = label_width - utf8_strwidth(what);
        assert(len >= 0);
        if (one_name != two_name)
@@ -470,7 +473,7 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q,
                case DIFF_STATUS_COPIED:
                case DIFF_STATUS_RENAMED:
                        if (d->rename_status)
-                               die("BUG: multiple renames on the same target? how?");
+                               BUG("multiple renames on the same target? how?");
                        d->rename_source = xstrdup(p->one->path);
                        d->rename_score = p->score * 100 / MAX_SCORE;
                        d->rename_status = p->status;
@@ -484,7 +487,7 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q,
                        break;
 
                default:
-                       die("BUG: unhandled diff-files status '%c'", p->status);
+                       BUG("unhandled diff-files status '%c'", p->status);
                        break;
                }
 
@@ -547,7 +550,7 @@ static void wt_status_collect_updated_cb(struct diff_queue_struct *q,
                case DIFF_STATUS_COPIED:
                case DIFF_STATUS_RENAMED:
                        if (d->rename_status)
-                               die("BUG: multiple renames on the same target? how?");
+                               BUG("multiple renames on the same target? how?");
                        d->rename_source = xstrdup(p->one->path);
                        d->rename_score = p->score * 100 / MAX_SCORE;
                        d->rename_status = p->status;
@@ -569,7 +572,7 @@ static void wt_status_collect_updated_cb(struct diff_queue_struct *q,
                        break;
 
                default:
-                       die("BUG: unhandled diff-index status '%c'", p->status);
+                       BUG("unhandled diff-index status '%c'", p->status);
                        break;
                }
        }
@@ -592,6 +595,9 @@ static void wt_status_collect_changes_worktree(struct wt_status *s)
        }
        rev.diffopt.format_callback = wt_status_collect_changed_cb;
        rev.diffopt.format_callback_data = s;
+       rev.diffopt.detect_rename = s->detect_rename >= 0 ? s->detect_rename : rev.diffopt.detect_rename;
+       rev.diffopt.rename_limit = s->rename_limit >= 0 ? s->rename_limit : rev.diffopt.rename_limit;
+       rev.diffopt.rename_score = s->rename_score >= 0 ? s->rename_score : rev.diffopt.rename_score;
        copy_pathspec(&rev.prune_data, &s->pathspec);
        run_diff_files(&rev, 0);
 }
@@ -603,7 +609,7 @@ static void wt_status_collect_changes_index(struct wt_status *s)
 
        init_revisions(&rev, NULL);
        memset(&opt, 0, sizeof(opt));
-       opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference;
+       opt.def = s->is_initial ? empty_tree_oid_hex() : s->reference;
        setup_revisions(0, NULL, &rev, &opt);
 
        rev.diffopt.flags.override_submodule_config = 1;
@@ -625,9 +631,9 @@ static void wt_status_collect_changes_index(struct wt_status *s)
        rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
        rev.diffopt.format_callback = wt_status_collect_updated_cb;
        rev.diffopt.format_callback_data = s;
-       rev.diffopt.detect_rename = DIFF_DETECT_RENAME;
-       rev.diffopt.rename_limit = 200;
-       rev.diffopt.break_opt = 0;
+       rev.diffopt.detect_rename = s->detect_rename >= 0 ? s->detect_rename : rev.diffopt.detect_rename;
+       rev.diffopt.rename_limit = s->rename_limit >= 0 ? s->rename_limit : rev.diffopt.rename_limit;
+       rev.diffopt.rename_score = s->rename_score >= 0 ? s->rename_score : rev.diffopt.rename_score;
        copy_pathspec(&rev.prune_data, &s->pathspec);
        run_diff_index(&rev, 1);
 }
@@ -981,11 +987,13 @@ static void wt_longstatus_print_verbose(struct wt_status *s)
        rev.diffopt.ita_invisible_in_index = 1;
 
        memset(&opt, 0, sizeof(opt));
-       opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference;
+       opt.def = s->is_initial ? empty_tree_oid_hex() : s->reference;
        setup_revisions(0, NULL, &rev, &opt);
 
        rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
-       rev.diffopt.detect_rename = DIFF_DETECT_RENAME;
+       rev.diffopt.detect_rename = s->detect_rename >= 0 ? s->detect_rename : rev.diffopt.detect_rename;
+       rev.diffopt.rename_limit = s->rename_limit >= 0 ? s->rename_limit : rev.diffopt.rename_limit;
+       rev.diffopt.rename_score = s->rename_score >= 0 ? s->rename_score : rev.diffopt.rename_score;
        rev.diffopt.file = s->fp;
        rev.diffopt.close_file = 0;
        /*
@@ -2158,7 +2166,7 @@ static void wt_porcelain_v2_print_unmerged_entry(
        case 6: key = "AA"; break; /* both added */
        case 7: key = "UU"; break; /* both modified */
        default:
-               die("BUG: unhandled unmerged status %x", d->stagemask);
+               BUG("unhandled unmerged status %x", d->stagemask);
        }
 
        /*
@@ -2185,7 +2193,7 @@ static void wt_porcelain_v2_print_unmerged_entry(
                sum |= (1 << (stage - 1));
        }
        if (sum != d->stagemask)
-               die("BUG: observed stagemask 0x%x != expected stagemask 0x%x", sum, d->stagemask);
+               BUG("observed stagemask 0x%x != expected stagemask 0x%x", sum, d->stagemask);
 
        if (s->null_termination)
                path_index = it->string;
@@ -2289,7 +2297,7 @@ void wt_status_print(struct wt_status *s)
                wt_porcelain_v2_print(s);
                break;
        case STATUS_FORMAT_UNSPECIFIED:
-               die("BUG: finalize_deferred_config() should have been called");
+               BUG("finalize_deferred_config() should have been called");
                break;
        case STATUS_FORMAT_NONE:
        case STATUS_FORMAT_LONG:
index 430770b854c41b38b95dae6e8fa8943629a2309b..1673d146fa2f1e15eabccd1c6b12d0e74b5f0870 100644 (file)
@@ -89,7 +89,9 @@ struct wt_status {
        int show_stash;
        int hints;
        enum ahead_behind_flags ahead_behind_flags;
-
+       int detect_rename;
+       int rename_score;
+       int rename_limit;
        enum wt_status_format status_format;
        unsigned char sha1_commit[GIT_MAX_RAWSZ]; /* when not Initial */
 
diff --git a/zlib.c b/zlib.c
index 4223f1a8c57a281d5e41c329a4f983a7bb3b57a0..d594cba3fc9d82d94b9277e886f2bee265e552f6 100644 (file)
--- a/zlib.c
+++ b/zlib.c
@@ -52,9 +52,9 @@ static void zlib_post_call(git_zstream *s)
        bytes_consumed = s->z.next_in - s->next_in;
        bytes_produced = s->z.next_out - s->next_out;
        if (s->z.total_out != s->total_out + bytes_produced)
-               die("BUG: total_out mismatch");
+               BUG("total_out mismatch");
        if (s->z.total_in != s->total_in + bytes_consumed)
-               die("BUG: total_in mismatch");
+               BUG("total_in mismatch");
 
        s->total_out = s->z.total_out;
        s->total_in = s->z.total_in;