Code

Merge branch 'fk/use-kwset-pickaxe-grep-f'
authorJunio C Hamano <gitster@pobox.com>
Wed, 5 Oct 2011 19:36:22 +0000 (12:36 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 5 Oct 2011 19:36:22 +0000 (12:36 -0700)
* fk/use-kwset-pickaxe-grep-f:
  obstack.c: Fix some sparse warnings
  sparse: Fix an "Using plain integer as NULL pointer" warning

229 files changed:
Documentation/RelNotes/1.7.6.1.txt
Documentation/RelNotes/1.7.6.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.6.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.6.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.7.txt
Documentation/SubmittingPatches
Documentation/config.txt
Documentation/git-branch.txt
Documentation/git-cherry-pick.txt
Documentation/git-clean.txt
Documentation/git-daemon.txt
Documentation/git-fast-import.txt
Documentation/git-for-each-ref.txt
Documentation/git-format-patch.txt
Documentation/git-http-fetch.txt
Documentation/git-log.txt
Documentation/git-notes.txt
Documentation/git-push.txt
Documentation/git-receive-pack.txt
Documentation/git-remote-helpers.txt
Documentation/git-remote-testgit.txt [new file with mode: 0644]
Documentation/git-revert.txt
Documentation/git-send-pack.txt
Documentation/git-svn.txt
Documentation/git.txt
Documentation/gitnamespaces.txt
Documentation/gitrepository-layout.txt
Documentation/howto/maintain-git.txt
Documentation/sequencer.txt [new file with mode: 0644]
Documentation/technical/api-ref-iteration.txt [new file with mode: 0644]
Documentation/technical/api-string-list.txt
GIT-VERSION-GEN
Makefile
abspath.c
advice.c
advice.h
attr.c
branch.c
branch.h
builtin/branch.c
builtin/bundle.c
builtin/check-ref-format.c
builtin/checkout.c
builtin/clean.c
builtin/clone.c
builtin/commit.c
builtin/config.c
builtin/describe.c
builtin/diff.c
builtin/fetch-pack.c
builtin/fetch.c
builtin/for-each-ref.c
builtin/fsck.c
builtin/grep.c
builtin/init-db.c
builtin/log.c
builtin/ls-files.c
builtin/merge.c
builtin/pack-objects.c
builtin/patch-id.c
builtin/push.c
builtin/receive-pack.c
builtin/remote.c
builtin/rev-list.c
builtin/revert.c
builtin/send-pack.c
builtin/show-branch.c
builtin/update-ref.c
bundle.c
bundle.h
cache.h
color.c
color.h
combine-diff.c
commit.c
commit.h
config.c
connected.c [new file with mode: 0644]
connected.h [new file with mode: 0644]
contrib/fast-import/git-p4
contrib/fast-import/git-p4.txt
contrib/hooks/post-receive-email
convert.c
date.c
diff-lib.c
diff.c
diff.h
diffcore.h
environment.c
fast-import.c
fsck.c
git-am.sh
git-bisect.sh
git-difftool--helper.sh
git-filter-branch.sh
git-mergetool--lib.sh
git-mergetool.sh
git-pull.sh
git-rebase--interactive.sh
git-remote-testgit.py
git-sh-i18n.sh
git-stash.sh
git-submodule.sh
git-svn.perl
git.c
graph.c
grep.c
http-fetch.c
http-push.c
list-objects.c
list-objects.h
log-tree.c
merge-recursive.c
merge-recursive.h
mergetools/araxis [new file with mode: 0644]
mergetools/bc3 [new file with mode: 0644]
mergetools/defaults [new file with mode: 0644]
mergetools/diffuse [new file with mode: 0644]
mergetools/ecmerge [new file with mode: 0644]
mergetools/emerge [new file with mode: 0644]
mergetools/kdiff3 [new file with mode: 0644]
mergetools/kompare [new file with mode: 0644]
mergetools/meld [new file with mode: 0644]
mergetools/opendiff [new file with mode: 0644]
mergetools/p4merge [new file with mode: 0644]
mergetools/tkdiff [new file with mode: 0644]
mergetools/tortoisemerge [new file with mode: 0644]
mergetools/vim [new file with mode: 0644]
mergetools/xxdiff [new file with mode: 0644]
notes.c
pager.c
parse-options-cb.c [new file with mode: 0644]
parse-options.c
parse-options.h
path.c
quote.c
read-cache.c
refs.c
remote-curl.c
revision.c
revision.h
sequencer.c [new file with mode: 0644]
sequencer.h [new file with mode: 0644]
setup.c
sha1_file.c
sha1_name.c
strbuf.c
string-list.c
string-list.h
submodule.c
submodule.h
t/lib-gpg.sh [new file with mode: 0755]
t/lib-gpg/pubring.gpg [new file with mode: 0644]
t/lib-gpg/random_seed [new file with mode: 0644]
t/lib-gpg/secring.gpg [new file with mode: 0644]
t/lib-gpg/trustdb.gpg [new file with mode: 0644]
t/t0006-date.sh
t/t1013-loose-object-format.sh [new file with mode: 0755]
t/t1013/objects/14/9cedb5c46929d18e0f118e9fa31927487af3b6 [new file with mode: 0644]
t/t1013/objects/16/56f9233d999f61ef23ef390b9c71d75399f435 [new file with mode: 0644]
t/t1013/objects/1e/72a6b2c4a577ab0338860fa9fe87f761fc9bbd [new file with mode: 0644]
t/t1013/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 [new file with mode: 0644]
t/t1013/objects/2e/65efe2a145dda7ee51d1741299f848e5bf752e [new file with mode: 0644]
t/t1013/objects/6b/aee0540ea990d9761a3eb9ab183003a71c3696 [new file with mode: 0644]
t/t1013/objects/70/e6a83d8dcb26fc8bc0cf702e2ddeb6adca18fd [new file with mode: 0644]
t/t1013/objects/76/e7fa9941f4d5f97f64fea65a2cba436bc79cbb [new file with mode: 0644]
t/t1013/objects/78/75c6237d3fcdd0ac2f0decc7d3fa6a50b66c09 [new file with mode: 0644]
t/t1013/objects/7a/37b887a73791d12d26c0d3e39568a8fb0fa6e8 [new file with mode: 0644]
t/t1013/objects/85/df50785d62d3b05ab03d9cbf7e4a0b49449730 [new file with mode: 0644]
t/t1013/objects/8d/4e360d6c70fbd72411991c02a09c442cf7a9fa [new file with mode: 0644]
t/t1013/objects/95/b1625de3ba8b2214d1e0d0591138aea733f64f [new file with mode: 0644]
t/t1013/objects/9a/e9e86b7bd6cb1472d9373702d8249973da0832 [new file with mode: 0644]
t/t1013/objects/bd/15045f6ce8ff75747562173640456a394412c8 [new file with mode: 0644]
t/t1013/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 [new file with mode: 0644]
t/t1013/objects/f8/16d5255855ac160652ee5253b06cd8ee14165a [new file with mode: 0644]
t/t1020-subdirectory.sh
t/t1402-check-ref-format.sh
t/t1450-fsck.sh
t/t2018-checkout-branch.sh
t/t3005-ls-files-relative.sh [new file with mode: 0755]
t/t3030-merge-recursive.sh
t/t3200-branch.sh
t/t3404-rebase-interactive.sh
t/t3503-cherry-pick-root.sh
t/t3510-cherry-pick-sequence.sh [new file with mode: 0755]
t/t3900-i18n-commit.sh
t/t3902-quoted.sh
t/t3903-stash.sh
t/t3905-stash-include-untracked.sh
t/t4014-format-patch.sh
t/t5504-fetch-receive-strict.sh [new file with mode: 0755]
t/t5531-deep-submodule-push.sh
t/t5540-http-push.sh
t/t5601-clone.sh
t/t6019-rev-list-ancestry-path.sh
t/t6020-merge-df.sh
t/t6022-merge-rename.sh
t/t6030-bisect-porcelain.sh
t/t6036-recursive-corner-cases.sh
t/t6040-tracking-info.sh
t/t6042-merge-rename-corner-cases.sh [new file with mode: 0755]
t/t6300-for-each-ref.sh
t/t7004-tag.sh
t/t7004/pubring.gpg [deleted file]
t/t7004/random_seed [deleted file]
t/t7004/secring.gpg [deleted file]
t/t7004/trustdb.gpg [deleted file]
t/t7006-pager.sh
t/t7106-reset-sequence.sh [new file with mode: 0755]
t/t7602-merge-octopus-many.sh
t/t7800-difftool.sh
t/t9158-git-svn-mergeinfo.sh
t/t9160-git-svn-preserve-empty-dirs.sh [new file with mode: 0755]
t/t9161-git-svn-mergeinfo-push.sh [new file with mode: 0755]
t/t9161/branches.dump [new file with mode: 0644]
t/t9300-fast-import.sh
t/t9800-git-p4.sh
t/test-lib.sh
templates/hooks--post-commit.sample [deleted file]
templates/hooks--post-receive.sample [deleted file]
transport.c
transport.h
tree-walk.c
tree-walk.h
unpack-trees.c
unpack-trees.h
upload-pack.c
wt-status.c
xdiff/xprepare.c

index 95905e48f84bdcbee124d48ae6c982846670d5f6..42e46ab17f8b25b79fa440951ab85df012df8dc5 100644 (file)
@@ -12,6 +12,8 @@ Fixes since v1.7.6
    though the actual error was that "unexecutable" was found but did
    not have a proper she-bang line to be executed.
 
+ * Error exits from $PAGER were silently ignored.
+
  * "git checkout -b <branch>" was confused when attempting to create a
    branch whose name ends with "-g" followed by hexadecimal digits,
    and refused to work.
@@ -21,9 +23,16 @@ Fixes since v1.7.6
 
  * "git diff --cc" learned to correctly ignore binary files.
 
+ * "git diff -c/--cc" mishandled a deletion that resolves a conflict, and
+   looked in the working tree instead.
+
  * "git fast-export" forgot to quote pathnames with unsafe characters
    in its output.
 
+ * "git fetch" over smart-http transport used to abort when the
+   repository was updated between the initial connection and the
+   subsequent object transfer.
+
  * "git fetch" did not recurse into submodules in subdirectories.
 
  * "git ls-tree" did not error out when asked to show a corrupt tree.
@@ -31,6 +40,8 @@ Fixes since v1.7.6
  * "git pull" without any argument left an extra whitespace after the
    command name in its reflog.
 
+ * "git push --quiet" was not really quiet.
+
  * "git rebase -i -p" incorrectly dropped commits from side branches.
 
  * "git reset [<commit>] paths..." did not reset the index entry correctly
diff --git a/Documentation/RelNotes/1.7.6.2.txt b/Documentation/RelNotes/1.7.6.2.txt
new file mode 100644 (file)
index 0000000..67ae414
--- /dev/null
@@ -0,0 +1,8 @@
+Git v1.7.6.2 Release Notes
+==========================
+
+Fixes since v1.7.6.1
+--------------------
+
+ * v1.7.6.1 broke "git push --quiet"; it used to be a no-op against an old
+   version of Git running on the other end, but v1.7.6.1 made it abort.
diff --git a/Documentation/RelNotes/1.7.6.3.txt b/Documentation/RelNotes/1.7.6.3.txt
new file mode 100644 (file)
index 0000000..9597183
--- /dev/null
@@ -0,0 +1,24 @@
+Git v1.7.6.3 Release Notes
+==========================
+
+Fixes since v1.7.6.2
+--------------------
+
+ * "git -c var=value subcmd" misparsed the custom configuration when
+   value contained an equal sign.
+
+ * "git fetch" had a major performance regression, wasting many
+   needless cycles in a repository where there is no submodules
+   present. This was especially bad, when there were many refs.
+
+ * "git reflog $refname" did not default to the "show" subcommand as
+   the documentation advertised the command to do.
+
+ * "git reset" did not leave meaningful log message in the reflog.
+
+ * "git status --ignored" did not show ignored items when there is no
+   untracked items.
+
+ * "git tag --contains $commit" was unnecessarily inefficient.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.6.4.txt b/Documentation/RelNotes/1.7.6.4.txt
new file mode 100644 (file)
index 0000000..e19acac
--- /dev/null
@@ -0,0 +1,32 @@
+Git v1.7.6.4 Release Notes
+==========================
+
+Fixes since v1.7.6.3
+--------------------
+
+ * The error reporting logic of "git am" when the command is fed a file
+   whose mail-storage format is unknown was fixed.
+
+ * "git branch --set-upstream @{-1} foo" did not expand @{-1} correctly.
+
+ * "git check-ref-format --print" used to parrot a candidate string that
+   began with a slash (e.g. /refs/heads/master) without stripping it, to make
+   the result a suitably normalized string the caller can append to "$GIT_DIR/".
+
+ * "git clone" failed to clone locally from a ".git" file that itself
+   is not a directory but is a pointer to one.
+
+ * "git clone" from a local repository that borrows from another
+   object store using a relative path in its objects/info/alternates
+   file did not adjust the alternates in the resulting repository.
+
+ * "git describe --dirty" did not refresh the index before checking the
+   state of the working tree files.
+
+ * "git ls-files ../$path" that is run from a subdirectory reported errors
+   incorrectly when there is no such path that matches the given pathspec.
+
+ * "git mergetool" could loop forever prompting when nothing can be read
+   from the standard input.
+
+Also contains minor fixes and documentation updates.
index 28214e27210d772efc0a3cac74344ac2244fe790..7655cccfaa1585a14257b5a86d663f501c06bf1d 100644 (file)
@@ -8,7 +8,7 @@ Updates since v1.7.6
 
  * Interix, Cygwin and Minix ports got updated.
 
- * A handful of patches to update git-p4 (in contrib/).
+ * Various updates to git-p4 (in contrib/), fast-import, and git-svn.
 
  * Gitweb learned to read from /etc/gitweb-common.conf when it exists,
    before reading from gitweb_config.perl or from /etc/gitweb.conf
@@ -19,65 +19,97 @@ Updates since v1.7.6
    functions can compress or uncompress more than 4GB data in one call on
    platforms with 64-bit long, which has been corrected.
 
- * "git am" learned to pass "--exclude=<path>" option through to underlying
+ * Git now recognizes loose objects written by other implementations that
+   use a non-standard window size for zlib deflation (e.g. Agit running on
+   Android with 4kb window). We used to reject anything that was not
+   deflated with 32kb window.
+
+ * Interaction between the use of pager and coloring of the output has
+   been improved, especially when a command that is not built-in was
+   involved.
+
+ * "git am" learned to pass the "--exclude=<path>" option through to underlying
    "git apply".
 
- * You can now feed many empty lines before feeding a mbox file to
+ * You can now feed many empty lines before feeding an mbox file to
    "git am".
 
  * "git archive" can be told to pass the output to gzip compression and
    produce "archive.tar.gz".
 
- * "git bisect" can be used in a bare repository (provided if the test
+ * "git bisect" can be used in a bare repository (provided that the test
    you perform per each iteration does not need a working tree, of
    course).
 
+ * The length of abbreviated object names in "git branch -v" output
+   now honors the core.abbrev configuration variable.
+
  * "git check-attr" can take relative paths from the command line.
 
- * "git check-attr" learned "--all" option to list the attributes for a
+ * "git check-attr" learned an "--all" option to list the attributes for a
    given path.
 
  * "git checkout" (both the code to update the files upon checking out a
-   different branch, the code to checkout specific set of files) learned
+   different branch and the code to checkout a specific set of files) learned
    to stream the data from object store when possible, without having to
-   read the entire contents of a file in memory first. An earlier round
+   read the entire contents of a file into memory first. An earlier round
    of this code that is not in any released version had a large leak but
    now it has been plugged.
 
- * "git clone" can now take "--config key=value" option to set the
+ * "git clone" can now take "--config key=value" option to set the
    repository configuration options that affect the initial checkout.
 
  * "git commit <paths>..." now lets you feed relative pathspecs that
-   refer outside your current subdirectory.
+   refer to outside your current subdirectory.
 
- * "git diff --stat" learned --stat-count option to limit the output of
-   diffstat report.
+ * "git diff --stat" learned --stat-count option to limit the output of
+   diffstat report.
 
- * "git diff" learned "--histogram" option, to use a different diff
+ * "git diff" learned a "--histogram" option to use a different diff
    generation machinery stolen from jgit, which might give better
    performance.
 
+ * "git diff" had a weird worst case behaviour that can be triggered
+   when comparing files with potentially many places that could match.
+
  * "git fetch", "git push" and friends no longer show connection
-   errors for addresses that couldn't be connected when at least one
+   errors for addresses that couldn't be connected to when at least one
    address succeeds (this is arguably a regression but a deliberate
    one).
 
- * "git grep" learned --break and --heading options, to let users mimic
-   output format of "ack".
+ * "git grep" learned "--break" and "--heading" options, to let users mimic
+   the output format of "ack".
 
- * "git grep" learned "-W" option that shows wider context using the same
+ * "git grep" learned "-W" option that shows wider context using the same
    logic used by "git diff" to determine the hunk header.
 
+ * Invoking the low-level "git http-fetch" without "-a" option (which
+   git itself never did---normal users should not have to worry about
+   this) is now deprecated.
+
+ * The "--decorate" option to "git log" and its family learned to
+   highlight grafted and replaced commits.
+
  * "git rebase master topci" no longer spews usage hints after giving
-   "fatal: no such branch: topci" error message.
+   the "fatal: no such branch: topci" error message.
 
- * "git stash" learned --include-untracked option.
+ * The recursive merge strategy implementation got a fairly large
+   fix for many corner cases that may rarely happen in real world
+   projects (it has been verified that none of the 16000+ merges in
+   the Linux kernel history back to v2.6.12 is affected with the
+   corner case bugs this update fixes).
+
+ * "git stash" learned an "--include-untracked option".
 
  * "git submodule update" used to stop at the first error updating a
    submodule; it now goes on to update other submodules that can be
    updated, and reports the ones with errors at the end.
 
- * "git upload-pack" and "git receive-pack" learned to pretend only a
+ * "git push" can be told with the "--recurse-submodules=check" option to
+   refuse pushing of the supermodule, if any of its submodules'
+   commits hasn't been pushed out to their remotes.
+
+ * "git upload-pack" and "git receive-pack" learned to pretend that only a
    subset of the refs exist in a repository. This may help a site to
    put many tiny repositories into one repository (this would not be
    useful for larger repositories as repacking would be problematic).
@@ -86,7 +118,7 @@ Updates since v1.7.6
    that is more efficient in reading objects in packfiles.
 
  * test scripts for gitweb tried to run even when CGI-related perl modules
-   are not installed; it now exits early when they are unavailable.
+   are not installed; they now exit early when the latter are unavailable.
 
 Also contains various documentation updates and minor miscellaneous
 changes.
@@ -95,28 +127,8 @@ changes.
 Fixes since v1.7.6
 ------------------
 
-Unless otherwise noted, all the fixes in 1.7.6.X maintenance track are
+Unless otherwise noted, all fixes in the 1.7.6.X maintenance track are
 included in this release.
 
- * Error exits from $PAGER were silently ignored.
-   (merge fc1b56f cb/maint-exec-error-report later).
-
- * "git diff -c/--cc" mishandled a deletion that resolves a conflict, and
-   looked in the working tree instead.
-   (merge 9969454 jc/maint-combined-diff-work-tree later).
-
- * "git fetch" over smart-http transport used to abort when the
-   repository was updated between the initial connection and the
-   subsequent object transfer.
-   (merge 051e400 jc/maint-smart-http-race-upload-pack later).
-
- * "git push --quiet" was not really quiet.
-   (merge 0d086b8 cb/maint-quiet-push later).
-
---
-exec >/var/tmp/1
-echo O=$(git describe master)
-O=v1.7.6-548-g324b6b1
-git log --first-parent --oneline $O..master
-echo
-git shortlog --no-merges ^maint ^$O master
+ * "git branch -m" and "git checkout -b" incorrectly allowed the tip
+   of the branch that is currently checked out updated.
index 938eccf2a5ac9dd629be1bc6bb53a59723c99083..0dbf2c9843dd3eed014d788892c8719036287308 100644 (file)
@@ -134,8 +134,7 @@ Another thing: NULL pointers shall be written as NULL, not as 0.
 
 (2) Generate your patch using git tools out of your commits.
 
-git based diff tools (git, Cogito, and StGIT included) generate
-unidiff which is the preferred format.
+git based diff tools generate unidiff which is the preferred format.
 
 You do not have to be afraid to use -M option to "git diff" or
 "git format-patch", if your patch involves file renames.  The
index 0658ffb889320d408f2cad9214e138879059c766..98bac550736f5fd54032371c7f8acbcc8730116c 100644 (file)
@@ -857,6 +857,13 @@ fetch.recurseSubmodules::
        when its superproject retrieves a commit that updates the submodule's
        reference.
 
+fetch.fsckObjects::
+       If it is set to true, git-fetch-pack will check all fetched
+       objects. It will abort in the case of a malformed object or a
+       broken link. The result of an abort are only dangling objects.
+       Defaults to false. If not set, the value of `transfer.fsckObjects`
+       is used instead.
+
 fetch.unpackLimit::
        If the number of objects fetched over the git native
        transfer is below this
@@ -1595,7 +1602,8 @@ receive.fsckObjects::
        If it is set to true, git-receive-pack will check all received
        objects. It will abort in the case of a malformed object or a
        broken link. The result of an abort are only dangling objects.
-       Defaults to false.
+       Defaults to false. If not set, the value of `transfer.fsckObjects`
+       is used instead.
 
 receive.unpackLimit::
        If the number of objects received in a push is below this
@@ -1830,6 +1838,11 @@ tar.umask::
        archiving user's umask will be used instead.  See umask(2) and
        linkgit:git-archive[1].
 
+transfer.fsckObjects::
+       When `fetch.fsckObjects` or `receive.fsckObjects` are
+       not set, the value of this variable is used instead.
+       Defaults to false.
+
 transfer.unpackLimit::
        When `fetch.unpackLimit` or `receive.unpackLimit` are
        not set, the value of this variable is used instead.
index c50f1898272f6b2a9a1c1cd236d3b27621d4fdb9..507b8d0ab2a2d524a607c5b91e6d17f6d2f36392 100644 (file)
@@ -113,7 +113,8 @@ OPTIONS
 
 --abbrev=<length>::
        Alter the sha1's minimum display length in the output listing.
-       The default value is 7.
+       The default value is 7 and can be overridden by the `core.abbrev`
+       config option.
 
 --no-abbrev::
        Display the full sha1s in the output listing rather than abbreviating them.
index 7cfa3d92ac8dc7a90068311c0047c667f98515a8..2660a842fc2ac76660963bc65c95ca47cb0e97cb 100644 (file)
@@ -9,6 +9,8 @@ SYNOPSIS
 --------
 [verse]
 'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] <commit>...
+'git cherry-pick' --reset
+'git cherry-pick' --continue
 
 DESCRIPTION
 -----------
@@ -110,6 +112,10 @@ effect to your index in a row.
        Pass the merge strategy-specific option through to the
        merge strategy.  See linkgit:git-merge[1] for details.
 
+SEQUENCER SUBCOMMANDS
+---------------------
+include::sequencer.txt[]
+
 EXAMPLES
 --------
 `git cherry-pick master`::
index 974e04ef1abaf4a3018b83b0323a46cbaa5339d4..79fb9841441d02cd635dcdd2aecef42d38c6b59d 100644 (file)
@@ -47,12 +47,14 @@ OPTIONS
 
 -e <pattern>::
 --exclude=<pattern>::
-       Specify special exceptions to not be cleaned.  Each <pattern> is
-       the same form as in $GIT_DIR/info/excludes and this option can be
-       given multiple times.
+       In addition to those found in .gitignore (per directory) and
+       $GIT_DIR/info/exclude, also consider these patterns to be in the
+       set of the ignore rules in effect.
 
 -x::
-       Don't use the ignore rules.  This allows removing all untracked
+       Don't use the standard ignore rules read from .gitignore (per
+       directory) and $GIT_DIR/info/exclude, but do still use the ignore
+       rules given with `-e` options.  This allows removing all untracked
        files, including build products.  This can be used (possibly in
        conjunction with 'git reset') to create a pristine
        working directory to test a clean build.
index ebd13be72e58cf0d0a66ddd0105117747ac900be..69a1e4af9ec007a6cc59cce07298a6a825369ef1 100644 (file)
@@ -93,14 +93,14 @@ OPTIONS
        Listen on an alternative port.  Incompatible with '--inetd' option.
 
 --init-timeout=<n>::
-       Timeout between the moment the connection is established and the
-       client request is received (typically a rather low value, since
+       Timeout (in seconds) between the moment the connection is established
+       and the client request is received (typically a rather low value, since
        that should be basically immediate).
 
 --timeout=<n>::
-       Timeout for specific client sub-requests. This includes the time
-       it takes for the server to process the sub-request and the time spent
-       waiting for the next client's request.
+       Timeout (in seconds) for specific client sub-requests. This includes
+       the time it takes for the server to process the sub-request and the
+       time spent waiting for the next client's request.
 
 --max-connections=<n>::
        Maximum number of concurrent clients, defaults to 32.  Set it to
index 2969388880a6d827a9970dd9d67fd81224a5bc34..ec6ef3119792a9e66a3a46bf6f0754458ea6a061 100644 (file)
@@ -425,8 +425,8 @@ Here `<name>` is the person's display name (for example
 (``cm@example.com'').  `LT` and `GT` are the literal less-than (\x3c)
 and greater-than (\x3e) symbols.  These are required to delimit
 the email address from the other fields in the line.  Note that
-`<name>` is free-form and may contain any sequence of bytes, except
-`LT` and `LF`.  It is typically UTF-8 encoded.
+`<name>` and `<email>` are free-form and may contain any sequence
+of bytes, except `LT`, `GT` and `LF`.  `<name>` is typically UTF-8 encoded.
 
 The time of the change is specified by `<when>` using the date format
 that was selected by the \--date-format=<fmt> command line option.
@@ -1012,10 +1012,14 @@ force::
        (see OPTIONS, above).
 
 import-marks::
+import-marks-if-exists::
        Like --import-marks except in two respects: first, only one
-       "feature import-marks" command is allowed per stream;
-       second, an --import-marks= command-line option overrides
-       any "feature import-marks" command in the stream.
+       "feature import-marks" or "feature import-marks-if-exists"
+       command is allowed per stream; second, an --import-marks=
+       or --import-marks-if-exists command-line option overrides
+       any of these "feature" commands in the stream; third,
+       "feature import-marks-if-exists" like a corresponding
+       command-line option silently skips a nonexistent file.
 
 cat-blob::
 ls::
index 152e695c8178fee9daa478cdf67eae58a2f1af00..c872b883ba25144457eccb9967bc68003a3332ea 100644 (file)
@@ -101,9 +101,10 @@ Fields that have name-email-date tuple as its value (`author`,
 `committer`, and `tagger`) can be suffixed with `name`, `email`,
 and `date` to extract the named component.
 
-The first line of the message in a commit and tag object is
-`subject`, the remaining lines are `body`.  The whole message
-is `contents`.
+The complete message in a commit and tag object is `contents`.
+Its first line is `contents:subject`, the remaining lines
+are `contents:body` and the optional GPG signature
+is `contents:signature`.
 
 For sorting purposes, fields with numeric values sort in numeric
 order (`objectsize`, `authordate`, `committerdate`, `taggerdate`).
index d13c9b23f7bce6379f638e5bf86b30c95abef30e..6ea9be775c09111c14668e0de390a04a301f3235 100644 (file)
@@ -166,15 +166,22 @@ will want to ensure that threading is disabled for `git send-email`.
 --to=<email>::
        Add a `To:` header to the email headers. This is in addition
        to any configured headers, and may be used multiple times.
+       The negated form `--no-to` discards all `To:` headers added so
+       far (from config or command line).
 
 --cc=<email>::
        Add a `Cc:` header to the email headers. This is in addition
        to any configured headers, and may be used multiple times.
+       The negated form `--no-cc` discards all `Cc:` headers added so
+       far (from config or command line).
 
 --add-header=<header>::
        Add an arbitrary header to the email headers.  This is in addition
        to any configured headers, and may be used multiple times.
-       For example, `--add-header="Organization: git-foo"`
+       For example, `--add-header="Organization: git-foo"`.
+       The negated form `--no-add-header` discards *all* (`To:`,
+       `Cc:`, and custom) headers added so far from config or command
+       line.
 
 --cover-letter::
        In addition to the patches, generate a cover letter file
index 4d42073867b09317dc674b0c0f5f1b76d4129a03..070cd1e6ed93b064bdd1fdfc1767a3f9a999d321 100644 (file)
@@ -15,6 +15,9 @@ DESCRIPTION
 -----------
 Downloads a remote git repository via HTTP.
 
+*NOTE*: use of this command without -a is deprecated.  The -a
+behaviour will become the default in a future release.
+
 OPTIONS
 -------
 commit-id::
index 6c934660d7bf0b5822bf2a7e6f8eb68cecbfc501..249fc878ec2058f9fcfc6655ecaeb299ba5622a7 100644 (file)
@@ -69,10 +69,13 @@ produced by --stat etc.
        its size is not included.
 
 [\--] <path>...::
-       Show only commits that affect any of the specified paths. To
-       prevent confusion with options and branch names, paths may need
-       to be prefixed with "\-- " to separate them from options or
-       refnames.
+       Show only commits that are enough to explain how the files
+       that match the specified paths came to be.  See "History
+       Simplification" below for details and other simplification
+       modes.
++
+To prevent confusion with options and branch names, paths may need to
+be prefixed with "\-- " to separate them from options or refnames.
 
 include::rev-list-options.txt[]
 
index 6a187f2e2336575fb4974b8c492359913a8c7a9e..e8319eac6928300d1eb12070874e705b9be060b6 100644 (file)
@@ -142,8 +142,9 @@ OPTIONS
 
 -C <object>::
 --reuse-message=<object>::
-       Take the note message from the given blob object (for
-       example, another note).
+       Take the given blob object (for example, another note) as the
+       note message. (Use `git notes copy <object>` instead to
+       copy notes between objects.)
 
 -c <object>::
 --reedit-message=<object>::
@@ -285,6 +286,8 @@ $ blob=$(git hash-object -w a.out)
 $ git notes --ref=built add -C "$blob" HEAD
 ------------
 
+(You cannot simply use `git notes --ref=built add -F a.out HEAD`
+because that is not binary-safe.)
 Of course, it doesn't make much sense to display non-text-format notes
 with 'git log', so if you use such notes, you'll probably need to write
 some special-purpose tools to do something useful with them.
index 49c6e9fa51daea75272ea7eec8a360bfd324a467..aede48877fb080bd12c346c74cf7453860d7de21 100644 (file)
@@ -162,6 +162,12 @@ useful if you write an alias or script around 'git push'.
        is specified. This flag forces progress status even if the
        standard error stream is not directed to a terminal.
 
+--recurse-submodules=check::
+       Check whether all submodule commits used by the revisions to be
+       pushed are available on a remote tracking branch. Otherwise the
+       push will be aborted and the command will exit with non-zero status.
+
+
 include::urls-remotes.txt[]
 
 OUTPUT
index a3a1d8eea3ec733d155475f2d6216e1e71245f10..b1f7dc643a0e9b6b232e1c75b39d7f784ab21b4a 100644 (file)
@@ -9,7 +9,7 @@ git-receive-pack - Receive what is pushed into the repository
 SYNOPSIS
 --------
 [verse]
-'git-receive-pack' [--quiet] <directory>
+'git-receive-pack' <directory>
 
 DESCRIPTION
 -----------
@@ -35,9 +35,6 @@ are not fast-forwards.
 
 OPTIONS
 -------
---quiet::
-       Print only error messages.
-
 <directory>::
        The repository to sync into.
 
index 4f83dea5a39ca2f8e695a2426e90cf0450533696..674797cd8308801dc3e3b4f3d6f581005a0a0d2a 100644 (file)
@@ -24,22 +24,141 @@ output. Because a remote helper runs as an independent process from
 git, there is no need to re-link git to add a new helper, nor any
 need to link the helper with the implementation of git.
 
-Every helper must support the "capabilities" command, which git will
-use to determine what other commands the helper will accept.  Other
-commands generally concern facilities like discovering and updating
-remote refs, transporting objects between the object database and
-the remote repository, and updating the local object store.
-
-Helpers supporting the 'fetch' capability can discover refs from the
-remote repository and transfer objects reachable from those refs to
-the local object store. Helpers supporting the 'push' capability can
-transfer local objects to the remote repository and update remote refs.
+Every helper must support the "capabilities" command, which git
+uses to determine what other commands the helper will accept.  Those
+other commands can be used to discover and update remote refs,
+transport objects between the object database and the remote repository,
+and update the local object store.
 
 Git comes with a "curl" family of remote helpers, that handle various
 transport protocols, such as 'git-remote-http', 'git-remote-https',
 'git-remote-ftp' and 'git-remote-ftps'. They implement the capabilities
 'fetch', 'option', and 'push'.
 
+INPUT FORMAT
+------------
+
+Git sends the remote helper a list of commands on standard input, one
+per line.  The first command is always the 'capabilities' command, in
+response to which the remote helper must print a list of the
+capabilities it supports (see below) followed by a blank line.  The
+response to the capabilities command determines what commands Git uses
+in the remainder of the command stream.
+
+The command stream is terminated by a blank line.  In some cases
+(indicated in the documentation of the relevant commands), this blank
+line is followed by a payload in some other protocol (e.g., the pack
+protocol), while in others it indicates the end of input.
+
+Capabilities
+~~~~~~~~~~~~
+
+Each remote helper is expected to support only a subset of commands.
+The operations a helper supports are declared to git in the response
+to the `capabilities` command (see COMMANDS, below).
+
+'option'::
+       For specifying settings like `verbosity` (how much output to
+       write to stderr) and `depth` (how much history is wanted in the
+       case of a shallow clone) that affect how other commands are
+       carried out.
+
+'connect'::
+       For fetching and pushing using git's native packfile protocol
+       that requires a bidirectional, full-duplex connection.
+
+'push'::
+       For listing remote refs and pushing specified objects from the
+       local object store to remote refs.
+
+'fetch'::
+       For listing remote refs and fetching the associated history to
+       the local object store.
+
+'import'::
+       For listing remote refs and fetching the associated history as
+       a fast-import stream.
+
+'refspec' <refspec>::
+       This modifies the 'import' capability, allowing the produced
+       fast-import stream to modify refs in a private namespace
+       instead of writing to refs/heads or refs/remotes directly.
+       It is recommended that all importers providing the 'import'
+       capability use this.
++
+A helper advertising the capability
+`refspec refs/heads/{asterisk}:refs/svn/origin/branches/{asterisk}`
+is saying that, when it is asked to `import refs/heads/topic`, the
+stream it outputs will update the `refs/svn/origin/branches/topic`
+ref.
++
+This capability can be advertised multiple times.  The first
+applicable refspec takes precedence.  The left-hand of refspecs
+advertised with this capability must cover all refs reported by
+the list command.  If no 'refspec' capability is advertised,
+there is an implied `refspec {asterisk}:{asterisk}`.
+
+Capabilities for Pushing
+~~~~~~~~~~~~~~~~~~~~~~~~
+'connect'::
+       Can attempt to connect to 'git receive-pack' (for pushing),
+       'git upload-pack', etc for communication using the
+       packfile protocol.
++
+Supported commands: 'connect'.
+
+'push'::
+       Can discover remote refs and push local commits and the
+       history leading up to them to new or existing remote refs.
++
+Supported commands: 'list for-push', 'push'.
+
+If a helper advertises both 'connect' and 'push', git will use
+'connect' if possible and fall back to 'push' if the helper requests
+so when connecting (see the 'connect' command under COMMANDS).
+
+Capabilities for Fetching
+~~~~~~~~~~~~~~~~~~~~~~~~~
+'connect'::
+       Can try to connect to 'git upload-pack' (for fetching),
+       'git receive-pack', etc for communication using the
+       packfile protocol.
++
+Supported commands: 'connect'.
+
+'fetch'::
+       Can discover remote refs and transfer objects reachable from
+       them to the local object store.
++
+Supported commands: 'list', 'fetch'.
+
+'import'::
+       Can discover remote refs and output objects reachable from
+       them as a stream in fast-import format.
++
+Supported commands: 'list', 'import'.
+
+If a helper advertises 'connect', git will use it if possible and
+fall back to another capability if the helper requests so when
+connecting (see the 'connect' command under COMMANDS).
+When choosing between 'fetch' and 'import', git prefers 'fetch'.
+Other frontends may have some other order of preference.
+
+'refspec' <refspec>::
+       This modifies the 'import' capability.
++
+A helper advertising
+`refspec refs/heads/{asterisk}:refs/svn/origin/branches/{asterisk}`
+in its capabilities is saying that, when it handles
+`import refs/heads/topic`, the stream it outputs will update the
+`refs/svn/origin/branches/topic` ref.
++
+This capability can be advertised multiple times.  The first
+applicable refspec takes precedence.  The left-hand of refspecs
+advertised with this capability must cover all refs reported by
+the list command.  If no 'refspec' capability is advertised,
+there is an implied `refspec {asterisk}:{asterisk}`.
+
 INVOCATION
 ----------
 
@@ -122,7 +241,22 @@ Supported if the helper has the "fetch" capability.
 'push' +<src>:<dst>::
        Pushes the given local <src> commit or branch to the
        remote branch described by <dst>.  A batch sequence of
-       one or more push commands is terminated with a blank line.
+       one or more 'push' commands is terminated with a blank line
+       (if there is only one reference to push, a single 'push' command
+       is followed by a blank line). For example, the following would
+       be two batches of 'push', the first asking the remote-helper
+       to push the local ref 'master' to the remote ref 'master' and
+       the local 'HEAD' to the remote 'branch', and the second
+       asking to push ref 'foo' to ref 'bar' (forced update requested
+       by the '+').
++
+------------
+push refs/heads/master:refs/heads/master
+push HEAD:refs/heads/branch
+\n
+push +refs/heads/foo:refs/heads/bar
+\n
+------------
 +
 Zero or more protocol options may be entered after the last 'push'
 command, before the batch's terminating blank line.
@@ -147,6 +281,11 @@ Supported if the helper has the "push" capability.
 Especially useful for interoperability with a foreign versioning
 system.
 +
+Just like 'push', a batch sequence of one or more 'import' is
+terminated with a blank line. For each batch of 'import', the remote
+helper should produce a fast-import stream terminated by a 'done'
+command.
++
 Supported if the helper has the "import" capability.
 
 'connect' <service>::
@@ -171,26 +310,6 @@ completing a valid response for the current command.
 Additional commands may be supported, as may be determined from
 capabilities reported by the helper.
 
-CAPABILITIES
-------------
-
-'fetch'::
-'option'::
-'push'::
-'import'::
-'connect'::
-       This helper supports the corresponding command with the same name.
-
-'refspec' 'spec'::
-       When using the import command, expect the source ref to have
-       been written to the destination ref. The earliest applicable
-       refspec takes precedence. For example
-       "refs/heads/{asterisk}:refs/svn/origin/branches/{asterisk}" means
-       that, after an "import refs/heads/name", the script has written to
-       refs/svn/origin/branches/name. If this capability is used at
-       all, it must cover all refs reported by the list command; if
-       it is not used, it is effectively "{asterisk}:{asterisk}"
-
 REF LIST ATTRIBUTES
 -------------------
 
@@ -243,6 +362,8 @@ SEE ALSO
 --------
 linkgit:git-remote[1]
 
+linkgit:git-remote-testgit[1]
+
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/Documentation/git-remote-testgit.txt b/Documentation/git-remote-testgit.txt
new file mode 100644 (file)
index 0000000..2a67d45
--- /dev/null
@@ -0,0 +1,30 @@
+git-remote-testgit(1)
+=====================
+
+NAME
+----
+git-remote-testgit - Example remote-helper
+
+
+SYNOPSIS
+--------
+[verse]
+git clone testgit::<source-repo> [<destination>]
+
+DESCRIPTION
+-----------
+
+This command is a simple remote-helper, that is used both as a
+testcase for the remote-helper functionality, and as an example to
+show remote-helper authors one possible implementation.
+
+The best way to learn more is to read the comments and source code in
+'git-remote-testgit.py'.
+
+SEE ALSO
+--------
+linkgit:git-remote-helpers[1]
+
+GIT
+---
+Part of the linkgit:git[1] suite
index b311d59c7c06dd696e3c667c97a5982137bdd87a..f3519413e7e8704deee0197df6876eaed97e28b0 100644 (file)
@@ -9,6 +9,8 @@ SYNOPSIS
 --------
 [verse]
 'git revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] <commit>...
+'git revert' --reset
+'git revert' --continue
 
 DESCRIPTION
 -----------
@@ -91,6 +93,10 @@ effect to your index in a row.
        Pass the merge strategy-specific option through to the
        merge strategy.  See linkgit:git-merge[1] for details.
 
+SEQUENCER SUBCOMMANDS
+---------------------
+include::sequencer.txt[]
+
 EXAMPLES
 --------
 `git revert HEAD~3`::
index bed9e1f097d3aa144a62765129bf3b599d365dcf..bd3eaa69bfb6e788d297b3e7d2c871d25a478f80 100644 (file)
@@ -9,7 +9,7 @@ git-send-pack - Push objects over git protocol to another repository
 SYNOPSIS
 --------
 [verse]
-'git send-pack' [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--quiet] [--verbose] [--thin] [<host>:]<directory> [<ref>...]
+'git send-pack' [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]
 
 DESCRIPTION
 -----------
@@ -45,9 +45,6 @@ OPTIONS
        the remote repository can lose commits; use it with
        care.
 
---quiet::
-       Print only error messages.
-
 --verbose::
        Run verbosely.
 
index ed5eca1fcef50f910d74abb495360dc6c3fbf35b..08cad6d2b676642b37b5a871cd52617c4308a748 100644 (file)
@@ -157,6 +157,17 @@ Skip "branches" and "tags" of first level directories;;
        affecting the working tree; and the 'rebase' command will be
        able to update the working tree with the latest changes.
 
+--preserve-empty-dirs;;
+       Create a placeholder file in the local Git repository for each
+       empty directory fetched from Subversion.  This includes directories
+       that become empty by removing all entries in the Subversion
+       repository (but not the directory itself).  The placeholder files
+       are also tracked and removed when no longer necessary.
+
+--placeholder-filename=<filename>;;
+       Set the name of placeholder files created by --preserve-empty-dirs.
+       Default: ".gitignore"
+
 'rebase'::
        This fetches revisions from the SVN parent of the current HEAD
        and rebases the current (uncommitted to SVN) work against it.
@@ -211,8 +222,17 @@ discouraged.
        Add the given merge information during the dcommit
        (e.g. `--mergeinfo="/branches/foo:1-10"`). All svn server versions can
        store this information (as a property), and svn clients starting from
-       version 1.5 can make use of it. 'git svn' currently does not use it
-       and does not set it automatically.
+       version 1.5 can make use of it. To specify merge information from multiple
+       branches, use a single space character between the branches
+       (`--mergeinfo="/branches/foo:1-10 /branches/bar:3,5-6,8"`)
++
+[verse]
+config key: svn.pushmergeinfo
++
+This option will cause git-svn to attempt to automatically populate the
+svn:mergeinfo property in the SVN repository when possible. Currently, this can
+only be done when dcommitting non-fast-forward merges where all parents but the
+first have already been pushed into SVN.
 
 'branch'::
        Create a branch in the SVN repository.
index 710d750cfd7a67bacedaa851afaeed4469d38a77..cbc51d5a949e60e023982efc61e2d80dd73abd37 100644 (file)
@@ -44,9 +44,18 @@ unreleased) version of git, that is available from 'master'
 branch of the `git.git` repository.
 Documentation for older releases are available here:
 
-* link:v1.7.6/git.html[documentation for release 1.7.6]
+* link:v1.7.7/git.html[documentation for release 1.7.7]
 
 * release notes for
+  link:RelNotes/1.7.7.txt[1.7.7].
+
+* link:v1.7.6.4/git.html[documentation for release 1.7.6.4]
+
+* release notes for
+  link:RelNotes/1.7.6.4.txt[1.7.6.4],
+  link:RelNotes/1.7.6.3.txt[1.7.6.3],
+  link:RelNotes/1.7.6.2.txt[1.7.6.2],
+  link:RelNotes/1.7.6.1.txt[1.7.6.1],
   link:RelNotes/1.7.6.txt[1.7.6].
 
 * link:v1.7.5.4/git.html[documentation for release 1.7.5.4]
index ed8924e856ee0c6cc707135555df0d265dabccc7..c6713cf5d77b5b46f576bfb41e6c0565c4afa088 100644 (file)
@@ -5,6 +5,13 @@ NAME
 ----
 gitnamespaces - Git namespaces
 
+SYNOPSIS
+--------
+[verse]
+GIT_NAMESPACE=<namespace> 'git upload-pack'
+GIT_NAMESPACE=<namespace> 'git receive-pack'
+
+
 DESCRIPTION
 -----------
 
index eb3d040783e8202f502d05e8b4dc4e3b39736779..5c891f1169b35e53bccc19f0423b5b7e98db720b 100644 (file)
@@ -23,32 +23,25 @@ objects::
        Object store associated with this repository.  Usually
        an object store is self sufficient (i.e. all the objects
        that are referred to by an object found in it are also
-       found in it), but there are couple of ways to violate
-       it.
+       found in it), but there are a few ways to violate it.
 +
-. You could populate the repository by running a commit walker
-without `-a` option.  Depending on which options are given, you
-could have only commit objects without associated blobs and
-trees this way, for example.  A repository with this kind of
-incomplete object store is not suitable to be published to the
-outside world but sometimes useful for private repository.
-. You also could have an incomplete but locally usable repository
-by cloning shallowly.  See linkgit:git-clone[1].
-. You can be using `objects/info/alternates` mechanism, or
-`$GIT_ALTERNATE_OBJECT_DIRECTORIES` mechanism to 'borrow'
+. You could have an incomplete but locally usable repository
+by creating a shallow clone.  See linkgit:git-clone[1].
+. You could be using the `objects/info/alternates` or
+`$GIT_ALTERNATE_OBJECT_DIRECTORIES` mechanisms to 'borrow'
 objects from other object stores.  A repository with this kind
 of incomplete object store is not suitable to be published for
 use with dumb transports but otherwise is OK as long as
-`objects/info/alternates` points at the right object stores
-it borrows from.
+`objects/info/alternates` points at the object stores it
+borrows from.
 
 objects/[0-9a-f][0-9a-f]::
-       Traditionally, each object is stored in its own file.
-       They are split into 256 subdirectories using the first
-       two letters from its object name to keep the number of
-       directory entries `objects` directory itself needs to
-       hold.  Objects found here are often called 'unpacked'
-       (or 'loose') objects.
+       A newly created object is stored in its own file.
+       The objects are splayed over 256 subdirectories using
+       the first two characters of the sha1 object name to
+       keep the number of directory entries in `objects`
+       itself to a manageable number. Objects found
+       here are often called 'unpacked' (or 'loose') objects.
 
 objects/pack::
        Packs (files that store many object in compressed form,
@@ -85,7 +78,7 @@ objects/info/http-alternates::
 
 refs::
        References are stored in subdirectories of this
-       directory.  The 'git prune' command knows to keep
+       directory.  The 'git prune' command knows to preserve
        objects reachable from refs found in this directory and
        its subdirectories.
 
@@ -119,16 +112,17 @@ HEAD::
 +
 HEAD can also record a specific commit directly, instead of
 being a symref to point at the current branch.  Such a state
-is often called 'detached HEAD', and almost all commands work
-identically as normal.  See linkgit:git-checkout[1] for
-details.
+is often called 'detached HEAD.'  See linkgit:git-checkout[1]
+for details.
 
 branches::
        A slightly deprecated way to store shorthands to be used
-       to specify URL to 'git fetch', 'git pull' and 'git push'
-       commands is to store a file in `branches/<name>` and
-       give 'name' to these commands in place of 'repository'
-       argument.
+       to specify a URL to 'git fetch', 'git pull' and 'git push'.
+       A file can be stored as `branches/<name>` and then
+       'name' can be given to these commands in place of
+       'repository' argument.  See the REMOTES section in
+       linkgit:git-fetch[1] for details.  This mechanism is legacy
+       and not likely to be found in modern repositories.
 
 hooks::
        Hooks are customization scripts used by various git
@@ -173,9 +167,11 @@ info/exclude::
        at it.  See also: linkgit:gitignore[5].
 
 remotes::
-       Stores shorthands to be used to give URL and default
-       refnames to interact with remote repository to
-       'git fetch', 'git pull' and 'git push' commands.
+       Stores shorthands for URL and default refnames for use
+       when interacting with remote repositories via 'git fetch',
+       'git pull' and 'git push' commands.  See the REMOTES section
+       in linkgit:git-fetch[1] for details.  This mechanism is legacy
+       and not likely to be found in modern repositories.
 
 logs::
        Records of changes made to refs are stored in this
index d527b307707c676e82a08f18cb9fdd7d3abcb228..8823a37067811037487a9f0496736bfafdc8b7ad 100644 (file)
@@ -176,7 +176,7 @@ by doing the following:
  - Update "What's cooking" message to review the updates to
    existing topics, newly added topics and graduated topics.
 
-   This step is helped with Meta/UWC script (where Meta/ contains
+   This step is helped with Meta/cook script (where Meta/ contains
    a checkout of the 'todo' branch).
 
  - Merge topics to 'next'.  For each branch whose tip is not
@@ -197,10 +197,9 @@ by doing the following:
 
    - Nothing is next-worthy; do not do anything.
 
- - Rebase topics that do not have any commit in next yet.  This
-   step is optional but sometimes is worth doing when an old
-   series that is not in next can take advantage of low-level
-   framework change that is merged to 'master' already.
+ - [** OBSOLETE **] Optionally rebase topics that do not have any commit
+   in next yet, when they can take advantage of low-level framework
+   change that is merged to 'master' already.
 
      $ git rebase master ai/topic
 
@@ -209,7 +208,7 @@ by doing the following:
    pre-rebase hook to make sure that topics that are already in
    'next' are not rebased beyond the merged commit.
 
- - Rebuild "pu" to merge the tips of topics not in 'next'.
+ - [** OBSOLETE **] Rebuild "pu" to merge the tips of topics not in 'next'.
 
      $ git checkout pu
      $ git reset --hard next
@@ -241,7 +240,7 @@ by doing the following:
 
  - Fetch html and man branches back from k.org, and push four
    integration branches and the two documentation branches to
-   repo.or.cz
+   repo.or.cz and other mirrors.
 
 
 Some observations to be made.
diff --git a/Documentation/sequencer.txt b/Documentation/sequencer.txt
new file mode 100644 (file)
index 0000000..3e6df33
--- /dev/null
@@ -0,0 +1,9 @@
+--reset::
+       Forget about the current operation in progress.  Can be used
+       to clear the sequencer state after a failed cherry-pick or
+       revert.
+
+--continue::
+       Continue the operation in progress using the information in
+       '.git/sequencer'.  Can be used to continue after resolving
+       conflicts in a failed cherry-pick or revert.
diff --git a/Documentation/technical/api-ref-iteration.txt b/Documentation/technical/api-ref-iteration.txt
new file mode 100644 (file)
index 0000000..dbbea95
--- /dev/null
@@ -0,0 +1,81 @@
+ref iteration API
+=================
+
+
+Iteration of refs is done by using an iterate function which will call a
+callback function for every ref. The callback function has this
+signature:
+
+       int handle_one_ref(const char *refname, const unsigned char *sha1,
+                          int flags, void *cb_data);
+
+There are different kinds of iterate functions which all take a
+callback of this type. The callback is then called for each found ref
+until the callback returns nonzero. The returned value is then also
+returned by the iterate function.
+
+Iteration functions
+-------------------
+
+* `head_ref()` just iterates the head ref.
+
+* `for_each_ref()` iterates all refs.
+
+* `for_each_ref_in()` iterates all refs which have a defined prefix and
+  strips that prefix from the passed variable refname.
+
+* `for_each_tag_ref()`, `for_each_branch_ref()`, `for_each_remote_ref()`,
+  `for_each_replace_ref()` iterate refs from the respective area.
+
+* `for_each_glob_ref()` iterates all refs that match the specified glob
+  pattern.
+
+* `for_each_glob_ref_in()` the previous and `for_each_ref_in()` combined.
+
+* `head_ref_submodule()`, `for_each_ref_submodule()`,
+  `for_each_ref_in_submodule()`, `for_each_tag_ref_submodule()`,
+  `for_each_branch_ref_submodule()`, `for_each_remote_ref_submodule()`
+  do the same as the functions descibed above but for a specified
+  submodule.
+
+* `for_each_rawref()` can be used to learn about broken ref and symref.
+
+* `for_each_reflog()` iterates each reflog file.
+
+Submodules
+----------
+
+If you want to iterate the refs of a submodule you first need to add the
+submodules object database. You can do this by a code-snippet like
+this:
+
+       const char *path = "path/to/submodule"
+       if (!add_submodule_odb(path))
+               die("Error submodule '%s' not populated.", path);
+
+`add_submodule_odb()` will return an non-zero value on success. If you
+do not do this you will get an error for each ref that it does not point
+to a valid object.
+
+Note: As a side-effect of this you can not safely assume that all
+objects you lookup are available in superproject. All submodule objects
+will be available the same way as the superprojects objects.
+
+Example:
+--------
+
+----
+static int handle_remote_ref(const char *refname,
+               const unsigned char *sha1, int flags, void *cb_data)
+{
+       struct strbuf *output = cb_data;
+       strbuf_addf(output, "%s\n", refname);
+       return 0;
+}
+
+...
+
+       struct strbuf output = STRBUF_INIT;
+       for_each_remote_ref(handle_remote_ref, &output);
+       printf("%s", output.buf);
+----
index 3f575bdcff3a6b38304710b22371784c2e7443c9..ce24eb96f5efdee579f8600323731368fee4048b 100644 (file)
@@ -29,6 +29,9 @@ member (you need this if you add things later) and you should set the
 
 . Can sort an unsorted list using `sort_string_list`.
 
+. Can remove individual items of an unsorted list using
+  `unsorted_string_list_delete_item`.
+
 . Finally it should free the list using `string_list_clear`.
 
 Example:
@@ -112,6 +115,13 @@ write `string_list_insert(...)->util = ...;`.
 The above two functions need to look through all items, as opposed to their
 counterpart for sorted lists, which performs a binary search.
 
+`unsorted_string_list_delete_item`::
+
+       Remove an item from a string_list. The `string` pointer of the items
+       will be freed in case the `strdup_strings` member of the string_list
+       is set. The third parameter controls if the `util` pointer of the
+       items should be freed or not.
+
 Data structures
 ---------------
 
index 8270abb5f982463119309423eded333a8153317e..f1dc5faeaddfa814bc49775c7186d8142b9988be 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.7.6.GIT
+DEF_VER=v1.7.7
 
 LF='
 '
index 45ef51f1807eccbb322d5e33913d9acdfb7b4938..4a8191c8d88d7160bcb994fb908396a452695da5 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -250,10 +250,6 @@ all::
 #   DEFAULT_EDITOR='$GIT_FALLBACK_EDITOR',
 #   DEFAULT_EDITOR='"C:\Program Files\Vim\gvim.exe" --nofork'
 #
-# Define COMPUTE_HEADER_DEPENDENCIES if your compiler supports the -MMD option
-# and you want to avoid rebuilding objects when an unrelated header file
-# changes.
-#
 # Define CHECK_HEADER_DEPENDENCIES to check for problems in the hard-coded
 # dependency rules.
 #
@@ -302,6 +298,7 @@ bindir = $(prefix)/$(bindir_relative)
 mandir = share/man
 infodir = share/info
 gitexecdir = libexec/git-core
+mergetoolsdir = $(gitexecdir)/mergetools
 sharedir = $(prefix)/share
 gitwebdir = $(sharedir)/gitweb
 template_dir = share/git-core/templates
@@ -519,6 +516,7 @@ LIB_H += compat/win32/pthread.h
 LIB_H += compat/win32/syslog.h
 LIB_H += compat/win32/sys/poll.h
 LIB_H += compat/win32/dirent.h
+LIB_H += connected.h
 LIB_H += csum-file.h
 LIB_H += decorate.h
 LIB_H += delta.h
@@ -560,6 +558,7 @@ LIB_H += rerere.h
 LIB_H += resolve-undo.h
 LIB_H += revision.h
 LIB_H += run-command.h
+LIB_H += sequencer.h
 LIB_H += sha1-array.h
 LIB_H += sha1-lookup.h
 LIB_H += sideband.h
@@ -598,6 +597,7 @@ LIB_OBJS += commit.o
 LIB_OBJS += compat/obstack.o
 LIB_OBJS += config.o
 LIB_OBJS += connect.o
+LIB_OBJS += connected.o
 LIB_OBJS += convert.o
 LIB_OBJS += copy.o
 LIB_OBJS += csum-file.o
@@ -646,6 +646,7 @@ LIB_OBJS += pack-revindex.o
 LIB_OBJS += pack-write.o
 LIB_OBJS += pager.o
 LIB_OBJS += parse-options.o
+LIB_OBJS += parse-options-cb.o
 LIB_OBJS += patch-delta.o
 LIB_OBJS += patch-ids.o
 LIB_OBJS += path.o
@@ -666,6 +667,7 @@ LIB_OBJS += revision.o
 LIB_OBJS += run-command.o
 LIB_OBJS += server-info.o
 LIB_OBJS += setup.o
+LIB_OBJS += sequencer.o
 LIB_OBJS += sha1-array.o
 LIB_OBJS += sha1-lookup.o
 LIB_OBJS += sha1_file.o
@@ -1240,6 +1242,15 @@ endif
 ifdef CHECK_HEADER_DEPENDENCIES
 COMPUTE_HEADER_DEPENDENCIES =
 USE_COMPUTED_HEADER_DEPENDENCIES =
+else
+ifndef COMPUTE_HEADER_DEPENDENCIES
+dep_check = $(shell $(CC) $(ALL_CFLAGS) \
+       -c -MF /dev/null -MMD -MP -x c /dev/null -o /dev/null 2>&1; \
+       echo $$?)
+ifeq ($(dep_check),0)
+COMPUTE_HEADER_DEPENDENCIES=YesPlease
+endif
+endif
 endif
 
 ifdef COMPUTE_HEADER_DEPENDENCIES
@@ -1892,7 +1903,7 @@ dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS))))
 
 ifdef COMPUTE_HEADER_DEPENDENCIES
 $(dep_dirs):
-       mkdir -p $@
+       @mkdir -p $@
 
 missing_dep_dirs := $(filter-out $(wildcard $(dep_dirs)),$(dep_dirs))
 dep_file = $(dir $@).depend/$(notdir $@).d
@@ -2208,7 +2219,7 @@ test-delta$X: diff-delta.o patch-delta.o
 
 test-line-buffer$X: vcs-svn/lib.a
 
-test-parse-options$X: parse-options.o
+test-parse-options$X: parse-options.o parse-options-cb.o
 
 test-string-pool$X: vcs-svn/lib.a
 
@@ -2261,6 +2272,13 @@ endif
 gitexec_instdir_SQ = $(subst ','\'',$(gitexec_instdir))
 export gitexec_instdir
 
+ifneq ($(filter /%,$(firstword $(mergetoolsdir))),)
+mergetools_instdir = $(mergetoolsdir)
+else
+mergetools_instdir = $(prefix)/$(mergetoolsdir)
+endif
+mergetools_instdir_SQ = $(subst ','\'',$(mergetools_instdir))
+
 install_bindir_programs := $(patsubst %,%$X,$(BINDIR_PROGRAMS_NEED_X)) $(BINDIR_PROGRAMS_NO_X)
 
 install: all
@@ -2270,6 +2288,9 @@ install: all
        $(INSTALL) -m 644 $(SCRIPT_LIB) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
        $(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)'
        $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
+       $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(mergetools_instdir_SQ)'
+       (cd mergetools && $(TAR) cf - .) | \
+       (cd '$(DESTDIR_SQ)$(mergetools_instdir_SQ)' && umask 022 && $(TAR) xof -)
 ifndef NO_PERL
        $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
        $(MAKE) -C gitweb install
index 37287f86c18f8b532feb38b5c70541c8cb3d3a44..f04ac18e33063f7e2cb9ab9eab9a6b86cb089b5a 100644 (file)
--- a/abspath.c
+++ b/abspath.c
@@ -139,3 +139,31 @@ const char *absolute_path(const char *path)
        }
        return buf;
 }
+
+/*
+ * Unlike prefix_path, this should be used if the named file does
+ * not have to interact with index entry; i.e. name of a random file
+ * on the filesystem.
+ */
+const char *prefix_filename(const char *pfx, int pfx_len, const char *arg)
+{
+       static char path[PATH_MAX];
+#ifndef WIN32
+       if (!pfx_len || is_absolute_path(arg))
+               return arg;
+       memcpy(path, pfx, pfx_len);
+       strcpy(path + pfx_len, arg);
+#else
+       char *p;
+       /* don't add prefix to absolute paths, but still replace '\' by '/' */
+       if (is_absolute_path(arg))
+               pfx_len = 0;
+       else if (pfx_len)
+               memcpy(path, pfx, pfx_len);
+       strcpy(path + pfx_len, arg);
+       for (p = path + pfx_len; *p; p++)
+               if (*p == '\\')
+                       *p = '/';
+#endif
+       return path;
+}
index 0be4b5f008e1646946ba12fa5e51aa0830911ece..e02e632df380a8e9772d9cd9b1282204c56a7d4e 100644 (file)
--- a/advice.c
+++ b/advice.c
@@ -19,6 +19,15 @@ static struct {
        { "detachedhead", &advice_detached_head },
 };
 
+void advise(const char *advice, ...)
+{
+       va_list params;
+
+       va_start(params, advice);
+       vreportf("hint: ", advice, params);
+       va_end(params);
+}
+
 int git_default_advice_config(const char *var, const char *value)
 {
        const char *k = skip_prefix(var, "advice.");
@@ -34,16 +43,24 @@ int git_default_advice_config(const char *var, const char *value)
        return 0;
 }
 
-void NORETURN die_resolve_conflict(const char *me)
+int error_resolve_conflict(const char *me)
 {
-       if (advice_resolve_conflict)
+       error("'%s' is not possible because you have unmerged files.", me);
+       if (advice_resolve_conflict) {
                /*
                 * Message used both when 'git commit' fails and when
                 * other commands doing a merge do.
                 */
-               die("'%s' is not possible because you have unmerged files.\n"
-                   "Please, fix them up in the work tree, and then use 'git add/rm <file>' as\n"
-                   "appropriate to mark resolution and make a commit, or use 'git commit -a'.", me);
-       else
-               die("'%s' is not possible because you have unmerged files.", me);
+               advise("Fix them up in the work tree,");
+               advise("and then use 'git add/rm <file>' as");
+               advise("appropriate to mark resolution and make a commit,");
+               advise("or use 'git commit -a'.");
+       }
+       return -1;
+}
+
+void NORETURN die_resolve_conflict(const char *me)
+{
+       error_resolve_conflict(me);
+       die("Exiting because of an unresolved conflict.");
 }
index 3244ebb5c1caffebaaf8f28ce221533c38ec29b5..e5d0af782b1445b48b49cd58f481a593268c3384 100644 (file)
--- a/advice.h
+++ b/advice.h
@@ -11,7 +11,8 @@ extern int advice_implicit_identity;
 extern int advice_detached_head;
 
 int git_default_advice_config(const char *var, const char *value);
-
+void advise(const char *advice, ...);
+int error_resolve_conflict(const char *me);
 extern void NORETURN die_resolve_conflict(const char *me);
 
 #endif /* ADVICE_H */
diff --git a/attr.c b/attr.c
index da29c8eb452af6a0a07829ab1ed2409c8aea5ef2..33cb4e4d113cbb3816ba824cb06bf494a4bd9bc3 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -1,3 +1,12 @@
+/*
+ * Handle git attributes.  See gitattributes(5) for a description of
+ * the file syntax, and Documentation/technical/api-gitattributes.txt
+ * for a description of the API.
+ *
+ * One basic design decision here is that we are not going to support
+ * an insanely large number of attributes.
+ */
+
 #define NO_THE_INDEX_COMPATIBILITY_MACROS
 #include "cache.h"
 #include "exec_cmd.h"
@@ -13,12 +22,7 @@ static const char git_attr__unknown[] = "(builtin)unknown";
 
 static const char *attributes_file;
 
-/*
- * The basic design decision here is that we are not going to have
- * insanely large number of attributes.
- *
- * This is a randomly chosen prime.
- */
+/* This is a randomly chosen prime. */
 #define HASHSIZE 257
 
 #ifndef DEBUG_ATTR
@@ -106,22 +110,26 @@ struct git_attr *git_attr(const char *name)
        return git_attr_internal(name, strlen(name));
 }
 
-/*
- * .gitattributes file is one line per record, each of which is
- *
- * (1) glob pattern.
- * (2) whitespace
- * (3) whitespace separated list of attribute names, each of which
- *     could be prefixed with '-' to mean "set to false", '!' to mean
- *     "unset".
- */
-
 /* What does a matched pattern decide? */
 struct attr_state {
        struct git_attr *attr;
        const char *setto;
 };
 
+/*
+ * One rule, as from a .gitattributes file.
+ *
+ * If is_macro is true, then u.attr is a pointer to the git_attr being
+ * defined.
+ *
+ * If is_macro is false, then u.pattern points at the filename pattern
+ * to which the rule applies.  (The memory pointed to is part of the
+ * memory block allocated for the match_attr instance.)
+ *
+ * In either case, num_attr is the number of attributes affected by
+ * this rule, and state is an array listing them.  The attributes are
+ * listed as they appear in the file (macros unexpanded).
+ */
 struct match_attr {
        union {
                char *pattern;
@@ -134,8 +142,15 @@ struct match_attr {
 
 static const char blank[] = " \t\r\n";
 
+/*
+ * Parse a whitespace-delimited attribute state (i.e., "attr",
+ * "-attr", "!attr", or "attr=value") from the string starting at src.
+ * If e is not NULL, write the results to *e.  Return a pointer to the
+ * remainder of the string (with leading whitespace removed), or NULL
+ * if there was an error.
+ */
 static const char *parse_attr(const char *src, int lineno, const char *cp,
-                             int *num_attr, struct match_attr *res)
+                             struct attr_state *e)
 {
        const char *ep, *equals;
        int len;
@@ -148,7 +163,7 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,
                len = equals - cp;
        else
                len = ep - cp;
-       if (!res) {
+       if (!e) {
                if (*cp == '-' || *cp == '!') {
                        cp++;
                        len--;
@@ -160,9 +175,6 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,
                        return NULL;
                }
        } else {
-               struct attr_state *e;
-
-               e = &(res->state[*num_attr]);
                if (*cp == '-' || *cp == '!') {
                        e->setto = (*cp == '-') ? ATTR__FALSE : ATTR__UNSET;
                        cp++;
@@ -175,7 +187,6 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,
                }
                e->attr = git_attr_internal(cp, len);
        }
-       (*num_attr)++;
        return ep + strspn(ep, blank);
 }
 
@@ -183,10 +194,9 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
                                          int lineno, int macro_ok)
 {
        int namelen;
-       int num_attr;
-       const char *cp, *name;
+       int num_attr, i;
+       const char *cp, *name, *states;
        struct match_attr *res = NULL;
-       int pass;
        int is_macro;
 
        cp = line + strspn(line, blank);
@@ -215,32 +225,35 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
        else
                is_macro = 0;
 
-       for (pass = 0; pass < 2; pass++) {
-               /* pass 0 counts and allocates, pass 1 fills */
-               num_attr = 0;
-               cp = name + namelen;
-               cp = cp + strspn(cp, blank);
-               while (*cp) {
-                       cp = parse_attr(src, lineno, cp, &num_attr, res);
-                       if (!cp)
-                               return NULL;
-               }
-               if (pass)
-                       break;
-               res = xcalloc(1,
-                             sizeof(*res) +
-                             sizeof(struct attr_state) * num_attr +
-                             (is_macro ? 0 : namelen + 1));
-               if (is_macro)
-                       res->u.attr = git_attr_internal(name, namelen);
-               else {
-                       res->u.pattern = (char *)&(res->state[num_attr]);
-                       memcpy(res->u.pattern, name, namelen);
-                       res->u.pattern[namelen] = 0;
-               }
-               res->is_macro = is_macro;
-               res->num_attr = num_attr;
+       states = name + namelen;
+       states += strspn(states, blank);
+
+       /* First pass to count the attr_states */
+       for (cp = states, num_attr = 0; *cp; num_attr++) {
+               cp = parse_attr(src, lineno, cp, NULL);
+               if (!cp)
+                       return NULL;
+       }
+
+       res = xcalloc(1,
+                     sizeof(*res) +
+                     sizeof(struct attr_state) * num_attr +
+                     (is_macro ? 0 : namelen + 1));
+       if (is_macro)
+               res->u.attr = git_attr_internal(name, namelen);
+       else {
+               res->u.pattern = (char *)&(res->state[num_attr]);
+               memcpy(res->u.pattern, name, namelen);
+               res->u.pattern[namelen] = 0;
        }
+       res->is_macro = is_macro;
+       res->num_attr = num_attr;
+
+       /* Second pass to fill the attr_states */
+       for (cp = states, i = 0; *cp; i++) {
+               cp = parse_attr(src, lineno, cp, &(res->state[i]));
+       }
+
        return res;
 }
 
index c0c865a4b1b0fca038f32b77f2239d7987438ff5..d8098762f62a9dfb991f64702d34047182cfa951 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -3,6 +3,7 @@
 #include "refs.h"
 #include "remote.h"
 #include "commit.h"
+#include "sequencer.h"
 
 struct tracking {
        struct refspec spec;
@@ -135,6 +136,28 @@ static int setup_tracking(const char *new_ref, const char *orig_ref,
        return 0;
 }
 
+int validate_new_branchname(const char *name, struct strbuf *ref,
+                           int force, int attr_only)
+{
+       if (strbuf_check_branch_ref(ref, name))
+               die("'%s' is not a valid branch name.", name);
+
+       if (!ref_exists(ref->buf))
+               return 0;
+       else if (!force && !attr_only)
+               die("A branch named '%s' already exists.", ref->buf + strlen("refs/heads/"));
+
+       if (!attr_only) {
+               const char *head;
+               unsigned char sha1[20];
+
+               head = resolve_ref("HEAD", sha1, 0, NULL);
+               if (!is_bare_repository() && head && !strcmp(head, ref->buf))
+                       die("Cannot force update the current branch.");
+       }
+       return 1;
+}
+
 void create_branch(const char *head,
                   const char *name, const char *start_name,
                   int force, int reflog, enum branch_track track)
@@ -151,17 +174,12 @@ void create_branch(const char *head,
        if (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)
                explicit_tracking = 1;
 
-       if (strbuf_check_branch_ref(&ref, name))
-               die("'%s' is not a valid branch name.", name);
-
-       if (resolve_ref(ref.buf, sha1, 1, NULL)) {
-               if (!force && track == BRANCH_TRACK_OVERRIDE)
+       if (validate_new_branchname(name, &ref, force,
+                                   track == BRANCH_TRACK_OVERRIDE)) {
+               if (!force)
                        dont_change_ref = 1;
-               else if (!force)
-                       die("A branch named '%s' already exists.", name);
-               else if (!is_bare_repository() && head && !strcmp(head, name))
-                       die("Cannot force update the current branch.");
-               forcing = 1;
+               else
+                       forcing = 1;
        }
 
        real_ref = NULL;
@@ -210,7 +228,7 @@ void create_branch(const char *head,
                         start_name);
 
        if (real_ref && track)
-               setup_tracking(name, real_ref, track);
+               setup_tracking(ref.buf+11, real_ref, track);
 
        if (!dont_change_ref)
                if (write_ref_sha1(lock, sha1, msg) < 0)
@@ -228,4 +246,5 @@ void remove_branch_state(void)
        unlink(git_path("MERGE_MSG"));
        unlink(git_path("MERGE_MODE"));
        unlink(git_path("SQUASH_MSG"));
+       remove_sequencer_state(0);
 }
index 4026e3832b265c4cef6e5bc151976771867b3da9..1285158dd4f26e5bbb0e0d7133055f168fee773f 100644 (file)
--- a/branch.h
+++ b/branch.h
 void create_branch(const char *head, const char *name, const char *start_name,
                   int force, int reflog, enum branch_track track);
 
+/*
+ * Validates that the requested branch may be created, returning the
+ * interpreted ref in ref, force indicates whether (non-head) branches
+ * may be overwritten. A non-zero return value indicates that the force
+ * parameter was non-zero and the branch already exists.
+ *
+ * Contrary to all of the above, when attr_only is 1, the caller is
+ * not interested in verifying if it is Ok to update the named
+ * branch to point at a potentially different commit. It is merely
+ * asking if it is OK to change some attribute for the named branch
+ * (e.g. tracking upstream).
+ *
+ * NEEDSWORK: This needs to be split into two separate functions in the
+ * longer run for sanity.
+ *
+ */
+int validate_new_branchname(const char *name, struct strbuf *ref, int force, int attr_only);
+
 /*
  * Remove information about the state of working on the current
  * branch. (E.g., MERGE_HEAD)
index 3142daa57a6fa1c8a7d21095946bf5d26443d0e0..f49596f826228e22d354194ddbb73ec100d8728e 100644 (file)
@@ -71,7 +71,7 @@ static int parse_branch_color_slot(const char *var, int ofs)
 static int git_branch_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "color.branch")) {
-               branch_use_color = git_config_colorbool(var, value, -1);
+               branch_use_color = git_config_colorbool(var, value);
                return 0;
        }
        if (!prefixcmp(var, "color.branch.")) {
@@ -88,7 +88,7 @@ static int git_branch_config(const char *var, const char *value, void *cb)
 
 static const char *branch_get_color(enum color_branch ix)
 {
-       if (branch_use_color > 0)
+       if (want_color(branch_use_color))
                return branch_colors[ix];
        return "";
 }
@@ -566,11 +566,7 @@ static void rename_branch(const char *oldname, const char *newname, int force)
                        die(_("Invalid branch name: '%s'"), oldname);
        }
 
-       if (strbuf_check_branch_ref(&newref, newname))
-               die(_("Invalid branch name: '%s'"), newname);
-
-       if (resolve_ref(newref.buf, sha1, 1, NULL) && !force)
-               die(_("A branch named '%s' already exists."), newref.buf + 11);
+       validate_new_branchname(newname, &newref, force, 0);
 
        strbuf_addf(&logmsg, "Branch: renamed %s to %s",
                 oldref.buf, newref.buf);
@@ -613,7 +609,7 @@ static int opt_parse_merge_filter(const struct option *opt, const char *arg, int
 int cmd_branch(int argc, const char **argv, const char *prefix)
 {
        int delete = 0, rename = 0, force_create = 0;
-       int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0;
+       int verbose = 0, abbrev = -1, detached = 0;
        int reflog = 0;
        enum branch_track track;
        int kinds = REF_LOCAL_BRANCH;
@@ -673,9 +669,6 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 
        git_config(git_branch_config, NULL);
 
-       if (branch_use_color == -1)
-               branch_use_color = git_use_color_default;
-
        track = git_branch_track;
 
        head = resolve_ref("HEAD", head_sha1, 0, NULL);
@@ -696,6 +689,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        if (!!delete + !!rename + !!force_create > 1)
                usage_with_options(builtin_branch_usage, options);
 
+       if (abbrev == -1)
+               abbrev = DEFAULT_ABBREV;
+
        if (delete)
                return delete_branches(argc, argv, delete > 1, kinds);
        else if (argc == 0)
index 81046a9cb870aab59a56492e175a51c032ad96ce..92a8a6026a9bd6da5394e7b37f07dbe91f73db9c 100644 (file)
@@ -58,7 +58,7 @@ int cmd_bundle(int argc, const char **argv, const char *prefix)
        } else if (!strcmp(cmd, "unbundle")) {
                if (!startup_info->have_repository)
                        die(_("Need a repository to unbundle."));
-               return !!unbundle(&header, bundle_fd) ||
+               return !!unbundle(&header, bundle_fd, 0) ||
                        list_bundle_refs(&header, argc, argv);
        } else
                usage(builtin_bundle_usage);
index ae3f28115a7a4d65d8bb6f284cd1ececa4f9c2ef..0723cf245e52e6c0ab99cbe92c74010f9f7d7167 100644 (file)
@@ -12,8 +12,8 @@ static const char builtin_check_ref_format_usage[] =
 "   or: git check-ref-format --branch <branchname-shorthand>";
 
 /*
- * Replace each run of adjacent slashes in src with a single slash,
- * and write the result to dst.
+ * Remove leading slashes and replace each run of adjacent slashes in
+ * src with a single slash, and write the result to dst.
  *
  * This function is similar to normalize_path_copy(), but stripped down
  * to meet check_ref_format's simpler needs.
@@ -21,7 +21,7 @@ static const char builtin_check_ref_format_usage[] =
 static void collapse_slashes(char *dst, const char *src)
 {
        char ch;
-       char prev = '\0';
+       char prev = '/';
 
        while ((ch = *src++) != '\0') {
                if (prev == '/' && ch == prev)
index d647a313036df65d928d028463564b5390756901..5e356a6c6178e08b90d992b0eae36048b5d81513 100644 (file)
@@ -201,7 +201,7 @@ static int checkout_merged(int pos, struct checkout *state)
 }
 
 static int checkout_paths(struct tree *source_tree, const char **pathspec,
-                         struct checkout_opts *opts)
+                         const char *prefix, struct checkout_opts *opts)
 {
        int pos;
        struct checkout state;
@@ -231,7 +231,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
                match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, ps_matched);
        }
 
-       if (report_path_error(ps_matched, pathspec, 0))
+       if (report_path_error(ps_matched, pathspec, prefix))
                return 1;
 
        /* "checkout -m path" to recreate conflicted state */
@@ -1064,7 +1064,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
                        die(_("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index."));
 
-               return checkout_paths(source_tree, pathspec, &opts);
+               return checkout_paths(source_tree, pathspec, prefix, &opts);
        }
 
        if (patch_mode)
@@ -1072,15 +1072,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
        if (opts.new_branch) {
                struct strbuf buf = STRBUF_INIT;
-               if (strbuf_check_branch_ref(&buf, opts.new_branch))
-                       die(_("git checkout: we do not like '%s' as a branch name."),
-                           opts.new_branch);
-               if (ref_exists(buf.buf)) {
-                       opts.branch_exists = 1;
-                       if (!opts.new_branch_force)
-                               die(_("git checkout: branch %s already exists"),
-                                   opts.new_branch);
-               }
+
+               opts.branch_exists = validate_new_branchname(opts.new_branch, &buf,
+                                                            !!opts.new_branch_force, 0);
+
                strbuf_release(&buf);
        }
 
index 75697f711116e42df1e21e608c21829f49549b08..0c7b3d0f4c28c9e9c721c961e529196751271d11 100644 (file)
@@ -54,7 +54,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
                OPT_BOOLEAN('d', NULL, &remove_directories,
                                "remove whole directories"),
                { OPTION_CALLBACK, 'e', "exclude", &exclude_list, "pattern",
-                 "exclude <pattern>", PARSE_OPT_NONEG, exclude_cb },
+                 "add <pattern> to ignore rules", PARSE_OPT_NONEG, exclude_cb },
                OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"),
                OPT_BOOLEAN('X', NULL, &ignored_only,
                                "remove only ignored files"),
@@ -98,7 +98,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
                setup_standard_excludes(&dir);
 
        for (i = 0; i < exclude_list.nr; i++)
-               add_exclude(exclude_list.items[i].string, "", 0, dir.exclude_list);
+               add_exclude(exclude_list.items[i].string, "", 0,
+                           &dir.exclude_list[EXC_CMDL]);
 
        pathspec = get_pathspec(prefix, argv);
 
index 7663bc22c9a3cb35b803815762791fec44d4c477..488f48e9a571fa9b958cd95b92808b44f4541982 100644 (file)
@@ -39,7 +39,7 @@ static const char * const builtin_clone_usage[] = {
 
 static int option_no_checkout, option_bare, option_mirror;
 static int option_local, option_no_hardlinks, option_shared, option_recursive;
-static char *option_template, *option_reference, *option_depth;
+static char *option_template, *option_depth;
 static char *option_origin = NULL;
 static char *option_branch = NULL;
 static const char *real_git_dir;
@@ -47,6 +47,16 @@ static char *option_upload_pack = "git-upload-pack";
 static int option_verbosity;
 static int option_progress;
 static struct string_list option_config;
+static struct string_list option_reference;
+
+static int opt_parse_reference(const struct option *opt, const char *arg, int unset)
+{
+       struct string_list *option_reference = opt->value;
+       if (!arg)
+               return -1;
+       string_list_append(option_reference, arg);
+       return 0;
+}
 
 static struct option builtin_clone_options[] = {
        OPT__VERBOSITY(&option_verbosity),
@@ -72,8 +82,8 @@ static struct option builtin_clone_options[] = {
                    "initialize submodules in the clone"),
        OPT_STRING(0, "template", &option_template, "template-directory",
                   "directory from which templates will be used"),
-       OPT_STRING(0, "reference", &option_reference, "repo",
-                  "reference repository"),
+       OPT_CALLBACK(0 , "reference", &option_reference, "repo",
+                    "reference repository", &opt_parse_reference),
        OPT_STRING('o', "origin", &option_origin, "branch",
                   "use <branch> instead of 'origin' to track upstream"),
        OPT_STRING('b', "branch", &option_branch, "branch",
@@ -103,9 +113,26 @@ static char *get_repo_path(const char *repo, int *is_bundle)
        for (i = 0; i < ARRAY_SIZE(suffix); i++) {
                const char *path;
                path = mkpath("%s%s", repo, suffix[i]);
-               if (is_directory(path)) {
+               if (stat(path, &st))
+                       continue;
+               if (S_ISDIR(st.st_mode)) {
                        *is_bundle = 0;
                        return xstrdup(absolute_path(path));
+               } else if (S_ISREG(st.st_mode) && st.st_size > 8) {
+                       /* Is it a "gitfile"? */
+                       char signature[8];
+                       int len, fd = open(path, O_RDONLY);
+                       if (fd < 0)
+                               continue;
+                       len = read_in_full(fd, signature, 8);
+                       close(fd);
+                       if (len != 8 || strncmp(signature, "gitdir: ", 8))
+                               continue;
+                       path = read_gitfile(path);
+                       if (path) {
+                               *is_bundle = 0;
+                               return xstrdup(absolute_path(path));
+                       }
                }
        }
 
@@ -199,39 +226,80 @@ static void strip_trailing_slashes(char *dir)
        *end = '\0';
 }
 
-static void setup_reference(const char *repo)
+static int add_one_reference(struct string_list_item *item, void *cb_data)
 {
-       const char *ref_git;
-       char *ref_git_copy;
-
+       char *ref_git;
+       struct strbuf alternate = STRBUF_INIT;
        struct remote *remote;
        struct transport *transport;
        const struct ref *extra;
 
-       ref_git = real_path(option_reference);
-
-       if (is_directory(mkpath("%s/.git/objects", ref_git)))
-               ref_git = mkpath("%s/.git", ref_git);
-       else if (!is_directory(mkpath("%s/objects", ref_git)))
+       /* Beware: real_path() and mkpath() return static buffer */
+       ref_git = xstrdup(real_path(item->string));
+       if (is_directory(mkpath("%s/.git/objects", ref_git))) {
+               char *ref_git_git = xstrdup(mkpath("%s/.git", ref_git));
+               free(ref_git);
+               ref_git = ref_git_git;
+       } else if (!is_directory(mkpath("%s/objects", ref_git)))
                die(_("reference repository '%s' is not a local directory."),
-                   option_reference);
+                   item->string);
 
-       ref_git_copy = xstrdup(ref_git);
+       strbuf_addf(&alternate, "%s/objects", ref_git);
+       add_to_alternates_file(alternate.buf);
+       strbuf_release(&alternate);
 
-       add_to_alternates_file(ref_git_copy);
-
-       remote = remote_get(ref_git_copy);
-       transport = transport_get(remote, ref_git_copy);
+       remote = remote_get(ref_git);
+       transport = transport_get(remote, ref_git);
        for (extra = transport_get_remote_refs(transport); extra;
             extra = extra->next)
                add_extra_ref(extra->name, extra->old_sha1, 0);
 
        transport_disconnect(transport);
+       free(ref_git);
+       return 0;
+}
 
-       free(ref_git_copy);
+static void setup_reference(void)
+{
+       for_each_string_list(&option_reference, add_one_reference, NULL);
 }
 
-static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest)
+static void copy_alternates(struct strbuf *src, struct strbuf *dst,
+                           const char *src_repo)
+{
+       /*
+        * Read from the source objects/info/alternates file
+        * and copy the entries to corresponding file in the
+        * destination repository with add_to_alternates_file().
+        * Both src and dst have "$path/objects/info/alternates".
+        *
+        * Instead of copying bit-for-bit from the original,
+        * we need to append to existing one so that the already
+        * created entry via "clone -s" is not lost, and also
+        * to turn entries with paths relative to the original
+        * absolute, so that they can be used in the new repository.
+        */
+       FILE *in = fopen(src->buf, "r");
+       struct strbuf line = STRBUF_INIT;
+
+       while (strbuf_getline(&line, in, '\n') != EOF) {
+               char *abs_path, abs_buf[PATH_MAX];
+               if (!line.len || line.buf[0] == '#')
+                       continue;
+               if (is_absolute_path(line.buf)) {
+                       add_to_alternates_file(line.buf);
+                       continue;
+               }
+               abs_path = mkpath("%s/objects/%s", src_repo, line.buf);
+               normalize_path_copy(abs_buf, abs_path);
+               add_to_alternates_file(abs_buf);
+       }
+       strbuf_release(&line);
+       fclose(in);
+}
+
+static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
+                                  const char *src_repo, int src_baselen)
 {
        struct dirent *de;
        struct stat buf;
@@ -267,7 +335,14 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest)
                }
                if (S_ISDIR(buf.st_mode)) {
                        if (de->d_name[0] != '.')
-                               copy_or_link_directory(src, dest);
+                               copy_or_link_directory(src, dest,
+                                                      src_repo, src_baselen);
+                       continue;
+               }
+
+               /* Files that cannot be copied bit-for-bit... */
+               if (!strcmp(src->buf + src_baselen, "/info/alternates")) {
+                       copy_alternates(src, dest, src_repo);
                        continue;
                }
 
@@ -290,17 +365,20 @@ static const struct ref *clone_local(const char *src_repo,
                                     const char *dest_repo)
 {
        const struct ref *ret;
-       struct strbuf src = STRBUF_INIT;
-       struct strbuf dest = STRBUF_INIT;
        struct remote *remote;
        struct transport *transport;
 
-       if (option_shared)
-               add_to_alternates_file(src_repo);
-       else {
+       if (option_shared) {
+               struct strbuf alt = STRBUF_INIT;
+               strbuf_addf(&alt, "%s/objects", src_repo);
+               add_to_alternates_file(alt.buf);
+               strbuf_release(&alt);
+       } else {
+               struct strbuf src = STRBUF_INIT;
+               struct strbuf dest = STRBUF_INIT;
                strbuf_addf(&src, "%s/objects", src_repo);
                strbuf_addf(&dest, "%s/objects", dest_repo);
-               copy_or_link_directory(&src, &dest);
+               copy_or_link_directory(&src, &dest, src_repo, src.len);
                strbuf_release(&src);
                strbuf_release(&dest);
        }
@@ -544,8 +622,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        git_config_set(key.buf, repo);
        strbuf_reset(&key);
 
-       if (option_reference)
-               setup_reference(git_dir);
+       if (option_reference.nr)
+               setup_reference();
 
        fetch_pattern = value.buf;
        refspec = parse_fetch_refspec(1, &fetch_pattern);
index cb738574f729e91ff239f12db3f3f1ef1ef9570c..cbc9613ec661bc2cef8274cd66efb06b9cab55b6 100644 (file)
@@ -62,8 +62,6 @@ N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\
 "\n"
 "Otherwise, please use 'git reset'\n");
 
-static unsigned char head_sha1[20];
-
 static const char *use_message_buffer;
 static const char commit_editmsg[] = "COMMIT_EDITMSG";
 static struct lock_file index_lock; /* real index */
@@ -102,7 +100,7 @@ static enum {
 static char *cleanup_arg;
 
 static enum commit_whence whence;
-static int use_editor = 1, initial_commit, include_status = 1;
+static int use_editor = 1, include_status = 1;
 static int show_ignored_in_status;
 static const char *only_include_assumed;
 static struct strbuf message;
@@ -274,7 +272,7 @@ static int list_paths(struct string_list *list, const char *with_tree,
                        item->util = item; /* better a valid pointer than a fake one */
        }
 
-       return report_path_error(m, pattern, prefix ? strlen(prefix) : 0);
+       return report_path_error(m, pattern, prefix);
 }
 
 static void add_remove_files(struct string_list *list)
@@ -296,13 +294,13 @@ static void add_remove_files(struct string_list *list)
        }
 }
 
-static void create_base_index(void)
+static void create_base_index(const struct commit *current_head)
 {
        struct tree *tree;
        struct unpack_trees_options opts;
        struct tree_desc t;
 
-       if (initial_commit) {
+       if (!current_head) {
                discard_cache();
                return;
        }
@@ -315,7 +313,7 @@ static void create_base_index(void)
        opts.dst_index = &the_index;
 
        opts.fn = oneway_merge;
-       tree = parse_tree_indirect(head_sha1);
+       tree = parse_tree_indirect(current_head->object.sha1);
        if (!tree)
                die(_("failed to unpack HEAD tree object"));
        parse_tree(tree);
@@ -334,7 +332,8 @@ static void refresh_cache_or_die(int refresh_flags)
                die_resolve_conflict("commit");
 }
 
-static char *prepare_index(int argc, const char **argv, const char *prefix, int is_status)
+static char *prepare_index(int argc, const char **argv, const char *prefix,
+                          const struct commit *current_head, int is_status)
 {
        int fd;
        struct string_list partial;
@@ -450,7 +449,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
 
        memset(&partial, 0, sizeof(partial));
        partial.strdup_strings = 1;
-       if (list_paths(&partial, initial_commit ? NULL : "HEAD", prefix, pathspec))
+       if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, pathspec))
                exit(1);
 
        discard_cache();
@@ -469,7 +468,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
                                                (uintmax_t) getpid()),
                                       LOCK_DIE_ON_ERROR);
 
-       create_base_index();
+       create_base_index(current_head);
        add_remove_files(&partial);
        refresh_cache(REFRESH_QUIET);
 
@@ -518,12 +517,9 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int
        return s->commitable;
 }
 
-static int is_a_merge(const unsigned char *sha1)
+static int is_a_merge(const struct commit *current_head)
 {
-       struct commit *commit = lookup_commit(sha1);
-       if (!commit || parse_commit(commit))
-               die(_("could not parse HEAD commit"));
-       return !!(commit->parents && commit->parents->next);
+       return !!(current_head->parents && current_head->parents->next);
 }
 
 static const char sign_off_header[] = "Signed-off-by: ";
@@ -627,6 +623,7 @@ static char *cut_ident_timestamp_part(char *string)
 }
 
 static int prepare_to_commit(const char *index_file, const char *prefix,
+                            struct commit *current_head,
                             struct wt_status *s,
                             struct strbuf *author_ident)
 {
@@ -848,7 +845,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
         * empty due to conflict resolution, which the user should okay.
         */
        if (!commitable && whence != FROM_MERGE && !allow_empty &&
-           !(amend && is_a_merge(head_sha1))) {
+           !(amend && is_a_merge(current_head))) {
                run_status(stdout, index_file, prefix, 0, s);
                if (amend)
                        fputs(_(empty_amend_advice), stderr);
@@ -1006,6 +1003,7 @@ static const char *read_commit_message(const char *name)
 static int parse_and_validate_options(int argc, const char *argv[],
                                      const char * const usage[],
                                      const char *prefix,
+                                     struct commit *current_head,
                                      struct wt_status *s)
 {
        int f = 0;
@@ -1026,11 +1024,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
        if (!use_editor)
                setenv("GIT_EDITOR", ":", 1);
 
-       if (get_sha1("HEAD", head_sha1))
-               initial_commit = 1;
-
        /* Sanity check options */
-       if (amend && initial_commit)
+       if (amend && !current_head)
                die(_("You have nothing to amend."));
        if (amend && whence != FROM_COMMIT)
                die(_("You are in the middle of a %s -- cannot amend."), whence_s());
@@ -1102,12 +1097,12 @@ static int parse_and_validate_options(int argc, const char *argv[],
 }
 
 static int dry_run_commit(int argc, const char **argv, const char *prefix,
-                         struct wt_status *s)
+                         const struct commit *current_head, struct wt_status *s)
 {
        int commitable;
        const char *index_file;
 
-       index_file = prepare_index(argc, argv, prefix, 1);
+       index_file = prepare_index(argc, argv, prefix, current_head, 1);
        commitable = run_status(stdout, index_file, prefix, 0, s);
        rollback_index_files();
 
@@ -1146,7 +1141,7 @@ static int git_status_config(const char *k, const char *v, void *cb)
                return 0;
        }
        if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
-               s->use_color = git_config_colorbool(k, v, -1);
+               s->use_color = git_config_colorbool(k, v);
                return 0;
        }
        if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) {
@@ -1239,10 +1234,6 @@ int cmd_status(int argc, const char **argv, const char *prefix)
 
        if (s.relative_paths)
                s.prefix = prefix;
-       if (s.use_color == -1)
-               s.use_color = git_use_color_default;
-       if (diff_use_color_default == -1)
-               diff_use_color_default = git_use_color_default;
 
        switch (status_format) {
        case STATUS_FORMAT_SHORT:
@@ -1260,7 +1251,8 @@ int cmd_status(int argc, const char **argv, const char *prefix)
        return 0;
 }
 
-static void print_summary(const char *prefix, const unsigned char *sha1)
+static void print_summary(const char *prefix, const unsigned char *sha1,
+                         int initial_commit)
 {
        struct rev_info rev;
        struct commit *commit;
@@ -1382,12 +1374,13 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
        struct strbuf author_ident = STRBUF_INIT;
        const char *index_file, *reflog_msg;
        char *nl, *p;
-       unsigned char commit_sha1[20];
+       unsigned char sha1[20];
        struct ref_lock *ref_lock;
        struct commit_list *parents = NULL, **pptr = &parents;
        struct stat statbuf;
        int allow_fast_forward = 1;
        struct wt_status s;
+       struct commit *current_head = NULL;
 
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_commit_usage, builtin_commit_options);
@@ -1396,40 +1389,38 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
        git_config(git_commit_config, &s);
        determine_whence(&s);
 
-       if (s.use_color == -1)
-               s.use_color = git_use_color_default;
-       argc = parse_and_validate_options(argc, argv, builtin_commit_usage,
-                                         prefix, &s);
-       if (dry_run) {
-               if (diff_use_color_default == -1)
-                       diff_use_color_default = git_use_color_default;
-               return dry_run_commit(argc, argv, prefix, &s);
+       if (get_sha1("HEAD", sha1))
+               current_head = NULL;
+       else {
+               current_head = lookup_commit(sha1);
+               if (!current_head || parse_commit(current_head))
+                       die(_("could not parse HEAD commit"));
        }
-       index_file = prepare_index(argc, argv, prefix, 0);
+       argc = parse_and_validate_options(argc, argv, builtin_commit_usage,
+                                         prefix, current_head, &s);
+       if (dry_run)
+               return dry_run_commit(argc, argv, prefix, current_head, &s);
+       index_file = prepare_index(argc, argv, prefix, current_head, 0);
 
        /* Set up everything for writing the commit object.  This includes
           running hooks, writing the trees, and interacting with the user.  */
-       if (!prepare_to_commit(index_file, prefix, &s, &author_ident)) {
+       if (!prepare_to_commit(index_file, prefix,
+                              current_head, &s, &author_ident)) {
                rollback_index_files();
                return 1;
        }
 
        /* Determine parents */
        reflog_msg = getenv("GIT_REFLOG_ACTION");
-       if (initial_commit) {
+       if (!current_head) {
                if (!reflog_msg)
                        reflog_msg = "commit (initial)";
        } else if (amend) {
                struct commit_list *c;
-               struct commit *commit;
 
                if (!reflog_msg)
                        reflog_msg = "commit (amend)";
-               commit = lookup_commit(head_sha1);
-               if (!commit || parse_commit(commit))
-                       die(_("could not parse HEAD commit"));
-
-               for (c = commit->parents; c; c = c->next)
+               for (c = current_head->parents; c; c = c->next)
                        pptr = &commit_list_insert(c->item, pptr)->next;
        } else if (whence == FROM_MERGE) {
                struct strbuf m = STRBUF_INIT;
@@ -1437,7 +1428,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 
                if (!reflog_msg)
                        reflog_msg = "commit (merge)";
-               pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
+               pptr = &commit_list_insert(current_head, pptr)->next;
                fp = fopen(git_path("MERGE_HEAD"), "r");
                if (fp == NULL)
                        die_errno(_("could not open '%s' for reading"),
@@ -1463,7 +1454,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                        reflog_msg = (whence == FROM_CHERRY_PICK)
                                        ? "commit (cherry-pick)"
                                        : "commit";
-               pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
+               pptr = &commit_list_insert(current_head, pptr)->next;
        }
 
        /* Finally, get the commit message */
@@ -1489,7 +1480,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                exit(1);
        }
 
-       if (commit_tree(sb.buf, active_cache_tree->sha1, parents, commit_sha1,
+       if (commit_tree(sb.buf, active_cache_tree->sha1, parents, sha1,
                        author_ident.buf)) {
                rollback_index_files();
                die(_("failed to write commit object"));
@@ -1497,7 +1488,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
        strbuf_release(&author_ident);
 
        ref_lock = lock_any_ref_for_update("HEAD",
-                                          initial_commit ? NULL : head_sha1,
+                                          !current_head
+                                          ? NULL
+                                          : current_head->object.sha1,
                                           0);
 
        nl = strchr(sb.buf, '\n');
@@ -1512,7 +1505,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                rollback_index_files();
                die(_("cannot lock HEAD ref"));
        }
-       if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0) {
+       if (write_ref_sha1(ref_lock, sha1, sb.buf) < 0) {
                rollback_index_files();
                die(_("cannot update HEAD ref"));
        }
@@ -1534,13 +1527,14 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                struct notes_rewrite_cfg *cfg;
                cfg = init_copy_notes_for_rewrite("amend");
                if (cfg) {
-                       copy_note_for_rewrite(cfg, head_sha1, commit_sha1);
+                       /* we are amending, so current_head is not NULL */
+                       copy_note_for_rewrite(cfg, current_head->object.sha1, sha1);
                        finish_copy_notes_for_rewrite(cfg);
                }
-               run_rewrite_hook(head_sha1, commit_sha1);
+               run_rewrite_hook(current_head->object.sha1, sha1);
        }
        if (!quiet)
-               print_summary(prefix, commit_sha1);
+               print_summary(prefix, sha1, !current_head);
 
        return 0;
 }
index 211e118d575411b712ccf0387db2408708576902..0b4ecac855dce9b70878de4d15f4d317254d9ef3 100644 (file)
@@ -303,24 +303,18 @@ static void get_color(const char *def_color)
        fputs(parsed_color, stdout);
 }
 
-static int stdout_is_tty;
 static int get_colorbool_found;
 static int get_diff_color_found;
+static int get_color_ui_found;
 static int git_get_colorbool_config(const char *var, const char *value,
                void *cb)
 {
-       if (!strcmp(var, get_colorbool_slot)) {
-               get_colorbool_found =
-                       git_config_colorbool(var, value, stdout_is_tty);
-       }
-       if (!strcmp(var, "diff.color")) {
-               get_diff_color_found =
-                       git_config_colorbool(var, value, stdout_is_tty);
-       }
-       if (!strcmp(var, "color.ui")) {
-               git_use_color_default = git_config_colorbool(var, value, stdout_is_tty);
-               return 0;
-       }
+       if (!strcmp(var, get_colorbool_slot))
+               get_colorbool_found = git_config_colorbool(var, value);
+       else if (!strcmp(var, "diff.color"))
+               get_diff_color_found = git_config_colorbool(var, value);
+       else if (!strcmp(var, "color.ui"))
+               get_color_ui_found = git_config_colorbool(var, value);
        return 0;
 }
 
@@ -334,9 +328,11 @@ static int get_colorbool(int print)
                if (!strcmp(get_colorbool_slot, "color.diff"))
                        get_colorbool_found = get_diff_color_found;
                if (get_colorbool_found < 0)
-                       get_colorbool_found = git_use_color_default;
+                       get_colorbool_found = get_color_ui_found;
        }
 
+       get_colorbool_found = want_color(get_colorbool_found);
+
        if (print) {
                printf("%s\n", get_colorbool_found ? "true" : "false");
                return 0;
@@ -510,9 +506,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
        }
        else if (actions == ACTION_GET_COLORBOOL) {
                if (argc == 1)
-                       stdout_is_tty = git_config_bool("command line", argv[0]);
-               else if (argc == 0)
-                       stdout_is_tty = isatty(1);
+                       color_stdout_is_tty = git_config_bool("command line", argv[0]);
                return get_colorbool(argc != 0);
        }
 
index 66fc291c8a81de71dee7b597c9ab0dc9d70e8e29..9f63067f50a6f49d61d40474608535905bec905b 100644 (file)
@@ -462,8 +462,21 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
                die(_("No names found, cannot describe anything."));
 
        if (argc == 0) {
-               if (dirty && !cmd_diff_index(ARRAY_SIZE(diff_index_args) - 1, diff_index_args, prefix))
-                       dirty = NULL;
+               if (dirty) {
+                       static struct lock_file index_lock;
+                       int fd;
+
+                       read_cache_preload(NULL);
+                       refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED,
+                                     NULL, NULL, NULL);
+                       fd = hold_locked_index(&index_lock, 0);
+                       if (0 <= fd)
+                               update_index_if_able(&the_index, &index_lock);
+
+                       if (!cmd_diff_index(ARRAY_SIZE(diff_index_args) - 1,
+                                           diff_index_args, prefix))
+                               dirty = NULL;
+               }
                describe("HEAD", 1);
        } else if (dirty) {
                die(_("--dirty is incompatible with committishes"));
index 69cd5eed78cb402839813e7eca65b5598afa4a90..1118689fb246b864ce758039543327c4304cdaa4 100644 (file)
@@ -277,9 +277,6 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
        gitmodules_config();
        git_config(git_diff_ui_config, NULL);
 
-       if (diff_use_color_default == -1)
-               diff_use_color_default = git_use_color_default;
-
        init_revisions(&rev, prefix);
 
        /* If this is a no-index diff, just run it and exit there. */
index 3c871c2da893dc9deb3a36bff03a7032ea038f8a..c8bf9b85b07690dce8af5e093e02994ae5c510be 100644 (file)
@@ -15,7 +15,9 @@ static int transfer_unpack_limit = -1;
 static int fetch_unpack_limit = -1;
 static int unpack_limit = 100;
 static int prefer_ofs_delta = 1;
-static int no_done = 0;
+static int no_done;
+static int fetch_fsck_objects = -1;
+static int transfer_fsck_objects = -1;
 static struct fetch_pack_args args = {
        /* .uploadpack = */ "git-upload-pack",
 };
@@ -185,6 +187,36 @@ static void consume_shallow_list(int fd)
        }
 }
 
+struct write_shallow_data {
+       struct strbuf *out;
+       int use_pack_protocol;
+       int count;
+};
+
+static int write_one_shallow(const struct commit_graft *graft, void *cb_data)
+{
+       struct write_shallow_data *data = cb_data;
+       const char *hex = sha1_to_hex(graft->sha1);
+       data->count++;
+       if (data->use_pack_protocol)
+               packet_buf_write(data->out, "shallow %s", hex);
+       else {
+               strbuf_addstr(data->out, hex);
+               strbuf_addch(data->out, '\n');
+       }
+       return 0;
+}
+
+static int write_shallow_commits(struct strbuf *out, int use_pack_protocol)
+{
+       struct write_shallow_data data;
+       data.out = out;
+       data.use_pack_protocol = use_pack_protocol;
+       data.count = 0;
+       for_each_commit_graft(write_one_shallow, &data);
+       return data.count;
+}
+
 static enum ack_type get_ack(int fd, unsigned char *result_sha1)
 {
        static char line[1000];
@@ -704,6 +736,12 @@ static int get_pack(int xd[2], char **pack_lockfile)
        }
        if (*hdr_arg)
                *av++ = hdr_arg;
+       if (fetch_fsck_objects >= 0
+           ? fetch_fsck_objects
+           : transfer_fsck_objects >= 0
+           ? transfer_fsck_objects
+           : 0)
+               *av++ = "--strict";
        *av++ = NULL;
 
        cmd.in = demux.out;
@@ -823,6 +861,16 @@ static int fetch_pack_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
+       if (!strcmp(var, "fetch.fsckobjects")) {
+               fetch_fsck_objects = git_config_bool(var, value);
+               return 0;
+       }
+
+       if (!strcmp(var, "transfer.fsckobjects")) {
+               transfer_fsck_objects = git_config_bool(var, value);
+               return 0;
+       }
+
        return git_default_config(var, value, cb);
 }
 
index 93c99385a95e6ab525fa64c1dbb70d5d4ca103ba..7a4e41cca75b87d3e5a7c4690658d9879777e965 100644 (file)
@@ -13,6 +13,7 @@
 #include "sigchain.h"
 #include "transport.h"
 #include "submodule.h"
+#include "connected.h"
 
 static const char * const builtin_fetch_usage[] = {
        "git fetch [<options>] [<repository> [<refspec>...]]",
@@ -345,6 +346,18 @@ static int update_local_ref(struct ref *ref,
        }
 }
 
+static int iterate_ref_map(void *cb_data, unsigned char sha1[20])
+{
+       struct ref **rm = cb_data;
+       struct ref *ref = *rm;
+
+       if (!ref)
+               return -1; /* end of the list */
+       *rm = ref->next;
+       hashcpy(sha1, ref->old_sha1);
+       return 0;
+}
+
 static int store_updated_refs(const char *raw_url, const char *remote_name,
                struct ref *ref_map)
 {
@@ -364,6 +377,11 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
                url = transport_anonymize_url(raw_url);
        else
                url = xstrdup("foreign");
+
+       rm = ref_map;
+       if (check_everything_connected(iterate_ref_map, 0, &rm))
+               return error(_("%s did not send all necessary objects\n"), url);
+
        for (rm = ref_map; rm; rm = rm->next) {
                struct ref *ref = NULL;
 
@@ -457,23 +475,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
  * We would want to bypass the object transfer altogether if
  * everything we are going to fetch already exists and is connected
  * locally.
- *
- * The refs we are going to fetch are in ref_map.  If running
- *
- *  $ git rev-list --objects --stdin --not --all
- *
- * (feeding all the refs in ref_map on its standard input)
- * does not error out, that means everything reachable from the
- * refs we are going to fetch exists and is connected to some of
- * our existing refs.
  */
 static int quickfetch(struct ref *ref_map)
 {
-       struct child_process revlist;
-       struct ref *ref;
-       int err;
-       const char *argv[] = {"rev-list",
-               "--quiet", "--objects", "--stdin", "--not", "--all", NULL};
+       struct ref *rm = ref_map;
 
        /*
         * If we are deepening a shallow clone we already have these
@@ -484,47 +489,7 @@ static int quickfetch(struct ref *ref_map)
         */
        if (depth)
                return -1;
-
-       if (!ref_map)
-               return 0;
-
-       memset(&revlist, 0, sizeof(revlist));
-       revlist.argv = argv;
-       revlist.git_cmd = 1;
-       revlist.no_stdout = 1;
-       revlist.no_stderr = 1;
-       revlist.in = -1;
-
-       err = start_command(&revlist);
-       if (err) {
-               error(_("could not run rev-list"));
-               return err;
-       }
-
-       /*
-        * If rev-list --stdin encounters an unknown commit, it terminates,
-        * which will cause SIGPIPE in the write loop below.
-        */
-       sigchain_push(SIGPIPE, SIG_IGN);
-
-       for (ref = ref_map; ref; ref = ref->next) {
-               if (write_in_full(revlist.in, sha1_to_hex(ref->old_sha1), 40) < 0 ||
-                   write_str_in_full(revlist.in, "\n") < 0) {
-                       if (errno != EPIPE && errno != EINVAL)
-                               error(_("failed write to rev-list: %s"), strerror(errno));
-                       err = -1;
-                       break;
-               }
-       }
-
-       if (close(revlist.in)) {
-               error(_("failed to close rev-list's stdin: %s"), strerror(errno));
-               err = -1;
-       }
-
-       sigchain_pop(SIGPIPE);
-
-       return finish_command(&revlist) || err;
+       return check_everything_connected(iterate_ref_map, 1, &rm);
 }
 
 static int fetch_refs(struct transport *transport, struct ref *ref_map)
@@ -941,6 +906,15 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
        argc = parse_options(argc, argv, prefix,
                             builtin_fetch_options, builtin_fetch_usage, 0);
 
+       if (recurse_submodules != RECURSE_SUBMODULES_OFF) {
+               if (recurse_submodules_default) {
+                       int arg = parse_fetch_recurse_submodules_arg("--recurse-submodules-default", recurse_submodules_default);
+                       set_config_fetch_recurse_submodules(arg);
+               }
+               gitmodules_config();
+               git_config(submodule_config, NULL);
+       }
+
        if (all) {
                if (argc == 1)
                        die(_("fetch --all does not take a repository argument"));
@@ -976,12 +950,6 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
        if (!result && (recurse_submodules != RECURSE_SUBMODULES_OFF)) {
                const char *options[10];
                int num_options = 0;
-               if (recurse_submodules_default) {
-                       int arg = parse_fetch_recurse_submodules_arg("--recurse-submodules-default", recurse_submodules_default);
-                       set_config_fetch_recurse_submodules(arg);
-               }
-               gitmodules_config();
-               git_config(submodule_config, NULL);
                add_options_to_argv(&num_options, options);
                result = fetch_populated_submodules(num_options, options,
                                                    submodule_prefix,
index 89e75c6894e6fdb2abbe464d825f5c7186a14141..d90e5d2b29f9ac72a104d6308c04497991a03c17 100644 (file)
@@ -69,6 +69,9 @@ static struct {
        { "subject" },
        { "body" },
        { "contents" },
+       { "contents:subject" },
+       { "contents:body" },
+       { "contents:signature" },
        { "upstream" },
        { "symref" },
        { "flag" },
@@ -361,6 +364,18 @@ static const char *copy_email(const char *buf)
        return xmemdupz(email, eoemail + 1 - email);
 }
 
+static char *copy_subject(const char *buf, unsigned long len)
+{
+       char *r = xmemdupz(buf, len);
+       int i;
+
+       for (i = 0; i < len; i++)
+               if (r[i] == '\n')
+                       r[i] = ' ';
+
+       return r;
+}
+
 static void grab_date(const char *buf, struct atom_value *v, const char *atomname)
 {
        const char *eoemail = strstr(buf, "> ");
@@ -458,38 +473,56 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru
        }
 }
 
-static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body)
+static void find_subpos(const char *buf, unsigned long sz,
+                       const char **sub, unsigned long *sublen,
+                       const char **body, unsigned long *bodylen,
+                       unsigned long *nonsiglen,
+                       const char **sig, unsigned long *siglen)
 {
-       while (*buf) {
-               const char *eol = strchr(buf, '\n');
-               if (!eol)
-                       return;
-               if (eol[1] == '\n') {
-                       buf = eol + 1;
-                       break; /* found end of header */
-               }
-               buf = eol + 1;
+       const char *eol;
+       /* skip past header until we hit empty line */
+       while (*buf && *buf != '\n') {
+               eol = strchrnul(buf, '\n');
+               if (*eol)
+                       eol++;
+               buf = eol;
        }
+       /* skip any empty lines */
        while (*buf == '\n')
                buf++;
-       if (!*buf)
-               return;
-       *sub = buf; /* first non-empty line */
-       buf = strchr(buf, '\n');
-       if (!buf) {
-               *body = "";
-               return; /* no body */
+
+       /* parse signature first; we might not even have a subject line */
+       *sig = buf + parse_signature(buf, strlen(buf));
+       *siglen = strlen(*sig);
+
+       /* subject is first non-empty line */
+       *sub = buf;
+       /* subject goes to first empty line */
+       while (buf < *sig && *buf && *buf != '\n') {
+               eol = strchrnul(buf, '\n');
+               if (*eol)
+                       eol++;
+               buf = eol;
        }
+       *sublen = buf - *sub;
+       /* drop trailing newline, if present */
+       if (*sublen && (*sub)[*sublen - 1] == '\n')
+               *sublen -= 1;
+
+       /* skip any empty lines */
        while (*buf == '\n')
-               buf++; /* skip blank between subject and body */
+               buf++;
        *body = buf;
+       *bodylen = strlen(buf);
+       *nonsiglen = *sig - buf;
 }
 
 /* See grab_values */
 static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
 {
        int i;
-       const char *subpos = NULL, *bodypos = NULL;
+       const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;
+       unsigned long sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0;
 
        for (i = 0; i < used_atom_cnt; i++) {
                const char *name = used_atom[i];
@@ -500,17 +533,27 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj
                        name++;
                if (strcmp(name, "subject") &&
                    strcmp(name, "body") &&
-                   strcmp(name, "contents"))
+                   strcmp(name, "contents") &&
+                   strcmp(name, "contents:subject") &&
+                   strcmp(name, "contents:body") &&
+                   strcmp(name, "contents:signature"))
                        continue;
                if (!subpos)
-                       find_subpos(buf, sz, &subpos, &bodypos);
-               if (!subpos)
-                       return;
+                       find_subpos(buf, sz,
+                                   &subpos, &sublen,
+                                   &bodypos, &bodylen, &nonsiglen,
+                                   &sigpos, &siglen);
 
                if (!strcmp(name, "subject"))
-                       v->s = copy_line(subpos);
+                       v->s = copy_subject(subpos, sublen);
+               else if (!strcmp(name, "contents:subject"))
+                       v->s = copy_subject(subpos, sublen);
                else if (!strcmp(name, "body"))
-                       v->s = xstrdup(bodypos);
+                       v->s = xmemdupz(bodypos, bodylen);
+               else if (!strcmp(name, "contents:body"))
+                       v->s = xmemdupz(bodypos, nonsiglen);
+               else if (!strcmp(name, "contents:signature"))
+                       v->s = xmemdupz(sigpos, siglen);
                else if (!strcmp(name, "contents"))
                        v->s = xstrdup(subpos);
        }
index 5ae0366bc8cfe52f226c2af151a66d39b9df02aa..df1a88b51ae7773a15276d144113c0002fddb1cd 100644 (file)
@@ -231,12 +231,9 @@ static void check_unreachable_object(struct object *obj)
                                unsigned long size;
                                char *buf = read_sha1_file(obj->sha1,
                                                &type, &size);
-                               if (buf) {
-                                       if (fwrite(buf, size, 1, f) != 1)
-                                               die_errno("Could not write '%s'",
-                                                         filename);
-                                       free(buf);
-                               }
+                               if (buf && fwrite(buf, 1, size, f) != size)
+                                       die_errno("Could not write '%s'", filename);
+                               free(buf);
                        } else
                                fprintf(f, "%s\n", sha1_to_hex(obj->sha1));
                        if (fclose(f))
index 1851797540c17791f03e0d2fb86f5463e4e3816e..a286692e467710d92346ab6900e98f1126cb967d 100644 (file)
@@ -325,7 +325,7 @@ static int grep_config(const char *var, const char *value, void *cb)
        }
 
        if (!strcmp(var, "color.grep"))
-               opt->color = git_config_colorbool(var, value, -1);
+               opt->color = git_config_colorbool(var, value);
        else if (!strcmp(var, "color.grep.context"))
                color = opt->color_context;
        else if (!strcmp(var, "color.grep.filename"))
@@ -598,8 +598,11 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
                struct strbuf base;
                int hit, len;
 
+               read_sha1_lock();
                data = read_object_with_reference(obj->sha1, tree_type,
                                                  &size, NULL);
+               read_sha1_unlock();
+
                if (!data)
                        die(_("unable to read tree (%s)"), sha1_to_hex(obj->sha1));
 
@@ -898,8 +901,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
        strcpy(opt.color_sep, GIT_COLOR_CYAN);
        opt.color = -1;
        git_config(grep_config, &opt);
-       if (opt.color == -1)
-               opt.color = git_use_color_default;
 
        /*
         * If there is no -- then the paths must exist in the working
index 025aa47c804e4400cfa16ae8acd8dc2a5175e7c0..d07554c8844a9b7dd3d4ae2b5efe2cbde623e4af 100644 (file)
@@ -347,7 +347,7 @@ static void separate_git_dir(const char *git_dir)
                const char *src;
 
                if (S_ISREG(st.st_mode))
-                       src = read_gitfile_gently(git_link);
+                       src = read_gitfile(git_link);
                else if (S_ISDIR(st.st_mode))
                        src = git_link;
                else
index 5c2af590047d92554d87acc5d7113a2f1f730a96..f5d49305903911eb7aa0fb3f73e0fd950b896228 100644 (file)
@@ -359,9 +359,6 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix)
 
        git_config(git_log_config, NULL);
 
-       if (diff_use_color_default == -1)
-               diff_use_color_default = git_use_color_default;
-
        init_revisions(&rev, prefix);
        rev.diff = 1;
        rev.simplify_history = 0;
@@ -446,9 +443,6 @@ int cmd_show(int argc, const char **argv, const char *prefix)
 
        git_config(git_log_config, NULL);
 
-       if (diff_use_color_default == -1)
-               diff_use_color_default = git_use_color_default;
-
        init_pathspec(&match_all, NULL);
        init_revisions(&rev, prefix);
        rev.diff = 1;
@@ -524,9 +518,6 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix)
 
        git_config(git_log_config, NULL);
 
-       if (diff_use_color_default == -1)
-               diff_use_color_default = git_use_color_default;
-
        init_revisions(&rev, prefix);
        init_reflog_walk(&rev.reflog_info);
        rev.verbose_header = 1;
@@ -549,9 +540,6 @@ int cmd_log(int argc, const char **argv, const char *prefix)
 
        git_config(git_log_config, NULL);
 
-       if (diff_use_color_default == -1)
-               diff_use_color_default = git_use_color_default;
-
        init_revisions(&rev, prefix);
        rev.always_show_header = 1;
        memset(&opt, 0, sizeof(opt));
@@ -620,7 +608,8 @@ static int git_format_config(const char *var, const char *value, void *cb)
                string_list_append(&extra_cc, value);
                return 0;
        }
-       if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
+       if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff") ||
+           !strcmp(var, "color.ui")) {
                return 0;
        }
        if (!strcmp(var, "format.numbered")) {
index 0e98bff0c46035162fc424418cc434b52299c1cc..e8a800d3ac42bfce328bea786d8e1efa4ff695c7 100644 (file)
@@ -353,11 +353,13 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix)
        }
 }
 
-int report_path_error(const char *ps_matched, const char **pathspec, int prefix_len)
+int report_path_error(const char *ps_matched, const char **pathspec, const char *prefix)
 {
        /*
         * Make sure all pathspec matched; otherwise it is an error.
         */
+       struct strbuf sb = STRBUF_INIT;
+       const char *name;
        int num, errors = 0;
        for (num = 0; pathspec[num]; num++) {
                int other, found_dup;
@@ -382,10 +384,12 @@ int report_path_error(const char *ps_matched, const char **pathspec, int prefix_
                if (found_dup)
                        continue;
 
+               name = quote_path_relative(pathspec[num], -1, &sb, prefix);
                error("pathspec '%s' did not match any file(s) known to git.",
-                     pathspec[num] + prefix_len);
+                     name);
                errors++;
        }
+       strbuf_release(&sb);
        return errors;
 }
 
@@ -577,7 +581,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
 
        if (ps_matched) {
                int bad;
-               bad = report_path_error(ps_matched, pathspec, prefix_len);
+               bad = report_path_error(ps_matched, pathspec, prefix);
                if (bad)
                        fprintf(stderr, "Did you forget to 'git add'?\n");
 
index 325891edb610945d01899b102993202af279bf3f..ee56974371fa9224ecb4b8203fb1356b6cf6e156 100644 (file)
@@ -390,8 +390,6 @@ static void finish(const unsigned char *new_head, const char *msg)
                opts.output_format |=
                        DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
                opts.detect_rename = DIFF_DETECT_RENAME;
-               if (diff_use_color_default > 0)
-                       DIFF_OPT_SET(&opts, COLOR_DIFF);
                if (diff_setup_done(&opts) < 0)
                        die(_("diff_setup_done failed"));
                diff_tree_sha1(head, new_head, "", &opts);
@@ -405,6 +403,16 @@ static void finish(const unsigned char *new_head, const char *msg)
        strbuf_release(&reflog_message);
 }
 
+static struct object *want_commit(const char *name)
+{
+       struct object *obj;
+       unsigned char sha1[20];
+       if (get_sha1(name, sha1))
+               return NULL;
+       obj = parse_object(sha1);
+       return peel_to_type(name, 0, obj, OBJ_COMMIT);
+}
+
 /* Get the name for the merge commit's message. */
 static void merge_name(const char *remote, struct strbuf *msg)
 {
@@ -420,7 +428,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
        remote = bname.buf;
 
        memset(branch_head, 0, sizeof(branch_head));
-       remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
+       remote_head = want_commit(remote);
        if (!remote_head)
                die(_("'%s' does not point to a commit"), remote);
 
@@ -903,7 +911,7 @@ static int finish_automerge(struct commit_list *common,
        strbuf_addch(&merge_msg, '\n');
        run_prepare_commit_msg();
        commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
-       strbuf_addf(&buf, "Merge made by %s.", wt_strategy);
+       strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
        finish(result_commit, buf.buf);
        strbuf_release(&buf);
        drop_save();
@@ -1033,10 +1041,6 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 
        git_config(git_merge_config, NULL);
 
-       /* for color.ui */
-       if (diff_use_color_default == -1)
-               diff_use_color_default = git_use_color_default;
-
        if (branch_mergeoptions)
                parse_branch_merge_options(branch_mergeoptions);
        argc = parse_options(argc, argv, prefix, builtin_merge_options,
@@ -1130,7 +1134,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                if (!allow_fast_forward)
                        die(_("Non-fast-forward commit does not make sense into "
                            "an empty head"));
-               remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
+               remote_head = want_commit(argv[0]);
                if (!remote_head)
                        die(_("%s - not something we can merge"), argv[0]);
                read_empty(remote_head->sha1, 0);
@@ -1176,7 +1180,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                struct object *o;
                struct commit *commit;
 
-               o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
+               o = want_commit(argv[i]);
                if (!o)
                        die(_("%s - not something we can merge"), argv[i]);
                commit = lookup_commit(o->sha1);
@@ -1244,8 +1248,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                if (have_message)
                        strbuf_addstr(&msg,
                                " (no commit created; -m option ignored)");
-               o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
-                       0, NULL, OBJ_COMMIT);
+               o = want_commit(sha1_to_hex(remoteheads->item->object.sha1));
                if (!o)
                        return 1;
 
index a9c67c18ba159c8f04fa6bfff52ed9718965190a..2b18de5dc37bf849dbdbc892e4e9f3a34893dd9d 100644 (file)
@@ -2073,7 +2073,9 @@ static void show_commit(struct commit *commit, void *data)
        commit->object.flags |= OBJECT_ADDED;
 }
 
-static void show_object(struct object *obj, const struct name_path *path, const char *last)
+static void show_object(struct object *obj,
+                       const struct name_path *path, const char *last,
+                       void *data)
 {
        char *name = path_name(path, last);
 
index f821eb3f0b864c26d5e822cc7c5b0747340abee5..3cfe02d5a5716de1a3aaa5edc1ca0e3d74c03b03 100644 (file)
@@ -56,13 +56,13 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after)
        return 1;
 }
 
-static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx)
+static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx, struct strbuf *line_buf)
 {
-       static char line[1000];
        int patchlen = 0, found_next = 0;
        int before = -1, after = -1;
 
-       while (fgets(line, sizeof(line), stdin) != NULL) {
+       while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) {
+               char *line = line_buf->buf;
                char *p = line;
                int len;
 
@@ -133,14 +133,16 @@ static void generate_id_list(void)
        unsigned char sha1[20], n[20];
        git_SHA_CTX ctx;
        int patchlen;
+       struct strbuf line_buf = STRBUF_INIT;
 
        git_SHA1_Init(&ctx);
        hashclr(sha1);
        while (!feof(stdin)) {
-               patchlen = get_one_patchid(n, &ctx);
+               patchlen = get_one_patchid(n, &ctx, &line_buf);
                flush_current_id(patchlen, sha1, &ctx);
                hashcpy(sha1, n);
        }
+       strbuf_release(&line_buf);
 }
 
 static const char patch_id_usage[] = "git patch-id < patch";
index 9cebf9ea234d968eab638f7dcdc65370f77c0047..35cce532f2bb632e01c0de0a8e6f9e1395eece88 100644 (file)
@@ -8,6 +8,7 @@
 #include "remote.h"
 #include "transport.h"
 #include "parse-options.h"
+#include "submodule.h"
 
 static const char * const push_usage[] = {
        "git push [<options>] [<repository> [<refspec>...]]",
@@ -219,6 +220,21 @@ static int do_push(const char *repo, int flags)
        return !!errs;
 }
 
+static int option_parse_recurse_submodules(const struct option *opt,
+                                  const char *arg, int unset)
+{
+       int *flags = opt->value;
+       if (arg) {
+               if (!strcmp(arg, "check"))
+                       *flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK;
+               else
+                       die("bad %s argument: %s", opt->long_name, arg);
+       } else
+               die("option %s needs an argument (check)", opt->long_name);
+
+       return 0;
+}
+
 int cmd_push(int argc, const char **argv, const char *prefix)
 {
        int flags = 0;
@@ -236,6 +252,9 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN),
                OPT_BIT( 0,  "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN),
                OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE),
+               { OPTION_CALLBACK, 0, "recurse-submodules", &flags, "check",
+                       "controls recursive pushing of submodules",
+                       PARSE_OPT_OPTARG, option_parse_recurse_submodules },
                OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"),
                OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive pack program"),
                OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"),
index 60260d0aa93a878968e5ed72a2a77b96c5c0f040..c1c5bac79ef21c1d1e80605f3ced34f0a5f73eac 100644 (file)
@@ -11,6 +11,7 @@
 #include "transport.h"
 #include "string-list.h"
 #include "sha1-array.h"
+#include "connected.h"
 
 static const char receive_pack_usage[] = "git receive-pack <git-dir>";
 
@@ -25,7 +26,8 @@ static int deny_deletes;
 static int deny_non_fast_forwards;
 static enum deny_action deny_current_branch = DENY_UNCONFIGURED;
 static enum deny_action deny_delete_current = DENY_UNCONFIGURED;
-static int receive_fsck_objects;
+static int receive_fsck_objects = -1;
+static int transfer_fsck_objects = -1;
 static int receive_unpack_limit = -1;
 static int transfer_unpack_limit = -1;
 static int unpack_limit = 100;
@@ -79,6 +81,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
+       if (strcmp(var, "transfer.fsckobjects") == 0) {
+               transfer_fsck_objects = git_config_bool(var, value);
+               return 0;
+       }
+
        if (!strcmp(var, "receive.denycurrentbranch")) {
                deny_current_branch = parse_deny_action(var, value);
                return 0;
@@ -205,21 +212,15 @@ static int copy_to_sideband(int in, int out, void *arg)
        return 0;
 }
 
-static int run_receive_hook(struct command *commands, const char *hook_name)
+typedef int (*feed_fn)(void *, const char **, size_t *);
+static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_state)
 {
-       static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4];
-       struct command *cmd;
        struct child_process proc;
        struct async muxer;
        const char *argv[2];
-       int have_input = 0, code;
-
-       for (cmd = commands; !have_input && cmd; cmd = cmd->next) {
-               if (!cmd->error_string)
-                       have_input = 1;
-       }
+       int code;
 
-       if (!have_input || access(hook_name, X_OK) < 0)
+       if (access(hook_name, X_OK) < 0)
                return 0;
 
        argv[0] = hook_name;
@@ -247,15 +248,13 @@ static int run_receive_hook(struct command *commands, const char *hook_name)
                return code;
        }
 
-       for (cmd = commands; cmd; cmd = cmd->next) {
-               if (!cmd->error_string) {
-                       size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n",
-                               sha1_to_hex(cmd->old_sha1),
-                               sha1_to_hex(cmd->new_sha1),
-                               cmd->ref_name);
-                       if (write_in_full(proc.in, buf, n) != n)
-                               break;
-               }
+       while (1) {
+               const char *buf;
+               size_t n;
+               if (feed(feed_state, &buf, &n))
+                       break;
+               if (write_in_full(proc.in, buf, n) != n)
+                       break;
        }
        close(proc.in);
        if (use_sideband)
@@ -263,6 +262,47 @@ static int run_receive_hook(struct command *commands, const char *hook_name)
        return finish_command(&proc);
 }
 
+struct receive_hook_feed_state {
+       struct command *cmd;
+       struct strbuf buf;
+};
+
+static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
+{
+       struct receive_hook_feed_state *state = state_;
+       struct command *cmd = state->cmd;
+
+       while (cmd && cmd->error_string)
+               cmd = cmd->next;
+       if (!cmd)
+               return -1; /* EOF */
+       strbuf_reset(&state->buf);
+       strbuf_addf(&state->buf, "%s %s %s\n",
+                   sha1_to_hex(cmd->old_sha1), sha1_to_hex(cmd->new_sha1),
+                   cmd->ref_name);
+       state->cmd = cmd->next;
+       if (bufp) {
+               *bufp = state->buf.buf;
+               *sizep = state->buf.len;
+       }
+       return 0;
+}
+
+static int run_receive_hook(struct command *commands, const char *hook_name)
+{
+       struct receive_hook_feed_state state;
+       int status;
+
+       strbuf_init(&state.buf, 0);
+       state.cmd = commands;
+       if (feed_receive_hook(&state, NULL, NULL))
+               return 0;
+       state.cmd = commands;
+       status = run_and_feed_hook(hook_name, feed_receive_hook, &state);
+       strbuf_release(&state.buf);
+       return status;
+}
+
 static int run_update_hook(struct command *cmd)
 {
        static const char update_hook[] = "hooks/update";
@@ -579,6 +619,43 @@ static void check_aliased_updates(struct command *commands)
        string_list_clear(&ref_list, 0);
 }
 
+static int command_singleton_iterator(void *cb_data, unsigned char sha1[20])
+{
+       struct command **cmd_list = cb_data;
+       struct command *cmd = *cmd_list;
+
+       if (!cmd)
+               return -1; /* end of list */
+       *cmd_list = NULL; /* this returns only one */
+       hashcpy(sha1, cmd->new_sha1);
+       return 0;
+}
+
+static void set_connectivity_errors(struct command *commands)
+{
+       struct command *cmd;
+
+       for (cmd = commands; cmd; cmd = cmd->next) {
+               struct command *singleton = cmd;
+               if (!check_everything_connected(command_singleton_iterator,
+                                               0, &singleton))
+                       continue;
+               cmd->error_string = "missing necessary objects";
+       }
+}
+
+static int iterate_receive_command_list(void *cb_data, unsigned char sha1[20])
+{
+       struct command **cmd_list = cb_data;
+       struct command *cmd = *cmd_list;
+
+       if (!cmd)
+               return -1; /* end of list */
+       *cmd_list = cmd->next;
+       hashcpy(sha1, cmd->new_sha1);
+       return 0;
+}
+
 static void execute_commands(struct command *commands, const char *unpacker_error)
 {
        struct command *cmd;
@@ -590,6 +667,11 @@ static void execute_commands(struct command *commands, const char *unpacker_erro
                return;
        }
 
+       cmd = commands;
+       if (check_everything_connected(iterate_receive_command_list,
+                                      0, &cmd))
+               set_connectivity_errors(commands);
+
        if (run_receive_hook(commands, pre_receive_hook)) {
                for (cmd = commands; cmd; cmd = cmd->next)
                        cmd->error_string = "pre-receive hook declined";
@@ -669,11 +751,16 @@ static const char *parse_pack_header(struct pack_header *hdr)
 
 static const char *pack_lockfile;
 
-static const char *unpack(int quiet)
+static const char *unpack(void)
 {
        struct pack_header hdr;
        const char *hdr_err;
        char hdr_arg[38];
+       int fsck_objects = (receive_fsck_objects >= 0
+                           ? receive_fsck_objects
+                           : transfer_fsck_objects >= 0
+                           ? transfer_fsck_objects
+                           : 0);
 
        hdr_err = parse_pack_header(&hdr);
        if (hdr_err)
@@ -684,11 +771,9 @@ static const char *unpack(int quiet)
 
        if (ntohl(hdr.hdr_entries) < unpack_limit) {
                int code, i = 0;
-               const char *unpacker[5];
+               const char *unpacker[4];
                unpacker[i++] = "unpack-objects";
-               if (quiet)
-                       unpacker[i++] = "-q";
-               if (receive_fsck_objects)
+               if (fsck_objects)
                        unpacker[i++] = "--strict";
                unpacker[i++] = hdr_arg;
                unpacker[i++] = NULL;
@@ -708,7 +793,7 @@ static const char *unpack(int quiet)
 
                keeper[i++] = "index-pack";
                keeper[i++] = "--stdin";
-               if (receive_fsck_objects)
+               if (fsck_objects)
                        keeper[i++] = "--strict";
                keeper[i++] = "--fix-thin";
                keeper[i++] = hdr_arg;
@@ -788,7 +873,6 @@ static void add_alternate_refs(void)
 
 int cmd_receive_pack(int argc, const char **argv, const char *prefix)
 {
-       int quiet = 0;
        int advertise_refs = 0;
        int stateless_rpc = 0;
        int i;
@@ -802,11 +886,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
                const char *arg = *argv++;
 
                if (*arg == '-') {
-                       if (!strcmp(arg, "--quiet")) {
-                               quiet = 1;
-                               continue;
-                       }
-
                        if (!strcmp(arg, "--advertise-refs")) {
                                advertise_refs = 1;
                                continue;
@@ -855,7 +934,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
                const char *unpack_status = NULL;
 
                if (!delete_only(commands))
-                       unpack_status = unpack(quiet);
+                       unpack_status = unpack();
                execute_commands(commands, unpack_status);
                if (pack_lockfile)
                        unlink_or_warn(pack_lockfile);
index 05b1f5b76de36b19ef4bebba32aff1b40cbc2030..f2a9c26dc3494c0881ca3f91b44785c4b52caae7 100644 (file)
@@ -1103,7 +1103,7 @@ static int show(int argc, const char **argv)
                        url = states.remote->url;
                        url_nr = states.remote->url_nr;
                }
-               for (i=0; i < url_nr; i++)
+               for (i = 0; i < url_nr; i++)
                        printf("  Push  URL: %s\n", url[i]);
                if (!i)
                        printf("  Push  URL: %s\n", "(no URL)");
index 56727e8c1d9d87fe3e3bf3919ba86d27d0735758..ab3be7ca82ea36fbb1e98f8b899970a6748981c6 100644 (file)
@@ -168,29 +168,24 @@ static void finish_commit(struct commit *commit, void *data)
        commit->buffer = NULL;
 }
 
-static void finish_object(struct object *obj, const struct name_path *path, const char *name)
+static void finish_object(struct object *obj,
+                         const struct name_path *path, const char *name,
+                         void *cb_data)
 {
        if (obj->type == OBJ_BLOB && !has_sha1_file(obj->sha1))
                die("missing blob object '%s'", sha1_to_hex(obj->sha1));
 }
 
-static void show_object(struct object *obj, const struct name_path *path, const char *component)
+static void show_object(struct object *obj,
+                       const struct name_path *path, const char *component,
+                       void *cb_data)
 {
-       char *name = path_name(path, component);
-       /* An object with name "foo\n0000000..." can be used to
-        * confuse downstream "git pack-objects" very badly.
-        */
-       const char *ep = strchr(name, '\n');
+       struct rev_info *info = cb_data;
 
-       finish_object(obj, path, name);
-       if (ep) {
-               printf("%s %.*s\n", sha1_to_hex(obj->sha1),
-                      (int) (ep - name),
-                      name);
-       }
-       else
-               printf("%s %s\n", sha1_to_hex(obj->sha1), name);
-       free(name);
+       finish_object(obj, path, component, cb_data);
+       if (info->verify_objects && !obj->parsed && obj->type != OBJ_COMMIT)
+               parse_object(obj->sha1);
+       show_object_with_name(stdout, obj, path, component);
 }
 
 static void show_edge(struct commit *commit)
index 1f27c63343904a969e60ab19408495007f59d133..ba27cf15ee5478981a0a9a53493f41b67fcf9440 100644 (file)
@@ -13,6 +13,8 @@
 #include "rerere.h"
 #include "merge-recursive.h"
 #include "refs.h"
+#include "dir.h"
+#include "sequencer.h"
 
 /*
  * This implements the builtins revert and cherry-pick.
 
 static const char * const revert_usage[] = {
        "git revert [options] <commit-ish>",
+       "git revert <subcommand>",
        NULL
 };
 
 static const char * const cherry_pick_usage[] = {
        "git cherry-pick [options] <commit-ish>",
+       "git cherry-pick <subcommand>",
        NULL
 };
 
-static int edit, no_replay, no_commit, mainline, signoff, allow_ff;
-static enum { REVERT, CHERRY_PICK } action;
-static struct commit *commit;
-static int commit_argc;
-static const char **commit_argv;
-static int allow_rerere_auto;
-
-static const char *me;
-
-/* Merge strategy. */
-static const char *strategy;
-static const char **xopts;
-static size_t xopts_nr, xopts_alloc;
+enum replay_action { REVERT, CHERRY_PICK };
+enum replay_subcommand { REPLAY_NONE, REPLAY_RESET, REPLAY_CONTINUE };
+
+struct replay_opts {
+       enum replay_action action;
+       enum replay_subcommand subcommand;
+
+       /* Boolean options */
+       int edit;
+       int record_origin;
+       int no_commit;
+       int signoff;
+       int allow_ff;
+       int allow_rerere_auto;
+
+       int mainline;
+       int commit_argc;
+       const char **commit_argv;
+
+       /* Merge strategy */
+       const char *strategy;
+       const char **xopts;
+       size_t xopts_nr, xopts_alloc;
+};
 
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
 
+static const char *action_name(const struct replay_opts *opts)
+{
+       return opts->action == REVERT ? "revert" : "cherry-pick";
+}
+
 static char *get_encoding(const char *message);
 
-static const char * const *revert_or_cherry_pick_usage(void)
+static const char * const *revert_or_cherry_pick_usage(struct replay_opts *opts)
 {
-       return action == REVERT ? revert_usage : cherry_pick_usage;
+       return opts->action == REVERT ? revert_usage : cherry_pick_usage;
 }
 
 static int option_parse_x(const struct option *opt,
                          const char *arg, int unset)
 {
+       struct replay_opts **opts_ptr = opt->value;
+       struct replay_opts *opts = *opts_ptr;
+
        if (unset)
                return 0;
 
-       ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc);
-       xopts[xopts_nr++] = xstrdup(arg);
+       ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc);
+       opts->xopts[opts->xopts_nr++] = xstrdup(arg);
        return 0;
 }
 
-static void parse_args(int argc, const char **argv)
+static void verify_opt_compatible(const char *me, const char *base_opt, ...)
+{
+       const char *this_opt;
+       va_list ap;
+
+       va_start(ap, base_opt);
+       while ((this_opt = va_arg(ap, const char *))) {
+               if (va_arg(ap, int))
+                       break;
+       }
+       va_end(ap);
+
+       if (this_opt)
+               die(_("%s: %s cannot be used with %s"), me, this_opt, base_opt);
+}
+
+static void verify_opt_mutually_compatible(const char *me, ...)
 {
-       const char * const * usage_str = revert_or_cherry_pick_usage();
+       const char *opt1, *opt2;
+       va_list ap;
+
+       va_start(ap, me);
+       while ((opt1 = va_arg(ap, const char *))) {
+               if (va_arg(ap, int))
+                       break;
+       }
+       if (opt1) {
+               while ((opt2 = va_arg(ap, const char *))) {
+                       if (va_arg(ap, int))
+                               break;
+               }
+       }
+
+       if (opt1 && opt2)
+               die(_("%s: %s cannot be used with %s"), me, opt1, opt2);
+}
+
+static void parse_args(int argc, const char **argv, struct replay_opts *opts)
+{
+       const char * const * usage_str = revert_or_cherry_pick_usage(opts);
+       const char *me = action_name(opts);
        int noop;
+       int reset = 0;
+       int contin = 0;
        struct option options[] = {
-               OPT_BOOLEAN('n', "no-commit", &no_commit, "don't automatically commit"),
-               OPT_BOOLEAN('e', "edit", &edit, "edit the commit message"),
+               OPT_BOOLEAN(0, "reset", &reset, "forget the current operation"),
+               OPT_BOOLEAN(0, "continue", &contin, "continue the current operation"),
+               OPT_BOOLEAN('n', "no-commit", &opts->no_commit, "don't automatically commit"),
+               OPT_BOOLEAN('e', "edit", &opts->edit, "edit the commit message"),
                { OPTION_BOOLEAN, 'r', NULL, &noop, NULL, "no-op (backward compatibility)",
                  PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 0 },
-               OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
-               OPT_INTEGER('m', "mainline", &mainline, "parent number"),
-               OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
-               OPT_STRING(0, "strategy", &strategy, "strategy", "merge strategy"),
-               OPT_CALLBACK('X', "strategy-option", &xopts, "option",
+               OPT_BOOLEAN('s', "signoff", &opts->signoff, "add Signed-off-by:"),
+               OPT_INTEGER('m', "mainline", &opts->mainline, "parent number"),
+               OPT_RERERE_AUTOUPDATE(&opts->allow_rerere_auto),
+               OPT_STRING(0, "strategy", &opts->strategy, "strategy", "merge strategy"),
+               OPT_CALLBACK('X', "strategy-option", &opts, "option",
                        "option for merge strategy", option_parse_x),
                OPT_END(),
                OPT_END(),
                OPT_END(),
        };
 
-       if (action == CHERRY_PICK) {
+       if (opts->action == CHERRY_PICK) {
                struct option cp_extra[] = {
-                       OPT_BOOLEAN('x', NULL, &no_replay, "append commit name"),
-                       OPT_BOOLEAN(0, "ff", &allow_ff, "allow fast-forward"),
+                       OPT_BOOLEAN('x', NULL, &opts->record_origin, "append commit name"),
+                       OPT_BOOLEAN(0, "ff", &opts->allow_ff, "allow fast-forward"),
                        OPT_END(),
                };
                if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra))
                        die(_("program error"));
        }
 
-       commit_argc = parse_options(argc, argv, NULL, options, usage_str,
-                                   PARSE_OPT_KEEP_ARGV0 |
-                                   PARSE_OPT_KEEP_UNKNOWN);
-       if (commit_argc < 2)
+       opts->commit_argc = parse_options(argc, argv, NULL, options, usage_str,
+                                       PARSE_OPT_KEEP_ARGV0 |
+                                       PARSE_OPT_KEEP_UNKNOWN);
+
+       /* Check for incompatible subcommands */
+       verify_opt_mutually_compatible(me,
+                               "--reset", reset,
+                               "--continue", contin,
+                               NULL);
+
+       /* Set the subcommand */
+       if (reset)
+               opts->subcommand = REPLAY_RESET;
+       else if (contin)
+               opts->subcommand = REPLAY_CONTINUE;
+       else
+               opts->subcommand = REPLAY_NONE;
+
+       /* Check for incompatible command line arguments */
+       if (opts->subcommand != REPLAY_NONE) {
+               char *this_operation;
+               if (opts->subcommand == REPLAY_RESET)
+                       this_operation = "--reset";
+               else
+                       this_operation = "--continue";
+
+               verify_opt_compatible(me, this_operation,
+                               "--no-commit", opts->no_commit,
+                               "--signoff", opts->signoff,
+                               "--mainline", opts->mainline,
+                               "--strategy", opts->strategy ? 1 : 0,
+                               "--strategy-option", opts->xopts ? 1 : 0,
+                               "-x", opts->record_origin,
+                               "--ff", opts->allow_ff,
+                               NULL);
+       }
+
+       else if (opts->commit_argc < 2)
                usage_with_options(usage_str, options);
 
-       commit_argv = argv;
+       if (opts->allow_ff)
+               verify_opt_compatible(me, "--ff",
+                               "--signoff", opts->signoff,
+                               "--no-commit", opts->no_commit,
+                               "-x", opts->record_origin,
+                               "--edit", opts->edit,
+                               NULL);
+       opts->commit_argv = argv;
 }
 
 struct commit_message {
@@ -116,25 +222,25 @@ struct commit_message {
        const char *message;
 };
 
-static int get_message(const char *raw_message, struct commit_message *out)
+static int get_message(struct commit *commit, struct commit_message *out)
 {
        const char *encoding;
        const char *abbrev, *subject;
        int abbrev_len, subject_len;
        char *q;
 
-       if (!raw_message)
+       if (!commit->buffer)
                return -1;
-       encoding = get_encoding(raw_message);
+       encoding = get_encoding(commit->buffer);
        if (!encoding)
                encoding = "UTF-8";
        if (!git_commit_encoding)
                git_commit_encoding = "UTF-8";
 
        out->reencoded_message = NULL;
-       out->message = raw_message;
+       out->message = commit->buffer;
        if (strcmp(encoding, git_commit_encoding))
-               out->reencoded_message = reencode_string(raw_message,
+               out->reencoded_message = reencode_string(commit->buffer,
                                        git_commit_encoding, encoding);
        if (out->reencoded_message)
                out->message = out->reencoded_message;
@@ -167,9 +273,6 @@ static char *get_encoding(const char *message)
 {
        const char *p = message, *eol;
 
-       if (!p)
-               die (_("Could not read commit message of %s"),
-                               sha1_to_hex(commit->object.sha1));
        while (*p && *p != '\n') {
                for (eol = p + 1; *eol && *eol != '\n'; eol++)
                        ; /* do nothing */
@@ -185,20 +288,7 @@ static char *get_encoding(const char *message)
        return NULL;
 }
 
-static void add_message_to_msg(struct strbuf *msgbuf, const char *message)
-{
-       const char *p = message;
-       while (*p && (*p != '\n' || p[1] != '\n'))
-               p++;
-
-       if (!*p)
-               strbuf_addstr(msgbuf, sha1_to_hex(commit->object.sha1));
-
-       p += 2;
-       strbuf_addstr(msgbuf, p);
-}
-
-static void write_cherry_pick_head(void)
+static void write_cherry_pick_head(struct commit *commit)
 {
        int fd;
        struct strbuf buf = STRBUF_INIT;
@@ -214,15 +304,6 @@ static void write_cherry_pick_head(void)
        strbuf_release(&buf);
 }
 
-static void advise(const char *advice, ...)
-{
-       va_list params;
-
-       va_start(params, advice);
-       vreportf("hint: ", advice, params);
-       va_end(params);
-}
-
 static void print_advice(void)
 {
        char *msg = getenv("GIT_CHERRY_PICK_HELP");
@@ -258,33 +339,23 @@ static void write_message(struct strbuf *msgbuf, const char *filename)
 
 static struct tree *empty_tree(void)
 {
-       struct tree *tree = xcalloc(1, sizeof(struct tree));
-
-       tree->object.parsed = 1;
-       tree->object.type = OBJ_TREE;
-       pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1);
-       return tree;
+       return lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN);
 }
 
-static NORETURN void die_dirty_index(const char *me)
+static int error_dirty_index(struct replay_opts *opts)
 {
-       if (read_cache_unmerged()) {
-               die_resolve_conflict(me);
-       } else {
-               if (advice_commit_before_merge) {
-                       if (action == REVERT)
-                               die(_("Your local changes would be overwritten by revert.\n"
-                                         "Please, commit your changes or stash them to proceed."));
-                       else
-                               die(_("Your local changes would be overwritten by cherry-pick.\n"
-                                         "Please, commit your changes or stash them to proceed."));
-               } else {
-                       if (action == REVERT)
-                               die(_("Your local changes would be overwritten by revert.\n"));
-                       else
-                               die(_("Your local changes would be overwritten by cherry-pick.\n"));
-               }
-       }
+       if (read_cache_unmerged())
+               return error_resolve_conflict(action_name(opts));
+
+       /* Different translation strings for cherry-pick and revert */
+       if (opts->action == CHERRY_PICK)
+               error(_("Your local changes would be overwritten by cherry-pick."));
+       else
+               error(_("Your local changes would be overwritten by revert."));
+
+       if (advice_commit_before_merge)
+               advise(_("Commit your changes or stash them to proceed."));
+       return -1;
 }
 
 static int fast_forward_to(const unsigned char *to, const unsigned char *from)
@@ -300,7 +371,8 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from)
 
 static int do_recursive_merge(struct commit *base, struct commit *next,
                              const char *base_label, const char *next_label,
-                             unsigned char *head, struct strbuf *msgbuf)
+                             unsigned char *head, struct strbuf *msgbuf,
+                             struct replay_opts *opts)
 {
        struct merge_options o;
        struct tree *result, *next_tree, *base_tree, *head_tree;
@@ -321,7 +393,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
        next_tree = next ? next->tree : empty_tree();
        base_tree = base ? base->tree : empty_tree();
 
-       for (xopt = xopts; xopt != xopts + xopts_nr; xopt++)
+       for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++)
                parse_merge_opt(&o, *xopt);
 
        clean = merge_trees(&o,
@@ -332,7 +404,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
            (write_cache(index_fd, active_cache, active_nr) ||
             commit_locked_index(&index_lock)))
                /* TRANSLATORS: %s will be "revert" or "cherry-pick" */
-               die(_("%s: Unable to write new index file"), me);
+               die(_("%s: Unable to write new index file"), action_name(opts));
        rollback_lock_file(&index_lock);
 
        if (!clean) {
@@ -361,7 +433,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
  * If we are revert, or if our cherry-pick results in a hand merge,
  * we had better say that the current user is responsible for that.
  */
-static int run_git_commit(const char *defmsg)
+static int run_git_commit(const char *defmsg, struct replay_opts *opts)
 {
        /* 6 is max possible length of our args array including NULL */
        const char *args[6];
@@ -369,9 +441,9 @@ static int run_git_commit(const char *defmsg)
 
        args[i++] = "commit";
        args[i++] = "-n";
-       if (signoff)
+       if (opts->signoff)
                args[i++] = "-s";
-       if (!edit) {
+       if (!opts->edit) {
                args[i++] = "-F";
                args[i++] = defmsg;
        }
@@ -380,7 +452,7 @@ static int run_git_commit(const char *defmsg)
        return run_command_v_opt(args, RUN_GIT_CMD);
 }
 
-static int do_pick_commit(void)
+static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 {
        unsigned char head[20];
        struct commit *base, *next, *parent;
@@ -390,7 +462,7 @@ static int do_pick_commit(void)
        struct strbuf msgbuf = STRBUF_INIT;
        int res;
 
-       if (no_commit) {
+       if (opts->no_commit) {
                /*
                 * We do not intend to commit immediately.  We just want to
                 * merge the differences in, so let's compute the tree
@@ -401,9 +473,9 @@ static int do_pick_commit(void)
                        die (_("Your index file is unmerged."));
        } else {
                if (get_sha1("HEAD", head))
-                       die (_("You do not have a valid HEAD"));
+                       return error(_("You do not have a valid HEAD"));
                if (index_differs_from("HEAD", 0))
-                       die_dirty_index(me);
+                       return error_dirty_index(opts);
        }
        discard_cache();
 
@@ -415,36 +487,36 @@ static int do_pick_commit(void)
                int cnt;
                struct commit_list *p;
 
-               if (!mainline)
-                       die(_("Commit %s is a merge but no -m option was given."),
-                           sha1_to_hex(commit->object.sha1));
+               if (!opts->mainline)
+                       return error(_("Commit %s is a merge but no -m option was given."),
+                               sha1_to_hex(commit->object.sha1));
 
                for (cnt = 1, p = commit->parents;
-                    cnt != mainline && p;
+                    cnt != opts->mainline && p;
                     cnt++)
                        p = p->next;
-               if (cnt != mainline || !p)
-                       die(_("Commit %s does not have parent %d"),
-                           sha1_to_hex(commit->object.sha1), mainline);
+               if (cnt != opts->mainline || !p)
+                       return error(_("Commit %s does not have parent %d"),
+                               sha1_to_hex(commit->object.sha1), opts->mainline);
                parent = p->item;
-       } else if (0 < mainline)
-               die(_("Mainline was specified but commit %s is not a merge."),
-                   sha1_to_hex(commit->object.sha1));
+       } else if (0 < opts->mainline)
+               return error(_("Mainline was specified but commit %s is not a merge."),
+                       sha1_to_hex(commit->object.sha1));
        else
                parent = commit->parents->item;
 
-       if (allow_ff && parent && !hashcmp(parent->object.sha1, head))
+       if (opts->allow_ff && parent && !hashcmp(parent->object.sha1, head))
                return fast_forward_to(commit->object.sha1, head);
 
        if (parent && parse_commit(parent) < 0)
                /* TRANSLATORS: The first %s will be "revert" or
                   "cherry-pick", the second %s a SHA1 */
-               die(_("%s: cannot parse parent commit %s"),
-                   me, sha1_to_hex(parent->object.sha1));
+               return error(_("%s: cannot parse parent commit %s"),
+                       action_name(opts), sha1_to_hex(parent->object.sha1));
 
-       if (get_message(commit->buffer, &msg) != 0)
-               die(_("Cannot get commit message for %s"),
-                               sha1_to_hex(commit->object.sha1));
+       if (get_message(commit, &msg) != 0)
+               return error(_("Cannot get commit message for %s"),
+                       sha1_to_hex(commit->object.sha1));
 
        /*
         * "commit" is an existing commit.  We would want to apply
@@ -455,7 +527,7 @@ static int do_pick_commit(void)
 
        defmsg = git_pathdup("MERGE_MSG");
 
-       if (action == REVERT) {
+       if (opts->action == REVERT) {
                base = commit;
                base_label = msg.label;
                next = parent;
@@ -471,23 +543,36 @@ static int do_pick_commit(void)
                }
                strbuf_addstr(&msgbuf, ".\n");
        } else {
+               const char *p;
+
                base = parent;
                base_label = msg.parent_label;
                next = commit;
                next_label = msg.label;
-               add_message_to_msg(&msgbuf, msg.message);
-               if (no_replay) {
+
+               /*
+                * Append the commit log message to msgbuf; it starts
+                * after the tree, parent, author, committer
+                * information followed by "\n\n".
+                */
+               p = strstr(msg.message, "\n\n");
+               if (p) {
+                       p += 2;
+                       strbuf_addstr(&msgbuf, p);
+               }
+
+               if (opts->record_origin) {
                        strbuf_addstr(&msgbuf, "(cherry picked from commit ");
                        strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
                        strbuf_addstr(&msgbuf, ")\n");
                }
-               if (!no_commit)
-                       write_cherry_pick_head();
+               if (!opts->no_commit)
+                       write_cherry_pick_head(commit);
        }
 
-       if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) {
+       if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REVERT) {
                res = do_recursive_merge(base, next, base_label, next_label,
-                                        head, &msgbuf);
+                                        head, &msgbuf, opts);
                write_message(&msgbuf, defmsg);
        } else {
                struct commit_list *common = NULL;
@@ -497,23 +582,23 @@ static int do_pick_commit(void)
 
                commit_list_insert(base, &common);
                commit_list_insert(next, &remotes);
-               res = try_merge_command(strategy, xopts_nr, xopts, common,
-                                       sha1_to_hex(head), remotes);
+               res = try_merge_command(opts->strategy, opts->xopts_nr, opts->xopts,
+                                       common, sha1_to_hex(head), remotes);
                free_commit_list(common);
                free_commit_list(remotes);
        }
 
        if (res) {
-               error(action == REVERT
+               error(opts->action == REVERT
                      ? _("could not revert %s... %s")
                      : _("could not apply %s... %s"),
                      find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV),
                      msg.subject);
                print_advice();
-               rerere(allow_rerere_auto);
+               rerere(opts->allow_rerere_auto);
        } else {
-               if (!no_commit)
-                       res = run_git_commit(defmsg);
+               if (!opts->no_commit)
+                       res = run_git_commit(defmsg, opts);
        }
 
        free_message(&msg);
@@ -522,18 +607,18 @@ static int do_pick_commit(void)
        return res;
 }
 
-static void prepare_revs(struct rev_info *revs)
+static void prepare_revs(struct rev_info *revs, struct replay_opts *opts)
 {
        int argc;
 
        init_revisions(revs, NULL);
        revs->no_walk = 1;
-       if (action != REVERT)
+       if (opts->action != REVERT)
                revs->reverse = 1;
 
-       argc = setup_revisions(commit_argc, commit_argv, revs, NULL);
+       argc = setup_revisions(opts->commit_argc, opts->commit_argv, revs, NULL);
        if (argc > 1)
-               usage(*revert_or_cherry_pick_usage());
+               usage(*revert_or_cherry_pick_usage(opts));
 
        if (prepare_revision_walk(revs))
                die(_("revision walk setup failed"));
@@ -542,64 +627,403 @@ static void prepare_revs(struct rev_info *revs)
                die(_("empty commit set passed"));
 }
 
-static void read_and_refresh_cache(const char *me)
+static void read_and_refresh_cache(struct replay_opts *opts)
 {
        static struct lock_file index_lock;
        int index_fd = hold_locked_index(&index_lock, 0);
        if (read_index_preload(&the_index, NULL) < 0)
-               die(_("git %s: failed to read the index"), me);
+               die(_("git %s: failed to read the index"), action_name(opts));
        refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
        if (the_index.cache_changed) {
                if (write_index(&the_index, index_fd) ||
                    commit_locked_index(&index_lock))
-                       die(_("git %s: failed to refresh the index"), me);
+                       die(_("git %s: failed to refresh the index"), action_name(opts));
        }
        rollback_lock_file(&index_lock);
 }
 
-static int revert_or_cherry_pick(int argc, const char **argv)
+/*
+ * Append a commit to the end of the commit_list.
+ *
+ * next starts by pointing to the variable that holds the head of an
+ * empty commit_list, and is updated to point to the "next" field of
+ * the last item on the list as new commits are appended.
+ *
+ * Usage example:
+ *
+ *     struct commit_list *list;
+ *     struct commit_list **next = &list;
+ *
+ *     next = commit_list_append(c1, next);
+ *     next = commit_list_append(c2, next);
+ *     assert(commit_list_count(list) == 2);
+ *     return list;
+ */
+static struct commit_list **commit_list_append(struct commit *commit,
+                                              struct commit_list **next)
+{
+       struct commit_list *new = xmalloc(sizeof(struct commit_list));
+       new->item = commit;
+       *next = new;
+       new->next = NULL;
+       return &new->next;
+}
+
+static int format_todo(struct strbuf *buf, struct commit_list *todo_list,
+               struct replay_opts *opts)
+{
+       struct commit_list *cur = NULL;
+       struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
+       const char *sha1_abbrev = NULL;
+       const char *action_str = opts->action == REVERT ? "revert" : "pick";
+
+       for (cur = todo_list; cur; cur = cur->next) {
+               sha1_abbrev = find_unique_abbrev(cur->item->object.sha1, DEFAULT_ABBREV);
+               if (get_message(cur->item, &msg))
+                       return error(_("Cannot get commit message for %s"), sha1_abbrev);
+               strbuf_addf(buf, "%s %s %s\n", action_str, sha1_abbrev, msg.subject);
+       }
+       return 0;
+}
+
+static struct commit *parse_insn_line(char *start, struct replay_opts *opts)
+{
+       unsigned char commit_sha1[20];
+       char sha1_abbrev[40];
+       enum replay_action action;
+       int insn_len = 0;
+       char *p, *q;
+
+       if (!prefixcmp(start, "pick ")) {
+               action = CHERRY_PICK;
+               insn_len = strlen("pick");
+               p = start + insn_len + 1;
+       } else if (!prefixcmp(start, "revert ")) {
+               action = REVERT;
+               insn_len = strlen("revert");
+               p = start + insn_len + 1;
+       } else
+               return NULL;
+
+       q = strchr(p, ' ');
+       if (!q)
+               return NULL;
+       q++;
+
+       strlcpy(sha1_abbrev, p, q - p);
+
+       /*
+        * Verify that the action matches up with the one in
+        * opts; we don't support arbitrary instructions
+        */
+       if (action != opts->action) {
+               const char *action_str;
+               action_str = action == REVERT ? "revert" : "cherry-pick";
+               error(_("Cannot %s during a %s"), action_str, action_name(opts));
+               return NULL;
+       }
+
+       if (get_sha1(sha1_abbrev, commit_sha1) < 0)
+               return NULL;
+
+       return lookup_commit_reference(commit_sha1);
+}
+
+static int parse_insn_buffer(char *buf, struct commit_list **todo_list,
+                       struct replay_opts *opts)
+{
+       struct commit_list **next = todo_list;
+       struct commit *commit;
+       char *p = buf;
+       int i;
+
+       for (i = 1; *p; i++) {
+               commit = parse_insn_line(p, opts);
+               if (!commit)
+                       return error(_("Could not parse line %d."), i);
+               next = commit_list_append(commit, next);
+               p = strchrnul(p, '\n');
+               if (*p)
+                       p++;
+       }
+       if (!*todo_list)
+               return error(_("No commits parsed."));
+       return 0;
+}
+
+static void read_populate_todo(struct commit_list **todo_list,
+                       struct replay_opts *opts)
+{
+       const char *todo_file = git_path(SEQ_TODO_FILE);
+       struct strbuf buf = STRBUF_INIT;
+       int fd, res;
+
+       fd = open(todo_file, O_RDONLY);
+       if (fd < 0)
+               die_errno(_("Could not open %s."), todo_file);
+       if (strbuf_read(&buf, fd, 0) < 0) {
+               close(fd);
+               strbuf_release(&buf);
+               die(_("Could not read %s."), todo_file);
+       }
+       close(fd);
+
+       res = parse_insn_buffer(buf.buf, todo_list, opts);
+       strbuf_release(&buf);
+       if (res)
+               die(_("Unusable instruction sheet: %s"), todo_file);
+}
+
+static int populate_opts_cb(const char *key, const char *value, void *data)
+{
+       struct replay_opts *opts = data;
+       int error_flag = 1;
+
+       if (!value)
+               error_flag = 0;
+       else if (!strcmp(key, "options.no-commit"))
+               opts->no_commit = git_config_bool_or_int(key, value, &error_flag);
+       else if (!strcmp(key, "options.edit"))
+               opts->edit = git_config_bool_or_int(key, value, &error_flag);
+       else if (!strcmp(key, "options.signoff"))
+               opts->signoff = git_config_bool_or_int(key, value, &error_flag);
+       else if (!strcmp(key, "options.record-origin"))
+               opts->record_origin = git_config_bool_or_int(key, value, &error_flag);
+       else if (!strcmp(key, "options.allow-ff"))
+               opts->allow_ff = git_config_bool_or_int(key, value, &error_flag);
+       else if (!strcmp(key, "options.mainline"))
+               opts->mainline = git_config_int(key, value);
+       else if (!strcmp(key, "options.strategy"))
+               git_config_string(&opts->strategy, key, value);
+       else if (!strcmp(key, "options.strategy-option")) {
+               ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc);
+               opts->xopts[opts->xopts_nr++] = xstrdup(value);
+       } else
+               return error(_("Invalid key: %s"), key);
+
+       if (!error_flag)
+               return error(_("Invalid value for %s: %s"), key, value);
+
+       return 0;
+}
+
+static void read_populate_opts(struct replay_opts **opts_ptr)
+{
+       const char *opts_file = git_path(SEQ_OPTS_FILE);
+
+       if (!file_exists(opts_file))
+               return;
+       if (git_config_from_file(populate_opts_cb, opts_file, *opts_ptr) < 0)
+               die(_("Malformed options sheet: %s"), opts_file);
+}
+
+static void walk_revs_populate_todo(struct commit_list **todo_list,
+                               struct replay_opts *opts)
 {
        struct rev_info revs;
+       struct commit *commit;
+       struct commit_list **next;
 
-       git_config(git_default_config, NULL);
-       me = action == REVERT ? "revert" : "cherry-pick";
-       setenv(GIT_REFLOG_ACTION, me, 0);
-       parse_args(argc, argv);
-
-       if (allow_ff) {
-               if (signoff)
-                       die(_("cherry-pick --ff cannot be used with --signoff"));
-               if (no_commit)
-                       die(_("cherry-pick --ff cannot be used with --no-commit"));
-               if (no_replay)
-                       die(_("cherry-pick --ff cannot be used with -x"));
-               if (edit)
-                       die(_("cherry-pick --ff cannot be used with --edit"));
+       prepare_revs(&revs, opts);
+
+       next = todo_list;
+       while ((commit = get_revision(&revs)))
+               next = commit_list_append(commit, next);
+}
+
+static int create_seq_dir(void)
+{
+       const char *seq_dir = git_path(SEQ_DIR);
+
+       if (file_exists(seq_dir))
+               return error(_("%s already exists."), seq_dir);
+       else if (mkdir(seq_dir, 0777) < 0)
+               die_errno(_("Could not create sequencer directory '%s'."), seq_dir);
+       return 0;
+}
+
+static void save_head(const char *head)
+{
+       const char *head_file = git_path(SEQ_HEAD_FILE);
+       static struct lock_file head_lock;
+       struct strbuf buf = STRBUF_INIT;
+       int fd;
+
+       fd = hold_lock_file_for_update(&head_lock, head_file, LOCK_DIE_ON_ERROR);
+       strbuf_addf(&buf, "%s\n", head);
+       if (write_in_full(fd, buf.buf, buf.len) < 0)
+               die_errno(_("Could not write to %s."), head_file);
+       if (commit_lock_file(&head_lock) < 0)
+               die(_("Error wrapping up %s."), head_file);
+}
+
+static void save_todo(struct commit_list *todo_list, struct replay_opts *opts)
+{
+       const char *todo_file = git_path(SEQ_TODO_FILE);
+       static struct lock_file todo_lock;
+       struct strbuf buf = STRBUF_INIT;
+       int fd;
+
+       fd = hold_lock_file_for_update(&todo_lock, todo_file, LOCK_DIE_ON_ERROR);
+       if (format_todo(&buf, todo_list, opts) < 0)
+               die(_("Could not format %s."), todo_file);
+       if (write_in_full(fd, buf.buf, buf.len) < 0) {
+               strbuf_release(&buf);
+               die_errno(_("Could not write to %s."), todo_file);
+       }
+       if (commit_lock_file(&todo_lock) < 0) {
+               strbuf_release(&buf);
+               die(_("Error wrapping up %s."), todo_file);
        }
+       strbuf_release(&buf);
+}
 
-       read_and_refresh_cache(me);
+static void save_opts(struct replay_opts *opts)
+{
+       const char *opts_file = git_path(SEQ_OPTS_FILE);
+
+       if (opts->no_commit)
+               git_config_set_in_file(opts_file, "options.no-commit", "true");
+       if (opts->edit)
+               git_config_set_in_file(opts_file, "options.edit", "true");
+       if (opts->signoff)
+               git_config_set_in_file(opts_file, "options.signoff", "true");
+       if (opts->record_origin)
+               git_config_set_in_file(opts_file, "options.record-origin", "true");
+       if (opts->allow_ff)
+               git_config_set_in_file(opts_file, "options.allow-ff", "true");
+       if (opts->mainline) {
+               struct strbuf buf = STRBUF_INIT;
+               strbuf_addf(&buf, "%d", opts->mainline);
+               git_config_set_in_file(opts_file, "options.mainline", buf.buf);
+               strbuf_release(&buf);
+       }
+       if (opts->strategy)
+               git_config_set_in_file(opts_file, "options.strategy", opts->strategy);
+       if (opts->xopts) {
+               int i;
+               for (i = 0; i < opts->xopts_nr; i++)
+                       git_config_set_multivar_in_file(opts_file,
+                                                       "options.strategy-option",
+                                                       opts->xopts[i], "^$", 0);
+       }
+}
 
-       prepare_revs(&revs);
+static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts)
+{
+       struct commit_list *cur;
+       int res;
 
-       while ((commit = get_revision(&revs))) {
-               int res = do_pick_commit();
-               if (res)
+       setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
+       if (opts->allow_ff)
+               assert(!(opts->signoff || opts->no_commit ||
+                               opts->record_origin || opts->edit));
+       read_and_refresh_cache(opts);
+
+       for (cur = todo_list; cur; cur = cur->next) {
+               save_todo(cur, opts);
+               res = do_pick_commit(cur->item, opts);
+               if (res) {
+                       if (!cur->next)
+                               /*
+                                * An error was encountered while
+                                * picking the last commit; the
+                                * sequencer state is useless now --
+                                * the user simply needs to resolve
+                                * the conflict and commit
+                                */
+                               remove_sequencer_state(0);
                        return res;
+               }
        }
 
+       /*
+        * Sequence of picks finished successfully; cleanup by
+        * removing the .git/sequencer directory
+        */
+       remove_sequencer_state(1);
        return 0;
 }
 
+static int pick_revisions(struct replay_opts *opts)
+{
+       struct commit_list *todo_list = NULL;
+       unsigned char sha1[20];
+
+       read_and_refresh_cache(opts);
+
+       /*
+        * Decide what to do depending on the arguments; a fresh
+        * cherry-pick should be handled differently from an existing
+        * one that is being continued
+        */
+       if (opts->subcommand == REPLAY_RESET) {
+               remove_sequencer_state(1);
+               return 0;
+       } else if (opts->subcommand == REPLAY_CONTINUE) {
+               if (!file_exists(git_path(SEQ_TODO_FILE)))
+                       goto error;
+               read_populate_opts(&opts);
+               read_populate_todo(&todo_list, opts);
+
+               /* Verify that the conflict has been resolved */
+               if (!index_differs_from("HEAD", 0))
+                       todo_list = todo_list->next;
+       } else {
+               /*
+                * Start a new cherry-pick/ revert sequence; but
+                * first, make sure that an existing one isn't in
+                * progress
+                */
+
+               walk_revs_populate_todo(&todo_list, opts);
+               if (create_seq_dir() < 0) {
+                       error(_("A cherry-pick or revert is in progress."));
+                       advise(_("Use --continue to continue the operation"));
+                       advise(_("or --reset to forget about it"));
+                       return -1;
+               }
+               if (get_sha1("HEAD", sha1)) {
+                       if (opts->action == REVERT)
+                               return error(_("Can't revert as initial commit"));
+                       return error(_("Can't cherry-pick into empty head"));
+               }
+               save_head(sha1_to_hex(sha1));
+               save_opts(opts);
+       }
+       return pick_commits(todo_list, opts);
+error:
+       return error(_("No %s in progress"), action_name(opts));
+}
+
 int cmd_revert(int argc, const char **argv, const char *prefix)
 {
+       struct replay_opts opts;
+       int res;
+
+       memset(&opts, 0, sizeof(opts));
        if (isatty(0))
-               edit = 1;
-       action = REVERT;
-       return revert_or_cherry_pick(argc, argv);
+               opts.edit = 1;
+       opts.action = REVERT;
+       git_config(git_default_config, NULL);
+       parse_args(argc, argv, &opts);
+       res = pick_revisions(&opts);
+       if (res < 0)
+               die(_("revert failed"));
+       return res;
 }
 
 int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
 {
-       action = CHERRY_PICK;
-       return revert_or_cherry_pick(argc, argv);
+       struct replay_opts opts;
+       int res;
+
+       memset(&opts, 0, sizeof(opts));
+       opts.action = CHERRY_PICK;
+       git_config(git_default_config, NULL);
+       parse_args(argc, argv, &opts);
+       res = pick_revisions(&opts);
+       if (res < 0)
+               die(_("cherry-pick failed"));
+       return res;
 }
index 40a1675997cee19afb77b94bdd514fb5bc9a27bc..c1f6ddd927d61fbc2558ee3624224af405d918c3 100644 (file)
@@ -439,10 +439,6 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
                                args.force_update = 1;
                                continue;
                        }
-                       if (!strcmp(arg, "--quiet")) {
-                               args.quiet = 1;
-                               continue;
-                       }
                        if (!strcmp(arg, "--verbose")) {
                                args.verbose = 1;
                                continue;
@@ -492,13 +488,8 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
                fd[0] = 0;
                fd[1] = 1;
        } else {
-               struct strbuf sb = STRBUF_INIT;
-               strbuf_addstr(&sb, receivepack);
-               if (args.quiet)
-                       strbuf_addstr(&sb, " --quiet");
-               conn = git_connect(fd, dest, sb.buf,
+               conn = git_connect(fd, dest, receivepack,
                        args.verbose ? CONNECT_VERBOSE : 0);
-               strbuf_release(&sb);
        }
 
        memset(&extra_have, 0, sizeof(extra_have));
index facc63a79ec0c86fd9df59792cd4f20030af5db1..4b480d7c7ca6c6258a5cd82cfc88df62cd0d218f 100644 (file)
@@ -26,14 +26,14 @@ static const char **default_arg;
 
 static const char *get_color_code(int idx)
 {
-       if (showbranch_use_color)
+       if (want_color(showbranch_use_color))
                return column_colors_ansi[idx % column_colors_ansi_max];
        return "";
 }
 
 static const char *get_color_reset_code(void)
 {
-       if (showbranch_use_color)
+       if (want_color(showbranch_use_color))
                return GIT_COLOR_RESET;
        return "";
 }
@@ -573,7 +573,7 @@ static int git_show_branch_config(const char *var, const char *value, void *cb)
        }
 
        if (!strcmp(var, "color.showbranch")) {
-               showbranch_use_color = git_config_colorbool(var, value, -1);
+               showbranch_use_color = git_config_colorbool(var, value);
                return 0;
        }
 
@@ -685,9 +685,6 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
 
        git_config(git_show_branch_config, NULL);
 
-       if (showbranch_use_color == -1)
-               showbranch_use_color = git_use_color_default;
-
        /* If nothing is specified, try the default first */
        if (ac == 1 && default_num) {
                ac = default_num;
index 76ba1d5881b3cddc527bd363dec340c83dde605f..835c62ab15c742af2f75e7bf66e35501701ed15a 100644 (file)
@@ -11,7 +11,7 @@ static const char * const git_update_ref_usage[] = {
 
 int cmd_update_ref(int argc, const char **argv, const char *prefix)
 {
-       const char *refname, *oldval, *msg=NULL;
+       const char *refname, *oldval, *msg = NULL;
        unsigned char sha1[20], oldsha1[20];
        int delete = 0, no_deref = 0, flags = 0;
        struct option options[] = {
index f48fd7d4c1c706b68e879e4dfabdd3fb8597a328..6bf849740c6de45fe8ca11eca2a06a52c7ebc0bb 100644 (file)
--- a/bundle.c
+++ b/bundle.c
@@ -380,12 +380,15 @@ int create_bundle(struct bundle_header *header, const char *path,
        return 0;
 }
 
-int unbundle(struct bundle_header *header, int bundle_fd)
+int unbundle(struct bundle_header *header, int bundle_fd, int flags)
 {
        const char *argv_index_pack[] = {"index-pack",
-               "--fix-thin", "--stdin", NULL};
+                                        "--fix-thin", "--stdin", NULL, NULL};
        struct child_process ip;
 
+       if (flags & BUNDLE_VERBOSE)
+               argv_index_pack[3] = "-v";
+
        if (verify_bundle(header, 0))
                return -1;
        memset(&ip, 0, sizeof(ip));
index e2aedd60d6ad1482bb6da173c853e6ba4805c8d7..c5a22c8d10c7bf5e27536a3b84033b77f3542445 100644 (file)
--- a/bundle.h
+++ b/bundle.h
@@ -18,7 +18,8 @@ int read_bundle_header(const char *path, struct bundle_header *header);
 int create_bundle(struct bundle_header *header, const char *path,
                int argc, const char **argv);
 int verify_bundle(struct bundle_header *header, int verbose);
-int unbundle(struct bundle_header *header, int bundle_fd);
+#define BUNDLE_VERBOSE 1
+int unbundle(struct bundle_header *header, int bundle_fd, int flags);
 int list_bundle_refs(struct bundle_header *header,
                int argc, const char **argv);
 
diff --git a/cache.h b/cache.h
index fcf4501a60d14105134a2d5be984afde5dda3e3d..82e12c862c0ed4fb0da7fb86fc3cef26558810dc 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -438,7 +438,7 @@ extern int set_git_dir(const char *path);
 extern const char *get_git_namespace(void);
 extern const char *strip_namespace(const char *namespaced_ref);
 extern const char *get_git_work_tree(void);
-extern const char *read_gitfile_gently(const char *path);
+extern const char *read_gitfile(const char *path);
 extern void set_git_work_tree(const char *tree);
 
 #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
@@ -1076,9 +1076,11 @@ extern int git_config_bool(const char *, const char *);
 extern int git_config_maybe_bool(const char *, const char *);
 extern int git_config_string(const char **, const char *, const char *);
 extern int git_config_pathname(const char **, const char *, const char *);
+extern int git_config_set_in_file(const char *, const char *, const char *);
 extern int git_config_set(const char *, const char *);
 extern int git_config_parse_key(const char *, char **, int *);
 extern int git_config_set_multivar(const char *, const char *, const char *, int);
+extern int git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int);
 extern int git_config_rename_section(const char *, const char *);
 extern const char *git_etc_gitconfig(void);
 extern int check_repository_format_version(const char *var, const char *value, void *cb);
@@ -1195,7 +1197,7 @@ extern int ws_blank_line(const char *line, int len, unsigned ws_rule);
 #define ws_tab_width(rule)     ((rule) & WS_TAB_WIDTH_MASK)
 
 /* ls-files */
-int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset);
+int report_path_error(const char *ps_matched, const char **pathspec, const char *prefix);
 void overlay_tree_on_cache(const char *tree_name, const char *prefix);
 
 char *alias_lookup(const char *alias);
diff --git a/color.c b/color.c
index 3db214c24720496f13481b718e46810c21f53b8d..e8e26818b3b1f2ffce1374e2edf88b40c575c3dd 100644 (file)
--- a/color.c
+++ b/color.c
@@ -1,7 +1,8 @@
 #include "cache.h"
 #include "color.h"
 
-int git_use_color_default = 0;
+static int git_use_color_default = 0;
+int color_stdout_is_tty = -1;
 
 /*
  * The list of available column colors.
@@ -157,7 +158,7 @@ bad:
        die("bad color value '%.*s' for variable '%s'", value_len, value, var);
 }
 
-int git_config_colorbool(const char *var, const char *value, int stdout_is_tty)
+int git_config_colorbool(const char *var, const char *value)
 {
        if (value) {
                if (!strcasecmp(value, "never"))
@@ -165,7 +166,7 @@ int git_config_colorbool(const char *var, const char *value, int stdout_is_tty)
                if (!strcasecmp(value, "always"))
                        return 1;
                if (!strcasecmp(value, "auto"))
-                       goto auto_color;
+                       return GIT_COLOR_AUTO;
        }
 
        if (!var)
@@ -176,10 +177,14 @@ int git_config_colorbool(const char *var, const char *value, int stdout_is_tty)
                return 0;
 
        /* any normal truth value defaults to 'auto' */
- auto_color:
-       if (stdout_is_tty < 0)
-               stdout_is_tty = isatty(1);
-       if (stdout_is_tty || (pager_in_use() && pager_use_color)) {
+       return GIT_COLOR_AUTO;
+}
+
+static int check_auto_color(void)
+{
+       if (color_stdout_is_tty < 0)
+               color_stdout_is_tty = isatty(1);
+       if (color_stdout_is_tty || (pager_in_use() && pager_use_color)) {
                char *term = getenv("TERM");
                if (term && strcmp(term, "dumb"))
                        return 1;
@@ -187,13 +192,36 @@ int git_config_colorbool(const char *var, const char *value, int stdout_is_tty)
        return 0;
 }
 
-int git_color_default_config(const char *var, const char *value, void *cb)
+int want_color(int var)
+{
+       static int want_auto = -1;
+
+       if (var < 0)
+               var = git_use_color_default;
+
+       if (var == GIT_COLOR_AUTO) {
+               if (want_auto < 0)
+                       want_auto = check_auto_color();
+               return want_auto;
+       }
+       return var;
+}
+
+int git_color_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "color.ui")) {
-               git_use_color_default = git_config_colorbool(var, value, -1);
+               git_use_color_default = git_config_colorbool(var, value);
                return 0;
        }
 
+       return 0;
+}
+
+int git_color_default_config(const char *var, const char *value, void *cb)
+{
+       if (git_color_config(var, value, cb) < 0)
+               return -1;
+
        return git_default_config(var, value, cb);
 }
 
diff --git a/color.h b/color.h
index 68a926a2cdfb870ae0da0bfbc4c5b36681609911..9a8495bb7ff06eb4e94e190d902b48c23fc021f9 100644 (file)
--- a/color.h
+++ b/color.h
@@ -49,20 +49,34 @@ struct strbuf;
 #define GIT_COLOR_NIL "NIL"
 
 /*
- * This variable stores the value of color.ui
+ * The first three are chosen to match common usage in the code, and what is
+ * returned from git_config_colorbool. The "auto" value can be returned from
+ * config_colorbool, and will be converted by want_color() into either 0 or 1.
  */
-extern int git_use_color_default;
+#define GIT_COLOR_UNKNOWN -1
+#define GIT_COLOR_NEVER  0
+#define GIT_COLOR_ALWAYS 1
+#define GIT_COLOR_AUTO   2
 
 /* A default list of colors to use for commit graphs and show-branch output */
 extern const char *column_colors_ansi[];
 extern const int column_colors_ansi_max;
 
 /*
- * Use this instead of git_default_config if you need the value of color.ui.
+ * Generally the color code will lazily figure this out itself, but
+ * this provides a mechanism for callers to override autodetection.
  */
+extern int color_stdout_is_tty;
+
+/*
+ * Use the first one if you need only color config; the second is a convenience
+ * if you are just going to change to git_default_config, too.
+ */
+int git_color_config(const char *var, const char *value, void *cb);
 int git_color_default_config(const char *var, const char *value, void *cb);
 
-int git_config_colorbool(const char *var, const char *value, int stdout_is_tty);
+int git_config_colorbool(const char *var, const char *value);
+int want_color(int var);
 void color_parse(const char *value, const char *var, char *dst);
 void color_parse_mem(const char *value, int len, const char *var, char *dst);
 __attribute__((format (printf, 3, 4)))
index b11eb7102c53d3418aa875ee899e61e01de87bca..214014dc645e43ae257e46f1046c8ca0d21d3472 100644 (file)
@@ -702,9 +702,8 @@ static void show_combined_header(struct combine_diff_path *elem,
        int abbrev = DIFF_OPT_TST(opt, FULL_INDEX) ? 40 : DEFAULT_ABBREV;
        const char *a_prefix = opt->a_prefix ? opt->a_prefix : "a/";
        const char *b_prefix = opt->b_prefix ? opt->b_prefix : "b/";
-       int use_color = DIFF_OPT_TST(opt, COLOR_DIFF);
-       const char *c_meta = diff_get_color(use_color, DIFF_METAINFO);
-       const char *c_reset = diff_get_color(use_color, DIFF_RESET);
+       const char *c_meta = diff_get_color_opt(opt, DIFF_METAINFO);
+       const char *c_reset = diff_get_color_opt(opt, DIFF_RESET);
        const char *abb;
        int added = 0;
        int deleted = 0;
@@ -964,7 +963,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
                show_combined_header(elem, num_parent, dense, rev,
                                     mode_differs, 1);
                dump_sline(sline, cnt, num_parent,
-                          DIFF_OPT_TST(opt, COLOR_DIFF), result_deleted);
+                          opt->use_color, result_deleted);
        }
        free(result);
 
@@ -1050,6 +1049,72 @@ void show_combined_diff(struct combine_diff_path *p,
                show_patch_diff(p, num_parent, dense, 1, rev);
 }
 
+static void free_combined_pair(struct diff_filepair *pair)
+{
+       free(pair->two);
+       free(pair);
+}
+
+/*
+ * A combine_diff_path expresses N parents on the LHS against 1 merge
+ * result. Synthesize a diff_filepair that has N entries on the "one"
+ * side and 1 entry on the "two" side.
+ *
+ * In the future, we might want to add more data to combine_diff_path
+ * so that we can fill fields we are ignoring (most notably, size) here,
+ * but currently nobody uses it, so this should suffice for now.
+ */
+static struct diff_filepair *combined_pair(struct combine_diff_path *p,
+                                          int num_parent)
+{
+       int i;
+       struct diff_filepair *pair;
+       struct diff_filespec *pool;
+
+       pair = xmalloc(sizeof(*pair));
+       pool = xcalloc(num_parent + 1, sizeof(struct diff_filespec));
+       pair->one = pool + 1;
+       pair->two = pool;
+
+       for (i = 0; i < num_parent; i++) {
+               pair->one[i].path = p->path;
+               pair->one[i].mode = p->parent[i].mode;
+               hashcpy(pair->one[i].sha1, p->parent[i].sha1);
+               pair->one[i].sha1_valid = !is_null_sha1(p->parent[i].sha1);
+               pair->one[i].has_more_entries = 1;
+       }
+       pair->one[num_parent - 1].has_more_entries = 0;
+
+       pair->two->path = p->path;
+       pair->two->mode = p->mode;
+       hashcpy(pair->two->sha1, p->sha1);
+       pair->two->sha1_valid = !is_null_sha1(p->sha1);
+       return pair;
+}
+
+static void handle_combined_callback(struct diff_options *opt,
+                                    struct combine_diff_path *paths,
+                                    int num_parent,
+                                    int num_paths)
+{
+       struct combine_diff_path *p;
+       struct diff_queue_struct q;
+       int i;
+
+       q.queue = xcalloc(num_paths, sizeof(struct diff_filepair *));
+       q.alloc = num_paths;
+       q.nr = num_paths;
+       for (i = 0, p = paths; p; p = p->next) {
+               if (!p->len)
+                       continue;
+               q.queue[i++] = combined_pair(p, num_parent);
+       }
+       opt->format_callback(&q, opt, opt->format_callback_data);
+       for (i = 0; i < num_paths; i++)
+               free_combined_pair(q.queue[i]);
+       free(q.queue);
+}
+
 void diff_tree_combined(const unsigned char *sha1,
                        const unsigned char parent[][20],
                        int num_parent,
@@ -1109,6 +1174,9 @@ void diff_tree_combined(const unsigned char *sha1,
                else if (opt->output_format &
                         (DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT))
                        needsep = 1;
+               else if (opt->output_format & DIFF_FORMAT_CALLBACK)
+                       handle_combined_callback(opt, paths, num_parent, num_paths);
+
                if (opt->output_format & DIFF_FORMAT_PATCH) {
                        if (needsep)
                                putchar(opt->line_termination);
index ac337c7d7dc1724fa918f9340816d3102edb10bd..97b43279cdf46159462d5f56abc20f2161f4c7ea 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -214,22 +214,12 @@ struct commit_graft *lookup_commit_graft(const unsigned char *sha1)
        return commit_graft[pos];
 }
 
-int write_shallow_commits(struct strbuf *out, int use_pack_protocol)
-{
-       int i, count = 0;
-       for (i = 0; i < commit_graft_nr; i++)
-               if (commit_graft[i]->nr_parent < 0) {
-                       const char *hex =
-                               sha1_to_hex(commit_graft[i]->sha1);
-                       count++;
-                       if (use_pack_protocol)
-                               packet_buf_write(out, "shallow %s", hex);
-                       else {
-                               strbuf_addstr(out, hex);
-                               strbuf_addch(out, '\n');
-                       }
-               }
-       return count;
+int for_each_commit_graft(each_commit_graft_fn fn, void *cb_data)
+{
+       int i, ret;
+       for (i = ret = 0; i < commit_graft_nr && !ret; i++)
+               ret = fn(commit_graft[i], cb_data);
+       return ret;
 }
 
 int unregister_shallow(const unsigned char *sha1)
@@ -515,7 +505,7 @@ void sort_in_topological_order(struct commit_list ** list, int lifo)
 
                commit = work_item->item;
                for (parents = commit->parents; parents ; parents = parents->next) {
-                       struct commit *parent=parents->item;
+                       struct commit *parent = parents->item;
 
                        if (!parent->indegree)
                                continue;
index a2d571b97410fa857b4c177325c4556dac50fe3f..12d100b8b6fcd092f3a6886a75c720011ef1b7dc 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -142,6 +142,7 @@ struct commit_graft {
        int nr_parent; /* < 0 if shallow commit */
        unsigned char parent[FLEX_ARRAY][20]; /* more */
 };
+typedef int (*each_commit_graft_fn)(const struct commit_graft *, void *);
 
 struct commit_graft *read_graft_line(char *buf, int len);
 int register_commit_graft(struct commit_graft *, int);
@@ -153,7 +154,7 @@ extern struct commit_list *get_octopus_merge_bases(struct commit_list *in);
 
 extern int register_shallow(const unsigned char *sha1);
 extern int unregister_shallow(const unsigned char *sha1);
-extern int write_shallow_commits(struct strbuf *out, int use_pack_protocol);
+extern int for_each_commit_graft(each_commit_graft_fn, void *);
 extern int is_repository_shallow(void);
 extern struct commit_list *get_shallow_commits(struct object_array *heads,
                int depth, int shallow_flag, int not_shallow_flag);
index 4183f80262ea9f24e286295a2295f93459548b78..a9e23594bd18b1cde49b55a40b2af8e2f3b74439 100644 (file)
--- a/config.c
+++ b/config.c
@@ -1095,6 +1095,12 @@ contline:
        return offset;
 }
 
+int git_config_set_in_file(const char *config_filename,
+                       const char *key, const char *value)
+{
+       return git_config_set_multivar_in_file(config_filename, key, value, NULL, 0);
+}
+
 int git_config_set(const char *key, const char *value)
 {
        return git_config_set_multivar(key, value, NULL, 0);
@@ -1192,19 +1198,14 @@ out_free_ret_1:
  * - the config file is removed and the lock file rename()d to it.
  *
  */
-int git_config_set_multivar(const char *key, const char *value,
-       const char *value_regex, int multi_replace)
+int git_config_set_multivar_in_file(const char *config_filename,
+                               const char *key, const char *value,
+                               const char *value_regex, int multi_replace)
 {
        int fd = -1, in_fd;
        int ret;
-       char *config_filename;
        struct lock_file *lock = NULL;
 
-       if (config_exclusive_filename)
-               config_filename = xstrdup(config_exclusive_filename);
-       else
-               config_filename = git_pathdup("config");
-
        /* parse-key returns negative; flip the sign to feed exit(3) */
        ret = 0 - git_config_parse_key(key, &store.key, &store.baselen);
        if (ret)
@@ -1381,7 +1382,6 @@ int git_config_set_multivar(const char *key, const char *value,
 out_free:
        if (lock)
                rollback_lock_file(lock);
-       free(config_filename);
        return ret;
 
 write_err_out:
@@ -1390,6 +1390,24 @@ write_err_out:
 
 }
 
+int git_config_set_multivar(const char *key, const char *value,
+                       const char *value_regex, int multi_replace)
+{
+       const char *config_filename;
+       char *buf = NULL;
+       int ret;
+
+       if (config_exclusive_filename)
+               config_filename = config_exclusive_filename;
+       else
+               config_filename = buf = git_pathdup("config");
+
+       ret = git_config_set_multivar_in_file(config_filename, key, value,
+                                       value_regex, multi_replace);
+       free(buf);
+       return ret;
+}
+
 static int section_name_match (const char *buf, const char *name)
 {
        int i = 0, j = 0, dot = 0;
diff --git a/connected.c b/connected.c
new file mode 100644 (file)
index 0000000..d762423
--- /dev/null
@@ -0,0 +1,62 @@
+#include "cache.h"
+#include "run-command.h"
+#include "sigchain.h"
+#include "connected.h"
+
+/*
+ * If we feed all the commits we want to verify to this command
+ *
+ *  $ git rev-list --verify-objects --stdin --not --all
+ *
+ * and if it does not error out, that means everything reachable from
+ * these commits locally exists and is connected to some of our
+ * existing refs.
+ *
+ * Returns 0 if everything is connected, non-zero otherwise.
+ */
+int check_everything_connected(sha1_iterate_fn fn, int quiet, void *cb_data)
+{
+       struct child_process rev_list;
+       const char *argv[] = {"rev-list", "--verify-objects",
+                             "--stdin", "--not", "--all", NULL, NULL};
+       char commit[41];
+       unsigned char sha1[20];
+       int err = 0;
+
+       if (fn(cb_data, sha1))
+               return err;
+
+       if (quiet)
+               argv[5] = "--quiet";
+
+       memset(&rev_list, 0, sizeof(rev_list));
+       rev_list.argv = argv;
+       rev_list.git_cmd = 1;
+       rev_list.in = -1;
+       rev_list.no_stdout = 1;
+       rev_list.no_stderr = quiet;
+       if (start_command(&rev_list))
+               return error(_("Could not run 'git rev-list'"));
+
+       sigchain_push(SIGPIPE, SIG_IGN);
+
+       commit[40] = '\n';
+       do {
+               memcpy(commit, sha1_to_hex(sha1), 40);
+               if (write_in_full(rev_list.in, commit, 41) < 0) {
+                       if (errno != EPIPE && errno != EINVAL)
+                               error(_("failed write to rev-list: %s"),
+                                     strerror(errno));
+                       err = -1;
+                       break;
+               }
+       } while (!fn(cb_data, sha1));
+
+       if (close(rev_list.in)) {
+               error(_("failed to close rev-list's stdin: %s"), strerror(errno));
+               err = -1;
+       }
+
+       sigchain_pop(SIGPIPE);
+       return finish_command(&rev_list) || err;
+}
diff --git a/connected.h b/connected.h
new file mode 100644 (file)
index 0000000..7e4585a
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef CONNECTED_H
+#define CONNECTED_H
+
+/*
+ * Take callback data, and return next object name in the buffer.
+ * When called after returning the name for the last object, return -1
+ * to signal EOF, otherwise return 0.
+ */
+typedef int (*sha1_iterate_fn)(void *, unsigned char [20]);
+
+/*
+ * Make sure that our object store has all the commits necessary to
+ * connect the ancestry chain to some of our existing refs, and all
+ * the trees and blobs that these commits use.
+ *
+ * Return 0 if Ok, non zero otherwise (i.e. some missing objects)
+ */
+extern int check_everything_connected(sha1_iterate_fn, int quiet, void *cb_data);
+
+#endif /* CONNECTED_H */
index 6b9de9e7e0e3304d42048ff3680eac9270330ee4..2f7b270566471ebe8088cd2f024c0ca5e49eee4c 100755 (executable)
@@ -342,6 +342,11 @@ def gitConfig(key, args = None): # set args to "--bool", for instance
         _gitConfig[key] = read_pipe(cmd, ignore_error=True).strip()
     return _gitConfig[key]
 
+def gitConfigList(key):
+    if not _gitConfig.has_key(key):
+        _gitConfig[key] = read_pipe("git config --get-all %s" % key, ignore_error=True).strip().split(os.linesep)
+    return _gitConfig[key]
+
 def p4BranchesInGit(branchesAreInRemotes = True):
     branches = {}
 
@@ -774,17 +779,22 @@ class P4Submit(Command, P4UserMap):
 
         if not self.detectRenames:
             # If not explicitly set check the config variable
-            self.detectRenames = gitConfig("git-p4.detectRenames").lower() == "true"
+            self.detectRenames = gitConfig("git-p4.detectRenames")
 
-        if self.detectRenames:
+        if self.detectRenames.lower() == "false" or self.detectRenames == "":
+            diffOpts = ""
+        elif self.detectRenames.lower() == "true":
             diffOpts = "-M"
         else:
-            diffOpts = ""
+            diffOpts = "-M%s" % self.detectRenames
 
-        if gitConfig("git-p4.detectCopies").lower() == "true":
+        detectCopies = gitConfig("git-p4.detectCopies")
+        if detectCopies.lower() == "true":
             diffOpts += " -C"
+        elif detectCopies != "" and detectCopies.lower() != "false":
+            diffOpts += " -C%s" % detectCopies
 
-        if gitConfig("git-p4.detectCopiesHarder").lower() == "true":
+        if gitConfig("git-p4.detectCopiesHarder", "--bool") == "true":
             diffOpts += " --find-copies-harder"
 
         diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
@@ -1450,7 +1460,13 @@ class P4Sync(Command, P4UserMap):
     def getBranchMapping(self):
         lostAndFoundBranches = set()
 
-        for info in p4CmdList("branches"):
+        user = gitConfig("git-p4.branchUser")
+        if len(user) > 0:
+            command = "branches -u %s" % user
+        else:
+            command = "branches"
+
+        for info in p4CmdList(command):
             details = p4Cmd("branch -o %s" % info["branch"])
             viewIdx = 0
             while details.has_key("View%s" % viewIdx):
@@ -1479,6 +1495,25 @@ class P4Sync(Command, P4UserMap):
                     if source not in self.knownBranches:
                         lostAndFoundBranches.add(source)
 
+        # Perforce does not strictly require branches to be defined, so we also
+        # check git config for a branch list.
+        #
+        # Example of branch definition in git config file:
+        # [git-p4]
+        #   branchList=main:branchA
+        #   branchList=main:branchB
+        #   branchList=branchA:branchC
+        configBranches = gitConfigList("git-p4.branchList")
+        for branch in configBranches:
+            if branch:
+                (source, destination) = branch.split(":")
+                self.knownBranches[destination] = source
+
+                lostAndFoundBranches.discard(destination)
+
+                if source not in self.knownBranches:
+                    lostAndFoundBranches.add(source)
+
 
         for branch in lostAndFoundBranches:
             self.knownBranches[branch] = branch
@@ -1824,12 +1859,14 @@ class P4Sync(Command, P4UserMap):
                     else:
                         paths = []
                         for (prev, cur) in zip(self.previousDepotPaths, depotPaths):
-                            for i in range(0, min(len(cur), len(prev))):
-                                if cur[i] <> prev[i]:
+                            prev_list = prev.split("/")
+                            cur_list = cur.split("/")
+                            for i in range(0, min(len(cur_list), len(prev_list))):
+                                if cur_list[i] <> prev_list[i]:
                                     i = i - 1
                                     break
 
-                            paths.append (cur[:i + 1])
+                            paths.append ("/".join(cur_list[:i + 1]))
 
                         self.previousDepotPaths = paths
 
index caa4bb3e30c081394b464de6e92ca3298a87fdd1..52003ae9045626077009811cd8e10c1135d69cd0 100644 (file)
@@ -232,6 +232,44 @@ git-p4.skipUserNameCheck
 When submitting, git-p4 checks that the git commits are authored by the current
 p4 user, and warns if they are not. This disables the check.
 
+git-p4.detectRenames
+
+Detect renames when submitting changes to Perforce server. Will enable -M git
+argument. Can be optionally set to a number representing the threshold
+percentage value of the rename detection.
+
+  git config [--global] git-p4.detectRenames true
+  git config [--global] git-p4.detectRenames 50
+
+git-p4.detectCopies
+
+Detect copies when submitting changes to Perforce server. Will enable -C git
+argument. Can be optionally set to a number representing the threshold
+percentage value of the copy detection.
+
+  git config [--global] git-p4.detectCopies true
+  git config [--global] git-p4.detectCopies 80
+
+git-p4.detectCopiesHarder
+
+Detect copies even between files that did not change when submitting changes to
+Perforce server. Will enable --find-copies-harder git argument.
+
+  git config [--global] git-p4.detectCopies true
+
+git-p4.branchUser
+
+Only use branch specifications defined by the selected username.
+
+  git config [--global] git-p4.branchUser username
+
+git-p4.branchList
+
+List of branches to be imported when branch detection is enabled.
+
+  git config [--global] git-p4.branchList main:branchA
+  git config [--global] --add git-p4.branchList main:branchB
+
 Implementation Details...
 =========================
 
index fa6d41a1ab8c5f3de777c1ba93d241eb5140a3c7..ba077c13f96779d5b29237e83974b0910b4bbdde 100755 (executable)
 # will have put this somewhere standard.  You should make this script
 # executable then link to it in the repository you would like to use it in.
 # For example, on debian the hook is stored in
-# /usr/share/doc/git-core/contrib/hooks/post-receive-email:
+# /usr/share/git-core/contrib/hooks/post-receive-email:
 #
 #  chmod a+x post-receive-email
 #  cd /path/to/your/repository.git
-#  ln -sf /usr/share/doc/git-core/contrib/hooks/post-receive-email hooks/post-receive
+#  ln -sf /usr/share/git-core/contrib/hooks/post-receive-email hooks/post-receive
 #
 # This hook script assumes it is enabled on the central repository of a
 # project, with all users pushing only to it and not between each other.  It
index 416bf83c75510930146dc0e3c2dd3273a9cb4e33..3bb5a4dd57c669bc59be0e2317ef33b64b024992 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -533,7 +533,7 @@ static int ident_to_git(const char *path, const char *src, size_t len,
                dollar = memchr(src, '$', len);
                if (!dollar)
                        break;
-               memcpy(dst, src, dollar + 1 - src);
+               memmove(dst, src, dollar + 1 - src);
                dst += dollar + 1 - src;
                len -= dollar + 1 - src;
                src  = dollar + 1;
@@ -553,7 +553,7 @@ static int ident_to_git(const char *path, const char *src, size_t len,
                        src  = dollar + 1;
                }
        }
-       memcpy(dst, src, len);
+       memmove(dst, src, len);
        strbuf_setlen(buf, dst + len - buf->buf);
        return 1;
 }
diff --git a/date.c b/date.c
index 896fbb48060c673b8dc2f2aa6ea3de3c1042fbd2..353e0a5e53f13c36549e65452931f254de3423c4 100644 (file)
--- a/date.c
+++ b/date.c
@@ -552,23 +552,35 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt
 static int match_tz(const char *date, int *offp)
 {
        char *end;
-       int offset = strtoul(date+1, &end, 10);
-       int min, hour;
-       int n = end - date - 1;
+       int hour = strtoul(date + 1, &end, 10);
+       int n = end - (date + 1);
+       int min = 0;
 
-       min = offset % 100;
-       hour = offset / 100;
+       if (n == 4) {
+               /* hhmm */
+               min = hour % 100;
+               hour = hour / 100;
+       } else if (n != 2) {
+               min = 99; /* random crap */
+       } else if (*end == ':') {
+               /* hh:mm? */
+               min = strtoul(end + 1, &end, 10);
+               if (end - (date + 1) != 5)
+                       min = 99; /* random crap */
+       } /* otherwise we parsed "hh" */
 
        /*
-        * Don't accept any random crap.. At least 3 digits, and
-        * a valid minute. We might want to check that the minutes
-        * are divisible by 30 or something too.
+        * Don't accept any random crap. Even though some places have
+        * offset larger than 12 hours (e.g. Pacific/Kiritimati is at
+        * UTC+14), there is something wrong if hour part is much
+        * larger than that. We might also want to check that the
+        * minutes are divisible by 15 or something too. (Offset of
+        * Kathmandu, Nepal is UTC+5:45)
         */
-       if (min < 60 && n > 2) {
-               offset = hour*60+min;
+       if (min < 60 && hour < 24) {
+               int offset = hour * 60 + min;
                if (*date == '-')
                        offset = -offset;
-
                *offp = offset;
        }
        return end - date;
index f8454dd2918dc74bee43d9f2faa1837cc5665abd..ebe751e72d0ca9db2f0a9d2576f3dc3f41ffc31d 100644 (file)
@@ -468,6 +468,7 @@ static int diff_cache(struct rev_info *revs,
        opts.unpack_data = revs;
        opts.src_index = &the_index;
        opts.dst_index = NULL;
+       opts.pathspec = &revs->diffopt.pathspec;
 
        init_tree_desc(&t, tree->buffer, tree->size);
        return unpack_trees(1, &t, &opts);
diff --git a/diff.c b/diff.c
index d3d8daec77142bc6a67aacdc2e62ee522ddf12db..fcc0078074c364d0a4c2bd75a6d390e517eb7f87 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -137,7 +137,7 @@ static int git_config_rename(const char *var, const char *value)
 int git_diff_ui_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
-               diff_use_color_default = git_config_colorbool(var, value, -1);
+               diff_use_color_default = git_config_colorbool(var, value);
                return 0;
        }
        if (!strcmp(var, "diff.renames")) {
@@ -164,6 +164,9 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
        if (!strcmp(var, "diff.ignoresubmodules"))
                handle_ignore_submodules_arg(&default_diff_options, value);
 
+       if (git_color_config(var, value, cb) < 0)
+               return -1;
+
        return git_diff_basic_config(var, value, cb);
 }
 
@@ -212,7 +215,7 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
        if (!prefixcmp(var, "submodule."))
                return parse_submodule_config_option(var, value);
 
-       return git_color_default_config(var, value, cb);
+       return git_default_config(var, value, cb);
 }
 
 static char *quote_two(const char *one, const char *two)
@@ -583,11 +586,10 @@ static void emit_rewrite_diff(const char *name_a,
                              struct diff_options *o)
 {
        int lc_a, lc_b;
-       int color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
        const char *name_a_tab, *name_b_tab;
-       const char *metainfo = diff_get_color(color_diff, DIFF_METAINFO);
-       const char *fraginfo = diff_get_color(color_diff, DIFF_FRAGINFO);
-       const char *reset = diff_get_color(color_diff, DIFF_RESET);
+       const char *metainfo = diff_get_color(o->use_color, DIFF_METAINFO);
+       const char *fraginfo = diff_get_color(o->use_color, DIFF_FRAGINFO);
+       const char *reset = diff_get_color(o->use_color, DIFF_RESET);
        static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT;
        const char *a_prefix, *b_prefix;
        char *data_one, *data_two;
@@ -623,7 +625,7 @@ static void emit_rewrite_diff(const char *name_a,
        size_two = fill_textconv(textconv_two, two, &data_two);
 
        memset(&ecbdata, 0, sizeof(ecbdata));
-       ecbdata.color_diff = color_diff;
+       ecbdata.color_diff = want_color(o->use_color);
        ecbdata.found_changesp = &o->found_changes;
        ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
        ecbdata.opt = o;
@@ -1004,7 +1006,7 @@ static void free_diff_words_data(struct emit_callback *ecbdata)
 
 const char *diff_get_color(int diff_use_color, enum color_diff ix)
 {
-       if (diff_use_color)
+       if (want_color(diff_use_color))
                return diff_colors[ix];
        return "";
 }
@@ -1808,11 +1810,10 @@ static int is_conflict_marker(const char *line, int marker_size, unsigned long l
 static void checkdiff_consume(void *priv, char *line, unsigned long len)
 {
        struct checkdiff_t *data = priv;
-       int color_diff = DIFF_OPT_TST(data->o, COLOR_DIFF);
        int marker_size = data->conflict_marker_size;
-       const char *ws = diff_get_color(color_diff, DIFF_WHITESPACE);
-       const char *reset = diff_get_color(color_diff, DIFF_RESET);
-       const char *set = diff_get_color(color_diff, DIFF_FILE_NEW);
+       const char *ws = diff_get_color(data->o->use_color, DIFF_WHITESPACE);
+       const char *reset = diff_get_color(data->o->use_color, DIFF_RESET);
+       const char *set = diff_get_color(data->o->use_color, DIFF_FILE_NEW);
        char *err;
        char *line_prefix = "";
        struct strbuf *msgbuf;
@@ -2157,7 +2158,7 @@ static void builtin_diff(const char *name_a,
                memset(&xecfg, 0, sizeof(xecfg));
                memset(&ecbdata, 0, sizeof(ecbdata));
                ecbdata.label_path = lbl;
-               ecbdata.color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
+               ecbdata.color_diff = want_color(o->use_color);
                ecbdata.found_changesp = &o->found_changes;
                ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
                if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
@@ -2205,7 +2206,7 @@ static void builtin_diff(const char *name_a,
                                        break;
                                }
                        }
-                       if (DIFF_OPT_TST(o, COLOR_DIFF)) {
+                       if (want_color(o->use_color)) {
                                struct diff_words_style *st = ecbdata.diff_words->style;
                                st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD);
                                st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW);
@@ -2855,7 +2856,7 @@ static void run_diff_cmd(const char *pgm,
                 */
                fill_metainfo(msg, name, other, one, two, o, p,
                              &must_show_header,
-                             DIFF_OPT_TST(o, COLOR_DIFF) && !pgm);
+                             want_color(o->use_color) && !pgm);
                xfrm_msg = msg->len ? msg->buf : NULL;
        }
 
@@ -3021,8 +3022,7 @@ void diff_setup(struct diff_options *options)
 
        options->change = diff_change;
        options->add_remove = diff_addremove;
-       if (diff_use_color_default > 0)
-               DIFF_OPT_SET(options, COLOR_DIFF);
+       options->use_color = diff_use_color_default;
        options->detect_rename = diff_detect_rename_default;
 
        if (diff_no_prefix) {
@@ -3412,24 +3412,21 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (!strcmp(arg, "--follow"))
                DIFF_OPT_SET(options, FOLLOW_RENAMES);
        else if (!strcmp(arg, "--color"))
-               DIFF_OPT_SET(options, COLOR_DIFF);
+               options->use_color = 1;
        else if (!prefixcmp(arg, "--color=")) {
-               int value = git_config_colorbool(NULL, arg+8, -1);
-               if (value == 0)
-                       DIFF_OPT_CLR(options, COLOR_DIFF);
-               else if (value > 0)
-                       DIFF_OPT_SET(options, COLOR_DIFF);
-               else
+               int value = git_config_colorbool(NULL, arg+8);
+               if (value < 0)
                        return error("option `color' expects \"always\", \"auto\", or \"never\"");
+               options->use_color = value;
        }
        else if (!strcmp(arg, "--no-color"))
-               DIFF_OPT_CLR(options, COLOR_DIFF);
+               options->use_color = 0;
        else if (!strcmp(arg, "--color-words")) {
-               DIFF_OPT_SET(options, COLOR_DIFF);
+               options->use_color = 1;
                options->word_diff = DIFF_WORDS_COLOR;
        }
        else if (!prefixcmp(arg, "--color-words=")) {
-               DIFF_OPT_SET(options, COLOR_DIFF);
+               options->use_color = 1;
                options->word_diff = DIFF_WORDS_COLOR;
                options->word_regex = arg + 14;
        }
@@ -3442,7 +3439,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                if (!strcmp(type, "plain"))
                        options->word_diff = DIFF_WORDS_PLAIN;
                else if (!strcmp(type, "color")) {
-                       DIFF_OPT_SET(options, COLOR_DIFF);
+                       options->use_color = 1;
                        options->word_diff = DIFF_WORDS_COLOR;
                }
                else if (!strcmp(type, "porcelain"))
diff --git a/diff.h b/diff.h
index b920a20f80936ab66f5fe88c950dd35e38e1e856..8c66b59517305546d3e2f66fca652284468e1da1 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -58,7 +58,7 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data)
 #define DIFF_OPT_SILENT_ON_REMOVE    (1 <<  5)
 #define DIFF_OPT_FIND_COPIES_HARDER  (1 <<  6)
 #define DIFF_OPT_FOLLOW_RENAMES      (1 <<  7)
-#define DIFF_OPT_COLOR_DIFF          (1 <<  8)
+/* (1 <<  8) unused */
 /* (1 <<  9) unused */
 #define DIFF_OPT_HAS_CHANGES         (1 << 10)
 #define DIFF_OPT_QUICK               (1 << 11)
@@ -101,6 +101,7 @@ struct diff_options {
        const char *single_follow;
        const char *a_prefix, *b_prefix;
        unsigned flags;
+       int use_color;
        int context;
        int interhunkcontext;
        int break_opt;
@@ -160,7 +161,7 @@ enum color_diff {
 };
 const char *diff_get_color(int diff_use_color, enum color_diff ix);
 #define diff_get_color_opt(o, ix) \
-       diff_get_color(DIFF_OPT_TST((o), COLOR_DIFF), ix)
+       diff_get_color((o)->use_color, ix)
 
 
 extern const char mime_boundary_leader[];
index b8f1fdecf4d9e5e8f8e834eb55a374f1af8352fd..8f32b824cdc1bac1f094d743f3bee9f32890edca 100644 (file)
@@ -45,7 +45,7 @@ struct diff_filespec {
        unsigned dirty_submodule : 2;  /* For submodules: its work tree is dirty */
 #define DIRTY_SUBMODULE_UNTRACKED 1
 #define DIRTY_SUBMODULE_MODIFIED  2
-
+       unsigned has_more_entries : 1; /* only appear in combined diff */
        struct userdiff_driver *driver;
        /* data should be considered "binary"; -1 means "don't know yet" */
        int is_binary;
index 03d29e8d48f46428c4efdacac84e6a0e1ddf4a0b..e96edcfebc4174a5166c11e7a511ea792e7a2639 100644 (file)
@@ -117,7 +117,7 @@ static void setup_git_env(void)
        git_dir = getenv(GIT_DIR_ENVIRONMENT);
        git_dir = git_dir ? xstrdup(git_dir) : NULL;
        if (!git_dir) {
-               git_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT);
+               git_dir = read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT);
                git_dir = git_dir ? xstrdup(git_dir) : NULL;
        }
        if (!git_dir)
index 7cc22625e5b73d2778566a154bce94316311b0c8..742e7da6b8b58dd0803d89c6ad6c59589274a31b 100644 (file)
@@ -170,6 +170,11 @@ Format of STDIN stream:
 #define DEPTH_BITS 13
 #define MAX_DEPTH ((1<<DEPTH_BITS)-1)
 
+/*
+ * We abuse the setuid bit on directories to mean "do not delta".
+ */
+#define NO_DELTA S_ISUID
+
 struct object_entry {
        struct pack_idx_entry idx;
        struct object_entry *next;
@@ -284,6 +289,7 @@ static uintmax_t marks_set_count;
 static uintmax_t object_count_by_type[1 << TYPE_BITS];
 static uintmax_t duplicate_count_by_type[1 << TYPE_BITS];
 static uintmax_t delta_count_by_type[1 << TYPE_BITS];
+static uintmax_t delta_count_attempts_by_type[1 << TYPE_BITS];
 static unsigned long object_count;
 static unsigned long branch_count;
 static unsigned long branch_load_count;
@@ -1045,6 +1051,7 @@ static int store_object(
        }
 
        if (last && last->data.buf && last->depth < max_depth && dat->len > 20) {
+               delta_count_attempts_by_type[type]++;
                delta = diff_delta(last->data.buf, last->data.len,
                        dat->buf, dat->len,
                        &deltalen, dat->len - 20);
@@ -1416,8 +1423,9 @@ static void mktree(struct tree_content *t, int v, struct strbuf *b)
                struct tree_entry *e = t->entries[i];
                if (!e->versions[v].mode)
                        continue;
-               strbuf_addf(b, "%o %s%c", (unsigned int)e->versions[v].mode,
-                                       e->name->str_dat, '\0');
+               strbuf_addf(b, "%o %s%c",
+                       (unsigned int)(e->versions[v].mode & ~NO_DELTA),
+                       e->name->str_dat, '\0');
                strbuf_add(b, e->versions[v].sha1, 20);
        }
 }
@@ -1427,7 +1435,7 @@ static void store_tree(struct tree_entry *root)
        struct tree_content *t = root->tree;
        unsigned int i, j, del;
        struct last_object lo = { STRBUF_INIT, 0, 0, /* no_swap */ 1 };
-       struct object_entry *le;
+       struct object_entry *le = NULL;
 
        if (!is_null_sha1(root->versions[1].sha1))
                return;
@@ -1437,7 +1445,8 @@ static void store_tree(struct tree_entry *root)
                        store_tree(t->entries[i]);
        }
 
-       le = find_object(root->versions[0].sha1);
+       if (!(root->versions[0].mode & NO_DELTA))
+               le = find_object(root->versions[0].sha1);
        if (S_ISDIR(root->versions[0].mode) && le && le->pack_id == pack_id) {
                mktree(t, 0, &old_tree);
                lo.data = old_tree;
@@ -1471,6 +1480,7 @@ static void tree_content_replace(
 {
        if (!S_ISDIR(mode))
                die("Root cannot be a non-directory");
+       hashclr(root->versions[0].sha1);
        hashcpy(root->versions[1].sha1, sha1);
        if (root->tree)
                release_tree_content_recursive(root->tree);
@@ -1515,6 +1525,23 @@ static int tree_content_set(
                                if (e->tree)
                                        release_tree_content_recursive(e->tree);
                                e->tree = subtree;
+
+                               /*
+                                * We need to leave e->versions[0].sha1 alone
+                                * to avoid modifying the preimage tree used
+                                * when writing out the parent directory.
+                                * But after replacing the subdir with a
+                                * completely different one, it's not a good
+                                * delta base any more, and besides, we've
+                                * thrown away the tree entries needed to
+                                * make a delta against it.
+                                *
+                                * So let's just explicitly disable deltas
+                                * for the subtree.
+                                */
+                               if (S_ISDIR(e->versions[0].mode))
+                                       e->versions[0].mode |= NO_DELTA;
+
                                hashclr(root->versions[1].sha1);
                                return 1;
                        }
@@ -1969,32 +1996,41 @@ static int validate_raw_date(const char *src, char *result, int maxlen)
 
 static char *parse_ident(const char *buf)
 {
-       const char *gt;
+       const char *ltgt;
        size_t name_len;
        char *ident;
 
-       gt = strrchr(buf, '>');
-       if (!gt)
+       /* ensure there is a space delimiter even if there is no name */
+       if (*buf == '<')
+               --buf;
+
+       ltgt = buf + strcspn(buf, "<>");
+       if (*ltgt != '<')
+               die("Missing < in ident string: %s", buf);
+       if (ltgt != buf && ltgt[-1] != ' ')
+               die("Missing space before < in ident string: %s", buf);
+       ltgt = ltgt + 1 + strcspn(ltgt + 1, "<>");
+       if (*ltgt != '>')
                die("Missing > in ident string: %s", buf);
-       gt++;
-       if (*gt != ' ')
+       ltgt++;
+       if (*ltgt != ' ')
                die("Missing space after > in ident string: %s", buf);
-       gt++;
-       name_len = gt - buf;
+       ltgt++;
+       name_len = ltgt - buf;
        ident = xmalloc(name_len + 24);
        strncpy(ident, buf, name_len);
 
        switch (whenspec) {
        case WHENSPEC_RAW:
-               if (validate_raw_date(gt, ident + name_len, 24) < 0)
-                       die("Invalid raw date \"%s\" in ident: %s", gt, buf);
+               if (validate_raw_date(ltgt, ident + name_len, 24) < 0)
+                       die("Invalid raw date \"%s\" in ident: %s", ltgt, buf);
                break;
        case WHENSPEC_RFC2822:
-               if (parse_date(gt, ident + name_len, 24) < 0)
-                       die("Invalid rfc2822 date \"%s\" in ident: %s", gt, buf);
+               if (parse_date(ltgt, ident + name_len, 24) < 0)
+                       die("Invalid rfc2822 date \"%s\" in ident: %s", ltgt, buf);
                break;
        case WHENSPEC_NOW:
-               if (strcmp("now", gt))
+               if (strcmp("now", ltgt))
                        die("Date in ident must be 'now': %s", buf);
                datestamp(ident + name_len, 24);
                break;
@@ -2690,13 +2726,13 @@ static void parse_new_tag(void)
                type = oe->type;
                hashcpy(sha1, oe->idx.sha1);
        } else if (!get_sha1(from, sha1)) {
-               unsigned long size;
-               char *buf;
-
-               buf = read_sha1_file(sha1, &type, &size);
-               if (!buf || size < 46)
-                       die("Not a valid commit: %s", from);
-               free(buf);
+               struct object_entry *oe = find_object(sha1);
+               if (!oe) {
+                       type = sha1_object_info(sha1, NULL);
+                       if (type < 0)
+                               die("Not a valid object: %s", from);
+               } else
+                       type = oe->type;
        } else
                die("Invalid ref name or SHA1 expression: %s", from);
        read_next_command();
@@ -2800,7 +2836,12 @@ static void cat_blob(struct object_entry *oe, unsigned char sha1[20])
        strbuf_release(&line);
        cat_blob_write(buf, size);
        cat_blob_write("\n", 1);
-       free(buf);
+       if (oe && oe->pack_id == pack_id) {
+               last_blob.offset = oe->idx.offset;
+               strbuf_attach(&last_blob.data, buf, size, size);
+               last_blob.depth = oe->depth;
+       } else
+               free(buf);
 }
 
 static void parse_cat_blob(void)
@@ -2929,7 +2970,7 @@ static void print_ls(int mode, const unsigned char *sha1, const char *path)
                /* mode SP type SP object_name TAB path LF */
                strbuf_reset(&line);
                strbuf_addf(&line, "%06o %s %s\t",
-                               mode, type, sha1_to_hex(sha1));
+                               mode & ~NO_DELTA, type, sha1_to_hex(sha1));
                quote_c_style(path, &line, NULL, 0);
                strbuf_addch(&line, '\n');
        }
@@ -3338,10 +3379,10 @@ int main(int argc, const char **argv)
                fprintf(stderr, "---------------------------------------------------------------------\n");
                fprintf(stderr, "Alloc'd objects: %10" PRIuMAX "\n", alloc_count);
                fprintf(stderr, "Total objects:   %10" PRIuMAX " (%10" PRIuMAX " duplicates                  )\n", total_count, duplicate_count);
-               fprintf(stderr, "      blobs  :   %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB], delta_count_by_type[OBJ_BLOB]);
-               fprintf(stderr, "      trees  :   %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE], delta_count_by_type[OBJ_TREE]);
-               fprintf(stderr, "      commits:   %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT], delta_count_by_type[OBJ_COMMIT]);
-               fprintf(stderr, "      tags   :   %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG], delta_count_by_type[OBJ_TAG]);
+               fprintf(stderr, "      blobs  :   %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB], delta_count_by_type[OBJ_BLOB], delta_count_attempts_by_type[OBJ_BLOB]);
+               fprintf(stderr, "      trees  :   %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE], delta_count_by_type[OBJ_TREE], delta_count_attempts_by_type[OBJ_TREE]);
+               fprintf(stderr, "      commits:   %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT], delta_count_by_type[OBJ_COMMIT], delta_count_attempts_by_type[OBJ_COMMIT]);
+               fprintf(stderr, "      tags   :   %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG], delta_count_by_type[OBJ_TAG], delta_count_attempts_by_type[OBJ_TAG]);
                fprintf(stderr, "Total branches:  %10lu (%10lu loads     )\n", branch_count, branch_load_count);
                fprintf(stderr, "      marks:     %10" PRIuMAX " (%10" PRIuMAX " unique    )\n", (((uintmax_t)1) << marks->shift) * 1024, marks_set_count);
                fprintf(stderr, "      atoms:     %10u\n", atom_cnt);
diff --git a/fsck.c b/fsck.c
index 60bd4bbf6a2206e2b1991093ea5d84be7f338f73..6c855f84f01c19678399d85181da1094bd61b371 100644 (file)
--- a/fsck.c
+++ b/fsck.c
@@ -224,13 +224,15 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func)
 
 static int fsck_ident(char **ident, struct object *obj, fsck_error error_func)
 {
-       if (**ident == '<' || **ident == '\n')
-               return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before email");
-       *ident += strcspn(*ident, "<\n");
-       if ((*ident)[-1] != ' ')
+       if (**ident == '<')
                return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before email");
+       *ident += strcspn(*ident, "<>\n");
+       if (**ident == '>')
+               return error_func(obj, FSCK_ERROR, "invalid author/committer line - bad name");
        if (**ident != '<')
                return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing email");
+       if ((*ident)[-1] != ' ')
+               return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before email");
        (*ident)++;
        *ident += strcspn(*ident, "<>\n");
        if (**ident != '>')
index b2c4b2b853fd2f0f99e08b108efcc9ace7ca1f3e..9042432e23d7e43edafce28b6822a041028b57b7 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -90,11 +90,8 @@ safe_to_abort () {
        then
                return 0
        fi
-       (
-               gettext "You seem to have moved HEAD since the last 'am' failure.
-Not rewinding to ORIG_HEAD" &&
-               echo
-       ) >&2
+               gettextln "You seem to have moved HEAD since the last 'am' failure.
+Not rewinding to ORIG_HEAD" >&2
        return 1
 }
 
@@ -103,9 +100,9 @@ stop_here_user_resolve () {
            printf '%s\n' "$resolvemsg"
            stop_here $1
     fi
-    eval_gettext "When you have resolved this problem run \"\$cmdline --resolved\".
+    eval_gettextln "When you have resolved this problem run \"\$cmdline --resolved\".
 If you would prefer to skip this patch, instead run \"\$cmdline --skip\".
-To restore the original branch and stop patching run \"\$cmdline --abort\"."; echo
+To restore the original branch and stop patching run \"\$cmdline --abort\"."
 
     stop_here $1
 }
@@ -119,7 +116,7 @@ go_next () {
 
 cannot_fallback () {
        echo "$1"
-       gettext "Cannot fall back to three-way merge."; echo
+       gettextln "Cannot fall back to three-way merge."
        exit 1
 }
 
@@ -298,7 +295,7 @@ split_patches () {
                        perl -ne 'BEGIN { $subject = 0 }
                                if ($subject > 1) { print ; }
                                elsif (/^\s+$/) { next ; }
-                               elsif (/^Author:/) { print s/Author/From/ ; }
+                               elsif (/^Author:/) { s/Author/From/ ; print ;}
                                elsif (/^(From|Date)/) { print ; }
                                elsif ($subject) {
                                        $subject = 2 ;
@@ -314,8 +311,43 @@ split_patches () {
                this=
                msgnum=
                ;;
+       hg)
+               this=0
+               for hg in "$@"
+               do
+                       this=$(( $this + 1 ))
+                       msgnum=$(printf "%0${prec}d" $this)
+                       # hg stores changeset metadata in #-commented lines preceding
+                       # the commit message and diff(s). The only metadata we care about
+                       # are the User and Date (Node ID and Parent are hashes which are
+                       # only relevant to the hg repository and thus not useful to us)
+                       # Since we cannot guarantee that the commit message is in
+                       # git-friendly format, we put no Subject: line and just consume
+                       # all of the message as the body
+                       perl -M'POSIX qw(strftime)' -ne 'BEGIN { $subject = 0 }
+                               if ($subject) { print ; }
+                               elsif (/^\# User /) { s/\# User/From:/ ; print ; }
+                               elsif (/^\# Date /) {
+                                       my ($hashsign, $str, $time, $tz) = split ;
+                                       $tz = sprintf "%+05d", (0-$tz)/36;
+                                       print "Date: " .
+                                             strftime("%a, %d %b %Y %H:%M:%S ",
+                                                      localtime($time))
+                                             . "$tz\n";
+                               } elsif (/^\# /) { next ; }
+                               else {
+                                       print "\n", $_ ;
+                                       $subject = 1;
+                               }
+                       ' <"$hg" >"$dotest/$msgnum" || clean_abort
+               done
+               echo "$this" >"$dotest/last"
+               this=
+               msgnum=
+               ;;
        *)
-               if test -n "$parse_patch" ; then
+               if test -n "$patch_format"
+               then
                        clean_abort "$(eval_gettext "Patch format \$patch_format is not supported.")"
                else
                        clean_abort "$(gettext "Patch format detection failed.")"
@@ -619,9 +651,9 @@ do
                        go_next && continue
 
                test -s "$dotest/patch" || {
-                       eval_gettext "Patch is empty.  Was it split wrong?
+                       eval_gettextln "Patch is empty.  Was it split wrong?
 If you would prefer to skip this patch, instead run \"\$cmdline --skip\".
-To restore the original branch and stop patching run \"\$cmdline --abort\"."; echo
+To restore the original branch and stop patching run \"\$cmdline --abort\"."
                        stop_here $this
                }
                rm -f "$dotest/original-commit" "$dotest/author-script"
@@ -656,7 +688,7 @@ To restore the original branch and stop patching run \"\$cmdline --abort\"."; ec
 
        if test -z "$GIT_AUTHOR_EMAIL"
        then
-               gettext "Patch does not have a valid e-mail address."; echo
+               gettextln "Patch does not have a valid e-mail address."
                stop_here $this
        fi
 
@@ -707,7 +739,7 @@ To restore the original branch and stop patching run \"\$cmdline --abort\"."; ec
            action=again
            while test "$action" = again
            do
-               gettext "Commit Body is:"; echo
+               gettextln "Commit Body is:"
                echo "--------------------------"
                cat "$dotest/final-commit"
                echo "--------------------------"
@@ -771,16 +803,16 @@ To restore the original branch and stop patching run \"\$cmdline --abort\"."; ec
                # working tree.
                resolved=
                git diff-index --quiet --cached HEAD -- && {
-                       gettext "No changes - did you forget to use 'git add'?
+                       gettextln "No changes - did you forget to use 'git add'?
 If there is nothing left to stage, chances are that something else
-already introduced the same changes; you might want to skip this patch."; echo
+already introduced the same changes; you might want to skip this patch."
                        stop_here_user_resolve $this
                }
                unmerged=$(git ls-files -u)
                if test -n "$unmerged"
                then
-                       gettext "You still have unmerged paths in your index
-did you forget to use 'git add'?"; echo
+                       gettextln "You still have unmerged paths in your index
+did you forget to use 'git add'?"
                        stop_here_user_resolve $this
                fi
                apply_status=0
@@ -805,7 +837,7 @@ did you forget to use 'git add'?"; echo
        fi
        if test $apply_status != 0
        then
-               eval_gettext 'Patch failed at $msgnum $FIRSTLINE'; echo
+               eval_gettextln 'Patch failed at $msgnum $FIRSTLINE'
                stop_here_user_resolve $this
        fi
 
index e0ca3fb853083d0ebdfe11e5eb3fead4723248ad..2524060475ef369c0b9a4641aa88dc2ad6083da4 100755 (executable)
@@ -45,10 +45,7 @@ bisect_head()
 
 bisect_autostart() {
        test -s "$GIT_DIR/BISECT_START" || {
-               (
-                       gettext "You need to start by \"git bisect start\"" &&
-                       echo
-               ) >&2
+               gettextln "You need to start by \"git bisect start\"" >&2
                if test -t 0
                then
                        # TRANSLATORS: Make sure to include [Y] and [n] in your
@@ -272,10 +269,7 @@ bisect_next_check() {
        t,,good)
                # have bad but not good.  we could bisect although
                # this is less optimum.
-               (
-                       gettext "Warning: bisecting only with a bad commit." &&
-                       echo
-               ) >&2
+               gettextln "Warning: bisecting only with a bad commit." >&2
                if test -t 0
                then
                        # TRANSLATORS: Make sure to include [Y] and [n] in your
@@ -291,18 +285,12 @@ bisect_next_check() {
 
                if test -s "$GIT_DIR/BISECT_START"
                then
-                       (
-                               gettext "You need to give me at least one good and one bad revisions.
-(You can use \"git bisect bad\" and \"git bisect good\" for that.)" &&
-                               echo
-                       ) >&2
+                       gettextln "You need to give me at least one good and one bad revisions.
+(You can use \"git bisect bad\" and \"git bisect good\" for that.)" >&2
                else
-                       (
-                               gettext "You need to start by \"git bisect start\".
+                       gettextln "You need to start by \"git bisect start\".
 You then need to give me at least one good and one bad revisions.
-(You can use \"git bisect bad\" and \"git bisect good\" for that.)" &&
-                               echo
-                       ) >&2
+(You can use \"git bisect bad\" and \"git bisect good\" for that.)" >&2
                fi
                exit 1 ;;
        esac
@@ -355,7 +343,7 @@ bisect_visualize() {
 
 bisect_reset() {
        test -s "$GIT_DIR/BISECT_START" || {
-               gettext "We are not bisecting."; echo
+               gettextln "We are not bisecting."
                return
        }
        case "$#" in
@@ -428,18 +416,15 @@ bisect_run () {
        while true
        do
                command="$@"
-               eval_gettext "running \$command"; echo
+               eval_gettextln "running \$command"
                "$@"
                res=$?
 
                # Check for really bad run error.
                if [ $res -lt 0 -o $res -ge 128 ]
                then
-                       (
-                               eval_gettext "bisect run failed:
-exit code \$res from '\$command' is < 0 or >= 128" &&
-                               echo
-                       ) >&2
+                       eval_gettextln "bisect run failed:
+exit code \$res from '\$command' is < 0 or >= 128" >&2
                        exit $res
                fi
 
@@ -464,26 +449,20 @@ exit code \$res from '\$command' is < 0 or >= 128" &&
                if sane_grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
                        > /dev/null
                then
-                       (
-                               gettext "bisect run cannot continue any more" &&
-                               echo
-                       ) >&2
+                       gettextln "bisect run cannot continue any more" >&2
                        exit $res
                fi
 
                if [ $res -ne 0 ]
                then
-                       (
-                               eval_gettext "bisect run failed:
-'bisect_state \$state' exited with error code \$res" &&
-                               echo
-                       ) >&2
+                       eval_gettextln "bisect run failed:
+'bisect_state \$state' exited with error code \$res" >&2
                        exit $res
                fi
 
                if sane_grep "is the first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null
                then
-                       gettext "bisect run success"; echo
+                       gettextln "bisect run success"
                        exit 0;
                fi
 
index 0594bf7ca54c6b91d5f96dd86d8962e93459d004..8452890be974d30942f0acaa3f19282c1b8b25b2 100755 (executable)
@@ -13,7 +13,8 @@ TOOL_MODE=diff
 should_prompt () {
        prompt_merge=$(git config --bool mergetool.prompt || echo true)
        prompt=$(git config --bool difftool.prompt || echo $prompt_merge)
-       if test "$prompt" = true; then
+       if test "$prompt" = true
+       then
                test -z "$GIT_DIFFTOOL_NO_PROMPT"
        else
                test -n "$GIT_DIFFTOOL_PROMPT"
@@ -37,9 +38,11 @@ launch_merge_tool () {
 
        # $LOCAL and $REMOTE are temporary files so prompt
        # the user with the real $MERGED name before launching $merge_tool.
-       if should_prompt; then
+       if should_prompt
+       then
                printf "\nViewing: '$MERGED'\n"
-               if use_ext_cmd; then
+               if use_ext_cmd
+               then
                        printf "Hit return to launch '%s': " \
                                "$GIT_DIFFTOOL_EXTCMD"
                else
@@ -48,7 +51,8 @@ launch_merge_tool () {
                read ans
        fi
 
-       if use_ext_cmd; then
+       if use_ext_cmd
+       then
                export BASE
                eval $GIT_DIFFTOOL_EXTCMD '"$LOCAL"' '"$REMOTE"'
        else
@@ -56,8 +60,10 @@ launch_merge_tool () {
        fi
 }
 
-if ! use_ext_cmd; then
-       if test -n "$GIT_DIFF_TOOL"; then
+if ! use_ext_cmd
+then
+       if test -n "$GIT_DIFF_TOOL"
+       then
                merge_tool="$GIT_DIFF_TOOL"
        else
                merge_tool="$(get_merge_tool)" || exit
index 804a7f4bc912ab0c9c51038456d9abe4216947a5..add2c0247fa91e0f629428c295fc581f19cf85e1 100755 (executable)
@@ -108,9 +108,7 @@ OPTIONS_SPEC=
 . git-sh-setup
 
 if [ "$(is_bare_repository)" = false ]; then
-       git diff-files --ignore-submodules --quiet &&
-       git diff-index --cached --quiet HEAD -- ||
-       die "Cannot rewrite branch(es) with a dirty working directory."
+       require_clean_work_tree 'rewrite branches'
 fi
 
 tempdir=.git-rewrite
index 9a89e8f31981a0c82cf25ab6c75a6f841c7ba79b..ed630b208a80a36d729b5074a39bc53df0cabd59 100644 (file)
@@ -9,36 +9,19 @@ merge_mode() {
 }
 
 translate_merge_tool_path () {
-       case "$1" in
-       araxis)
-               echo compare
-               ;;
-       bc3)
-               echo bcompare
-               ;;
-       emerge)
-               echo emacs
-               ;;
-       gvimdiff|gvimdiff2)
-               echo gvim
-               ;;
-       vimdiff|vimdiff2)
-               echo vim
-               ;;
-       *)
-               echo "$1"
-               ;;
-       esac
+       echo "$1"
 }
 
 check_unchanged () {
-       if test "$MERGED" -nt "$BACKUP"; then
+       if test "$MERGED" -nt "$BACKUP"
+       then
                status=0
        else
-               while true; do
+               while true
+               do
                        echo "$MERGED seems unchanged."
                        printf "Was the merge successful? [y/n] "
-                       read answer
+                       read answer || return 1
                        case "$answer" in
                        y*|Y*) status=0; break ;;
                        n*|N*) status=1; break ;;
@@ -47,37 +30,57 @@ check_unchanged () {
        fi
 }
 
+valid_tool_config () {
+       if test -n "$(get_merge_tool_cmd "$1")"
+       then
+               return 0
+       else
+               return 1
+       fi
+}
+
 valid_tool () {
+       setup_tool "$1" || valid_tool_config "$1"
+}
+
+setup_tool () {
        case "$1" in
-       araxis | bc3 | diffuse | ecmerge | emerge | gvimdiff | gvimdiff2 | \
-       kdiff3 | meld | opendiff | p4merge | tkdiff | vimdiff | vimdiff2 | xxdiff)
-               ;; # happy
-       kompare)
-               if ! diff_mode; then
-                       return 1
-               fi
-               ;;
-       tortoisemerge)
-               if ! merge_mode; then
-                       return 1
-               fi
+       vim*|gvim*)
+               tool=vim
                ;;
        *)
-               if test -z "$(get_merge_tool_cmd "$1")"; then
-                       return 1
-               fi
+               tool="$1"
                ;;
        esac
+       mergetools="$(git --exec-path)/mergetools"
+
+       # Load the default definitions
+       . "$mergetools/defaults"
+       if ! test -f "$mergetools/$tool"
+       then
+               return 1
+       fi
+
+       # Load the redefined functions
+       . "$mergetools/$tool"
+
+       if merge_mode && ! can_merge
+       then
+               echo "error: '$tool' can not be used to resolve merges" >&2
+               exit 1
+       elif diff_mode && ! can_diff
+       then
+               echo "error: '$tool' can only be used to resolve merges" >&2
+               exit 1
+       fi
+       return 0
 }
 
 get_merge_tool_cmd () {
        # Prints the custom command for a merge tool
-       if test -n "$1"; then
-               merge_tool="$1"
-       else
-               merge_tool="$(get_merge_tool)"
-       fi
-       if diff_mode; then
+       merge_tool="$1"
+       if diff_mode
+       then
                echo "$(git config difftool.$merge_tool.cmd ||
                        git config mergetool.$merge_tool.cmd)"
        else
@@ -85,6 +88,7 @@ get_merge_tool_cmd () {
        fi
 }
 
+# Entry point for running tools
 run_merge_tool () {
        # If GIT_PREFIX is empty then we cannot use it in tools
        # that expect to be able to chdir() to its value.
@@ -95,271 +99,29 @@ run_merge_tool () {
        base_present="$2"
        status=0
 
-       case "$1" in
-       araxis)
-               if merge_mode; then
-                       touch "$BACKUP"
-                       if $base_present; then
-                               "$merge_tool_path" -wait -merge -3 -a1 \
-                                       "$BASE" "$LOCAL" "$REMOTE" "$MERGED" \
-                                       >/dev/null 2>&1
-                       else
-                               "$merge_tool_path" -wait -2 \
-                                       "$LOCAL" "$REMOTE" "$MERGED" \
-                                       >/dev/null 2>&1
-                       fi
-                       check_unchanged
-               else
-                       "$merge_tool_path" -wait -2 "$LOCAL" "$REMOTE" \
-                               >/dev/null 2>&1
-               fi
-               ;;
-       bc3)
-               if merge_mode; then
-                       touch "$BACKUP"
-                       if $base_present; then
-                               "$merge_tool_path" "$LOCAL" "$REMOTE" "$BASE" \
-                                       -mergeoutput="$MERGED"
-                       else
-                               "$merge_tool_path" "$LOCAL" "$REMOTE" \
-                                       -mergeoutput="$MERGED"
-                       fi
-                       check_unchanged
-               else
-                       "$merge_tool_path" "$LOCAL" "$REMOTE"
-               fi
-               ;;
-       diffuse)
-               if merge_mode; then
-                       touch "$BACKUP"
-                       if $base_present; then
-                               "$merge_tool_path" \
-                                       "$LOCAL" "$MERGED" "$REMOTE" \
-                                       "$BASE" | cat
-                       else
-                               "$merge_tool_path" \
-                                       "$LOCAL" "$MERGED" "$REMOTE" | cat
-                       fi
-                       check_unchanged
-               else
-                       "$merge_tool_path" "$LOCAL" "$REMOTE" | cat
-               fi
-               ;;
-       ecmerge)
-               if merge_mode; then
-                       touch "$BACKUP"
-                       if $base_present; then
-                               "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" \
-                                       --default --mode=merge3 --to="$MERGED"
-                       else
-                               "$merge_tool_path" "$LOCAL" "$REMOTE" \
-                                       --default --mode=merge2 --to="$MERGED"
-                       fi
-                       check_unchanged
-               else
-                       "$merge_tool_path" --default --mode=diff2 \
-                               "$LOCAL" "$REMOTE"
-               fi
-               ;;
-       emerge)
-               if merge_mode; then
-                       if $base_present; then
-                               "$merge_tool_path" \
-                                       -f emerge-files-with-ancestor-command \
-                                       "$LOCAL" "$REMOTE" "$BASE" \
-                                       "$(basename "$MERGED")"
-                       else
-                               "$merge_tool_path" \
-                                       -f emerge-files-command \
-                                       "$LOCAL" "$REMOTE" \
-                                       "$(basename "$MERGED")"
-                       fi
-                       status=$?
-               else
-                       "$merge_tool_path" -f emerge-files-command \
-                               "$LOCAL" "$REMOTE"
-               fi
-               ;;
-       gvimdiff|vimdiff)
-               if merge_mode; then
-                       touch "$BACKUP"
-                       if $base_present; then
-                               "$merge_tool_path" -f -d -c "wincmd J" \
-                                       "$MERGED" "$LOCAL" "$BASE" "$REMOTE"
-                       else
-                               "$merge_tool_path" -f -d -c "wincmd l" \
-                                       "$LOCAL" "$MERGED" "$REMOTE"
-                       fi
-                       check_unchanged
-               else
-                       "$merge_tool_path" -R -f -d -c "wincmd l" \
-                               -c 'cd $GIT_PREFIX' \
-                               "$LOCAL" "$REMOTE"
-               fi
-               ;;
-       gvimdiff2|vimdiff2)
-               if merge_mode; then
-                       touch "$BACKUP"
-                       "$merge_tool_path" -f -d -c "wincmd l" \
-                               "$LOCAL" "$MERGED" "$REMOTE"
-                       check_unchanged
-               else
-                       "$merge_tool_path" -R -f -d -c "wincmd l" \
-                               -c 'cd $GIT_PREFIX' \
-                               "$LOCAL" "$REMOTE"
-               fi
-               ;;
-       kdiff3)
-               if merge_mode; then
-                       if $base_present; then
-                               ("$merge_tool_path" --auto \
-                                       --L1 "$MERGED (Base)" \
-                                       --L2 "$MERGED (Local)" \
-                                       --L3 "$MERGED (Remote)" \
-                                       -o "$MERGED" \
-                                       "$BASE" "$LOCAL" "$REMOTE" \
-                               > /dev/null 2>&1)
-                       else
-                               ("$merge_tool_path" --auto \
-                                       --L1 "$MERGED (Local)" \
-                                       --L2 "$MERGED (Remote)" \
-                                       -o "$MERGED" \
-                                       "$LOCAL" "$REMOTE" \
-                               > /dev/null 2>&1)
-                       fi
-                       status=$?
-               else
-                       ("$merge_tool_path" --auto \
-                               --L1 "$MERGED (A)" \
-                               --L2 "$MERGED (B)" "$LOCAL" "$REMOTE" \
-                       > /dev/null 2>&1)
-               fi
-               ;;
-       kompare)
-               "$merge_tool_path" "$LOCAL" "$REMOTE"
-               ;;
-       meld)
-               if merge_mode; then
-                       touch "$BACKUP"
-                       "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"
-                       check_unchanged
-               else
-                       "$merge_tool_path" "$LOCAL" "$REMOTE"
-               fi
-               ;;
-       opendiff)
-               if merge_mode; then
-                       touch "$BACKUP"
-                       if $base_present; then
-                               "$merge_tool_path" "$LOCAL" "$REMOTE" \
-                                       -ancestor "$BASE" \
-                                       -merge "$MERGED" | cat
-                       else
-                               "$merge_tool_path" "$LOCAL" "$REMOTE" \
-                                       -merge "$MERGED" | cat
-                       fi
-                       check_unchanged
-               else
-                       "$merge_tool_path" "$LOCAL" "$REMOTE" | cat
-               fi
-               ;;
-       p4merge)
-               if merge_mode; then
-                       touch "$BACKUP"
-                       $base_present || >"$BASE"
-                       "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED"
-                       check_unchanged
-               else
-                       "$merge_tool_path" "$LOCAL" "$REMOTE"
-               fi
-               ;;
-       tkdiff)
-               if merge_mode; then
-                       if $base_present; then
-                               "$merge_tool_path" -a "$BASE" \
-                                       -o "$MERGED" "$LOCAL" "$REMOTE"
-                       else
-                               "$merge_tool_path" \
-                                       -o "$MERGED" "$LOCAL" "$REMOTE"
-                       fi
-                       status=$?
-               else
-                       "$merge_tool_path" "$LOCAL" "$REMOTE"
-               fi
-               ;;
-       tortoisemerge)
-               if $base_present; then
-                       touch "$BACKUP"
-                       "$merge_tool_path" \
-                               -base:"$BASE" -mine:"$LOCAL" \
-                               -theirs:"$REMOTE" -merged:"$MERGED"
-                       check_unchanged
-               else
-                       echo "TortoiseMerge cannot be used without a base" 1>&2
-                       status=1
-               fi
-               ;;
-       xxdiff)
-               if merge_mode; then
-                       touch "$BACKUP"
-                       if $base_present; then
-                               "$merge_tool_path" -X --show-merged-pane \
-                                       -R 'Accel.SaveAsMerged: "Ctrl-S"' \
-                                       -R 'Accel.Search: "Ctrl+F"' \
-                                       -R 'Accel.SearchForward: "Ctrl-G"' \
-                                       --merged-file "$MERGED" \
-                                       "$LOCAL" "$BASE" "$REMOTE"
-                       else
-                               "$merge_tool_path" -X $extra \
-                                       -R 'Accel.SaveAsMerged: "Ctrl-S"' \
-                                       -R 'Accel.Search: "Ctrl+F"' \
-                                       -R 'Accel.SearchForward: "Ctrl-G"' \
-                                       --merged-file "$MERGED" \
-                                       "$LOCAL" "$REMOTE"
-                       fi
-                       check_unchanged
-               else
-                       "$merge_tool_path" \
-                               -R 'Accel.Search: "Ctrl+F"' \
-                               -R 'Accel.SearchForward: "Ctrl-G"' \
-                               "$LOCAL" "$REMOTE"
-               fi
-               ;;
-       *)
-               merge_tool_cmd="$(get_merge_tool_cmd "$1")"
-               if test -z "$merge_tool_cmd"; then
-                       if merge_mode; then
-                               status=1
-                       fi
-                       break
-               fi
-               if merge_mode; then
-                       trust_exit_code="$(git config --bool \
-                               mergetool."$1".trustExitCode || echo false)"
-                       if test "$trust_exit_code" = "false"; then
-                               touch "$BACKUP"
-                               ( eval $merge_tool_cmd )
-                               check_unchanged
-                       else
-                               ( eval $merge_tool_cmd )
-                               status=$?
-                       fi
-               else
-                       ( eval $merge_tool_cmd )
-               fi
-               ;;
-       esac
+       # Bring tool-specific functions into scope
+       setup_tool "$1"
+
+       if merge_mode
+       then
+               merge_cmd "$1"
+       else
+               diff_cmd "$1"
+       fi
        return $status
 }
 
 guess_merge_tool () {
-       if merge_mode; then
+       if merge_mode
+       then
                tools="tortoisemerge"
        else
                tools="kompare"
        fi
-       if test -n "$DISPLAY"; then
-               if test -n "$GNOME_DESKTOP_SESSION_ID" ; then
+       if test -n "$DISPLAY"
+       then
+               if test -n "$GNOME_DESKTOP_SESSION_ID"
+               then
                        tools="meld opendiff kdiff3 tkdiff xxdiff $tools"
                else
                        tools="opendiff kdiff3 tkdiff xxdiff meld $tools"
@@ -380,7 +142,8 @@ guess_merge_tool () {
        for i in $tools
        do
                merge_tool_path="$(translate_merge_tool_path "$i")"
-               if type "$merge_tool_path" > /dev/null 2>&1; then
+               if type "$merge_tool_path" >/dev/null 2>&1
+               then
                        echo "$i"
                        return 0
                fi
@@ -393,12 +156,14 @@ guess_merge_tool () {
 get_configured_merge_tool () {
        # Diff mode first tries diff.tool and falls back to merge.tool.
        # Merge mode only checks merge.tool
-       if diff_mode; then
+       if diff_mode
+       then
                merge_tool=$(git config diff.tool || git config merge.tool)
        else
                merge_tool=$(git config merge.tool)
        fi
-       if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then
+       if test -n "$merge_tool" && ! valid_tool "$merge_tool"
+       then
                echo >&2 "git config option $TOOL_MODE.tool set to unknown tool: $merge_tool"
                echo >&2 "Resetting to default..."
                return 1
@@ -408,26 +173,26 @@ get_configured_merge_tool () {
 
 get_merge_tool_path () {
        # A merge tool has been set, so verify that it's valid.
-       if test -n "$1"; then
-               merge_tool="$1"
-       else
-               merge_tool="$(get_merge_tool)"
-       fi
-       if ! valid_tool "$merge_tool"; then
+       merge_tool="$1"
+       if ! valid_tool "$merge_tool"
+       then
                echo >&2 "Unknown merge tool $merge_tool"
                exit 1
        fi
-       if diff_mode; then
+       if diff_mode
+       then
                merge_tool_path=$(git config difftool."$merge_tool".path ||
                                  git config mergetool."$merge_tool".path)
        else
                merge_tool_path=$(git config mergetool."$merge_tool".path)
        fi
-       if test -z "$merge_tool_path"; then
+       if test -z "$merge_tool_path"
+       then
                merge_tool_path="$(translate_merge_tool_path "$merge_tool")"
        fi
        if test -z "$(get_merge_tool_cmd "$merge_tool")" &&
-       ! type "$merge_tool_path" > /dev/null 2>&1; then
+               ! type "$merge_tool_path" >/dev/null 2>&1
+       then
                echo >&2 "The $TOOL_MODE tool $merge_tool is not available as"\
                         "'$merge_tool_path'"
                exit 1
@@ -437,9 +202,10 @@ get_merge_tool_path () {
 
 get_merge_tool () {
        # Check if a merge tool has been configured
-       merge_tool=$(get_configured_merge_tool)
+       merge_tool="$(get_configured_merge_tool)"
        # Try to guess an appropriate merge tool if no tool has been set.
-       if test -z "$merge_tool"; then
+       if test -z "$merge_tool"
+       then
                merge_tool="$(guess_merge_tool)" || exit
        fi
        echo "$merge_tool"
index 3c157bcd26232c758572bf908add44216cb8e2e7..b6d463f0d057361ab5909209d24fc8fd383a3dbb 100755 (executable)
@@ -72,7 +72,7 @@ describe_file () {
 resolve_symlink_merge () {
     while true; do
        printf "Use (l)ocal or (r)emote, or (a)bort? "
-       read ans
+       read ans || return 1
        case "$ans" in
            [lL]*)
                git checkout-index -f --stage=2 -- "$MERGED"
index eec3a07f0fa45013e8d9fa9a3dd1a2fab98a2970..63da37bcc2730358140ae4b862040ed8fb0ed77e 100755 (executable)
@@ -217,12 +217,9 @@ then
        # $orig_head commit, but we are merging into $curr_head.
        # First update the working tree to match $curr_head.
 
-       (
-               eval_gettext "Warning: fetch updated the current branch head.
+       eval_gettextln "Warning: fetch updated the current branch head.
 Warning: fast-forwarding your working tree from
-Warning: commit \$orig_head." &&
-               echo
-       ) >&2
+Warning: commit \$orig_head." >&2
        git update-index -q --refresh
        git read-tree -u -m "$orig_head" "$curr_head" ||
                die "$(eval_gettext "Cannot fast-forward your working tree.
index c6ba7c15510935353bd17b2b8d8188fe16052907..94f36c254c53366ba53d256c0bd50a1de07cdb85 100644 (file)
@@ -472,18 +472,24 @@ do_next () {
                git rev-parse --verify HEAD > "$state_dir"/stopped-sha
                ${SHELL:-@SHELL_PATH@} -c "$rest" # Actual execution
                status=$?
+               # Run in subshell because require_clean_work_tree can die.
+               dirty=f
+               (require_clean_work_tree "rebase" 2>/dev/null) || dirty=t
                if test "$status" -ne 0
                then
                        warn "Execution failed: $rest"
+                       test "$dirty" = f ||
+                       warn "and made changes to the index and/or the working tree"
+
                        warn "You can fix the problem, and then run"
                        warn
                        warn "  git rebase --continue"
                        warn
                        exit "$status"
-               fi
-               # Run in subshell because require_clean_work_tree can die.
-               if ! (require_clean_work_tree "rebase")
+               elif test "$dirty" = t
                then
+                       warn "Execution succeeded: $rest"
+                       warn "but left changes to the index and/or the working tree"
                        warn "Commit or stash your changes, and then run"
                        warn
                        warn "  git rebase --continue"
@@ -647,8 +653,24 @@ continue)
        then
                : Nothing to commit -- skip this
        else
+               if ! test -f "$author_script"
+               then
+                       die "You have staged changes in your working tree. If these changes are meant to be
+squashed into the previous commit, run:
+
+  git commit --amend
+
+If they are meant to go into a new commit, run:
+
+  git commit
+
+In both case, once you're done, continue with:
+
+  git rebase --continue
+"
+               fi
                . "$author_script" ||
-                       die "Cannot find the author identity"
+                       die "Error trying to find the author identity to amend commit"
                current_head=
                if test -f "$amend"
                then
index e9c832bfd3da7db771cc2113027d3e590dc51d59..3dc4851cfc70a2804b23a8eca4dac7702ae93c87 100644 (file)
@@ -1,5 +1,18 @@
 #!/usr/bin/env python
 
+# This command is a simple remote-helper, that is used both as a
+# testcase for the remote-helper functionality, and as an example to
+# show remote-helper authors one possible implementation.
+#
+# This is a Git <-> Git importer/exporter, that simply uses git
+# fast-import and git fast-export to consume and produce fast-import
+# streams.
+#
+# To understand better the way things work, one can activate debug
+# traces by setting (to any value) the environment variables
+# GIT_TRANSPORT_HELPER_DEBUG and GIT_DEBUG_TESTGIT, to see messages
+# from the transport-helper side, or from this example remote-helper.
+
 # hashlib is only available in python >= 2.5
 try:
     import hashlib
index 32ca59de82b6a26879128650bf40882f4ed16aa0..e672366f0c3db3b547233af146cd1bba275c0042 100644 (file)
@@ -11,19 +11,38 @@ then
                printf "%s" "$1"
        }
 
+       gettextln() {
+               printf "%s\n" "$1"
+       }
+
        eval_gettext () {
                printf "%s" "$1" | (
                        export PATH $(git sh-i18n--envsubst --variables "$1");
                        git sh-i18n--envsubst "$1"
                )
        }
+
+       eval_gettextln () {
+               printf "%s\n" "$1" | (
+                       export PATH $(git sh-i18n--envsubst --variables "$1");
+                       git sh-i18n--envsubst "$1"
+               )
+       }
 else
        gettext () {
                printf "%s" "# GETTEXT POISON #"
        }
 
+       gettextln () {
+               printf "%s\n" "# GETTEXT POISON #"
+       }
+
        eval_gettext () {
                printf "%s" "# GETTEXT POISON #"
        }
+
+       eval_gettextln () {
+               printf "%s\n" "# GETTEXT POISON #"
+       }
 fi
 
index f4e6f05ee212f452a1b3e13c492960a9222029f7..c76669284ce48411b0cd580c5943e3eb0a785884 100755 (executable)
@@ -198,8 +198,8 @@ save_stash () {
                        #    $ git stash save --blah-blah 2>&1 | head -n 2
                        #    error: unknown option for 'stash save': --blah-blah
                        #           To provide a message, use git stash save -- '--blah-blah'
-                       eval_gettext "$("error: unknown option for 'stash save': \$option
-       To provide a message, use git stash save -- '\$option'")"; echo
+                       eval_gettextln "$("error: unknown option for 'stash save': \$option
+       To provide a message, use git stash save -- '\$option'")"
                        usage
                        ;;
                *)
@@ -211,7 +211,7 @@ save_stash () {
 
        if test -n "$patch_mode" && test -n "$untracked"
        then
-           die "Can't use --patch and ---include-untracked or --all at the same time"
+           die "Can't use --patch and --include-untracked or --all at the same time"
        fi
 
        stash_msg="$*"
@@ -240,7 +240,7 @@ save_stash () {
                test "$untracked" = "all" && CLEAN_X_OPTION=-x || CLEAN_X_OPTION=
                if test -n "$untracked"
                then
-                       git clean --force --quiet $CLEAN_X_OPTION
+                       git clean --force --quiet -d $CLEAN_X_OPTION
                fi
 
                if test "$keep_index" = "t" && test -n $i_tree
@@ -470,10 +470,7 @@ apply_stash () {
                status=$?
                if test -n "$INDEX_OPTION"
                then
-                       (
-                               gettext "Index was not unstashed." &&
-                               echo
-                       ) >&2
+                       gettextln "Index was not unstashed." >&2
                fi
                exit $status
        fi
index f46862f61b48a970f6ec24a9ae7624905ba3013a..814d0d914eaf994462e61559b0692c958f97513b 100755 (executable)
@@ -228,12 +228,9 @@ cmd_add()
 
        if test -z "$force" && ! git add --dry-run --ignore-missing "$path" > /dev/null 2>&1
        then
-               (
-                       eval_gettext "The following path is ignored by one of your .gitignore files:
+               eval_gettextln "The following path is ignored by one of your .gitignore files:
 \$path
-Use -f if you really want to add it." &&
-                       echo
-               ) >&2
+Use -f if you really want to add it." >&2
                exit 1
        fi
 
@@ -242,7 +239,7 @@ Use -f if you really want to add it." &&
        then
                if test -d "$path"/.git -o -f "$path"/.git
                then
-                       eval_gettext "Adding existing repo at '\$path' to the index"; echo
+                       eval_gettextln "Adding existing repo at '\$path' to the index"
                else
                        die "$(eval_gettext "'\$path' already exists and is not a valid git repo")"
                fi
@@ -701,10 +698,7 @@ cmd_summary() {
                                ;; # removed
                        *)
                                # unexpected type
-                               (
-                                       eval_gettext "unexpected mode \$mod_dst" &&
-                                       echo
-                               ) >&2
+                               eval_gettextln "unexpected mode \$mod_dst" >&2
                                continue ;;
                        esac
                fi
@@ -791,9 +785,9 @@ cmd_summary() {
        done |
        if test -n "$for_status"; then
                if [ -n "$files" ]; then
-                       gettext "# Submodules changed but not updated:"; echo
+                       gettextln "# Submodules changed but not updated:"
                else
-                       gettext "# Submodule changes to be committed:"; echo
+                       gettextln "# Submodule changes to be committed:"
                fi
                echo "#"
                sed -e 's|^|# |' -e 's|^# $|#|'
index 89f83fd27abe315804173a809b3c7ef00ead6527..351e743a902568c8b6f68ffaf4030f087ba8568d 100755 (executable)
@@ -89,6 +89,7 @@ my ($_stdin, $_help, $_edit,
        $_prefix, $_no_checkout, $_url, $_verbose,
        $_git_format, $_commit_url, $_tag, $_merge_info);
 $Git::SVN::_follow_parent = 1;
+$SVN::Git::Fetcher::_placeholder_filename = ".gitignore";
 $_q ||= 0;
 my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
                     'config-dir=s' => \$Git::SVN::Ra::config_dir,
@@ -139,6 +140,10 @@ my %cmd = (
                           %fc_opts } ],
        clone => [ \&cmd_clone, "Initialize and fetch revisions",
                        { 'revision|r=s' => \$_revision,
+                         'preserve-empty-dirs' =>
+                               \$SVN::Git::Fetcher::_preserve_empty_dirs,
+                         'placeholder-filename=s' =>
+                               \$SVN::Git::Fetcher::_placeholder_filename,
                           %fc_opts, %init_opts } ],
        init => [ \&cmd_init, "Initialize a repo for tracking" .
                          " (requires URL argument)",
@@ -386,6 +391,12 @@ sub do_git_init_db {
        my $ignore_regex = \$SVN::Git::Fetcher::_ignore_regex;
        command_noisy('config', "$pfx.ignore-paths", $$ignore_regex)
                if defined $$ignore_regex;
+
+       if (defined $SVN::Git::Fetcher::_preserve_empty_dirs) {
+               my $fname = \$SVN::Git::Fetcher::_placeholder_filename;
+               command_noisy('config', "$pfx.preserve-empty-dirs", 'true');
+               command_noisy('config', "$pfx.placeholder-filename", $$fname);
+       }
 }
 
 sub init_subdir {
@@ -497,6 +508,195 @@ sub cmd_set_tree {
        unlink $gs->{index};
 }
 
+sub split_merge_info_range {
+       my ($range) = @_;
+       if ($range =~ /(\d+)-(\d+)/) {
+               return (int($1), int($2));
+       } else {
+               return (int($range), int($range));
+       }
+}
+
+sub combine_ranges {
+       my ($in) = @_;
+
+       my @fnums = ();
+       my @arr = split(/,/, $in);
+       for my $element (@arr) {
+               my ($start, $end) = split_merge_info_range($element);
+               push @fnums, $start;
+       }
+
+       my @sorted = @arr [ sort {
+               $fnums[$a] <=> $fnums[$b]
+       } 0..$#arr ];
+
+       my @return = ();
+       my $last = -1;
+       my $first = -1;
+       for my $element (@sorted) {
+               my ($start, $end) = split_merge_info_range($element);
+
+               if ($last == -1) {
+                       $first = $start;
+                       $last = $end;
+                       next;
+               }
+               if ($start <= $last+1) {
+                       if ($end > $last) {
+                               $last = $end;
+                       }
+                       next;
+               }
+               if ($first == $last) {
+                       push @return, "$first";
+               } else {
+                       push @return, "$first-$last";
+               }
+               $first = $start;
+               $last = $end;
+       }
+
+       if ($first != -1) {
+               if ($first == $last) {
+                       push @return, "$first";
+               } else {
+                       push @return, "$first-$last";
+               }
+       }
+
+       return join(',', @return);
+}
+
+sub merge_revs_into_hash {
+       my ($hash, $minfo) = @_;
+       my @lines = split(' ', $minfo);
+
+       for my $line (@lines) {
+               my ($branchpath, $revs) = split(/:/, $line);
+
+               if (exists($hash->{$branchpath})) {
+                       # Merge the two revision sets
+                       my $combined = "$hash->{$branchpath},$revs";
+                       $hash->{$branchpath} = combine_ranges($combined);
+               } else {
+                       # Just do range combining for consolidation
+                       $hash->{$branchpath} = combine_ranges($revs);
+               }
+       }
+}
+
+sub merge_merge_info {
+       my ($mergeinfo_one, $mergeinfo_two) = @_;
+       my %result_hash = ();
+
+       merge_revs_into_hash(\%result_hash, $mergeinfo_one);
+       merge_revs_into_hash(\%result_hash, $mergeinfo_two);
+
+       my $result = '';
+       # Sort below is for consistency's sake
+       for my $branchname (sort keys(%result_hash)) {
+               my $revlist = $result_hash{$branchname};
+               $result .= "$branchname:$revlist\n"
+       }
+       return $result;
+}
+
+sub populate_merge_info {
+       my ($d, $gs, $uuid, $linear_refs, $rewritten_parent) = @_;
+
+       my %parentshash;
+       read_commit_parents(\%parentshash, $d);
+       my @parents = @{$parentshash{$d}};
+       if ($#parents > 0) {
+               # Merge commit
+               my $all_parents_ok = 1;
+               my $aggregate_mergeinfo = '';
+               my $rooturl = $gs->repos_root;
+
+               if (defined($rewritten_parent)) {
+                       # Replace first parent with newly-rewritten version
+                       shift @parents;
+                       unshift @parents, $rewritten_parent;
+               }
+
+               foreach my $parent (@parents) {
+                       my ($branchurl, $svnrev, $paruuid) =
+                               cmt_metadata($parent);
+
+                       unless (defined($svnrev)) {
+                               # Should have been caught be preflight check
+                               fatal "merge commit $d has ancestor $parent, but that change "
+                     ."does not have git-svn metadata!";
+                       }
+                       unless ($branchurl =~ /^$rooturl(.*)/) {
+                               fatal "commit $parent git-svn metadata changed mid-run!";
+                       }
+                       my $branchpath = $1;
+
+                       my $ra = Git::SVN::Ra->new($branchurl);
+                       my (undef, undef, $props) =
+                               $ra->get_dir(canonicalize_path("."), $svnrev);
+                       my $par_mergeinfo = $props->{'svn:mergeinfo'};
+                       unless (defined $par_mergeinfo) {
+                               $par_mergeinfo = '';
+                       }
+                       # Merge previous mergeinfo values
+                       $aggregate_mergeinfo =
+                               merge_merge_info($aggregate_mergeinfo,
+                                                                $par_mergeinfo, 0);
+
+                       next if $parent eq $parents[0]; # Skip first parent
+                       # Add new changes being placed in tree by merge
+                       my @cmd = (qw/rev-list --reverse/,
+                                          $parent, qw/--not/);
+                       foreach my $par (@parents) {
+                               unless ($par eq $parent) {
+                                       push @cmd, $par;
+                               }
+                       }
+                       my @revsin = ();
+                       my ($revlist, $ctx) = command_output_pipe(@cmd);
+                       while (<$revlist>) {
+                               my $irev = $_;
+                               chomp $irev;
+                               my (undef, $csvnrev, undef) =
+                                       cmt_metadata($irev);
+                               unless (defined $csvnrev) {
+                                       # A child is missing SVN annotations...
+                                       # this might be OK, or might not be.
+                                       warn "W:child $irev is merged into revision "
+                                                ."$d but does not have git-svn metadata. "
+                                                ."This means git-svn cannot determine the "
+                                                ."svn revision numbers to place into the "
+                                                ."svn:mergeinfo property. You must ensure "
+                                                ."a branch is entirely committed to "
+                                                ."SVN before merging it in order for "
+                                                ."svn:mergeinfo population to function "
+                                                ."properly";
+                               }
+                               push @revsin, $csvnrev;
+                       }
+                       command_close_pipe($revlist, $ctx);
+
+                       last unless $all_parents_ok;
+
+                       # We now have a list of all SVN revnos which are
+                       # merged by this particular parent. Integrate them.
+                       next if $#revsin == -1;
+                       my $newmergeinfo = "$branchpath:" . join(',', @revsin);
+                       $aggregate_mergeinfo =
+                               merge_merge_info($aggregate_mergeinfo,
+                                                                $newmergeinfo, 1);
+               }
+               if ($all_parents_ok and $aggregate_mergeinfo) {
+                       return $aggregate_mergeinfo;
+               }
+       }
+
+       return undef;
+}
+
 sub cmd_dcommit {
        my $head = shift;
        command_noisy(qw/update-index --refresh/);
@@ -547,7 +747,66 @@ sub cmd_dcommit {
                     "without --no-rebase may be required."
        }
        my $expect_url = $url;
+
+       my $push_merge_info = eval {
+               command_oneline(qw/config --get svn.pushmergeinfo/)
+               };
+       if (not defined($push_merge_info)
+                       or $push_merge_info eq "false"
+                       or $push_merge_info eq "no"
+                       or $push_merge_info eq "never") {
+               $push_merge_info = 0;
+       }
+
+       unless (defined($_merge_info) || ! $push_merge_info) {
+               # Preflight check of changes to ensure no issues with mergeinfo
+               # This includes check for uncommitted-to-SVN parents
+               # (other than the first parent, which we will handle),
+               # information from different SVN repos, and paths
+               # which are not underneath this repository root.
+               my $rooturl = $gs->repos_root;
+               foreach my $d (@$linear_refs) {
+                       my %parentshash;
+                       read_commit_parents(\%parentshash, $d);
+                       my @realparents = @{$parentshash{$d}};
+                       if ($#realparents > 0) {
+                               # Merge commit
+                               shift @realparents; # Remove/ignore first parent
+                               foreach my $parent (@realparents) {
+                                       my ($branchurl, $svnrev, $paruuid) = cmt_metadata($parent);
+                                       unless (defined $paruuid) {
+                                               # A parent is missing SVN annotations...
+                                               # abort the whole operation.
+                                               fatal "$parent is merged into revision $d, "
+                                                        ."but does not have git-svn metadata. "
+                                                        ."Either dcommit the branch or use a "
+                                                        ."local cherry-pick, FF merge, or rebase "
+                                                        ."instead of an explicit merge commit.";
+                                       }
+
+                                       unless ($paruuid eq $uuid) {
+                                               # Parent has SVN metadata from different repository
+                                               fatal "merge parent $parent for change $d has "
+                                                        ."git-svn uuid $paruuid, while current change "
+                                                        ."has uuid $uuid!";
+                                       }
+
+                                       unless ($branchurl =~ /^$rooturl(.*)/) {
+                                               # This branch is very strange indeed.
+                                               fatal "merge parent $parent for $d is on branch "
+                                                        ."$branchurl, which is not under the "
+                                                        ."git-svn root $rooturl!";
+                                       }
+                               }
+                       }
+               }
+       }
+
+       my $rewritten_parent;
        Git::SVN::remove_username($expect_url);
+       if (defined($_merge_info)) {
+               $_merge_info =~ tr{ }{\n};
+       }
        while (1) {
                my $d = shift @$linear_refs or last;
                unless (defined $last_rev) {
@@ -561,6 +820,14 @@ sub cmd_dcommit {
                        print "diff-tree $d~1 $d\n";
                } else {
                        my $cmt_rev;
+
+                       unless (defined($_merge_info) || ! $push_merge_info) {
+                               $_merge_info = populate_merge_info($d, $gs,
+                                                            $uuid,
+                                                            $linear_refs,
+                                                            $rewritten_parent);
+                       }
+
                        my %ed_opts = ( r => $last_rev,
                                        log => get_commit_entry($d)->{log},
                                        ra => Git::SVN::Ra->new($url),
@@ -603,6 +870,9 @@ sub cmd_dcommit {
                                @finish = qw/reset --mixed/;
                        }
                        command_noisy(@finish, $gs->refname);
+
+                       $rewritten_parent = command_oneline(qw/rev-parse HEAD/);
+
                        if (@diff) {
                                @refs = ();
                                my ($url_, $rev_, $uuid_, $gs_) =
@@ -3011,7 +3281,7 @@ sub other_gs {
                        my (undef, $max_commit) = $gs->rev_map_max(1);
                        last if (!$max_commit);
                        my ($url) = ::cmt_metadata($max_commit);
-                       last if ($url eq $gs->full_url);
+                       last if ($url eq $gs->metadata_url);
                        $ref_id .= '-';
                }
                print STDERR "Initializing parent: $ref_id\n" unless $::_q > 1;
@@ -4080,12 +4350,13 @@ sub _read_password {
 }
 
 package SVN::Git::Fetcher;
-use vars qw/@ISA/;
+use vars qw/@ISA $_ignore_regex $_preserve_empty_dirs $_placeholder_filename
+            @deleted_gpath %added_placeholder $repo_id/;
 use strict;
 use warnings;
 use Carp qw/croak/;
+use File::Basename qw/dirname/;
 use IO::File qw//;
-use vars qw/$_ignore_regex/;
 
 # file baton members: path, mode_a, mode_b, pool, fh, blob, base
 sub new {
@@ -4097,8 +4368,34 @@ sub new {
                $self->{empty_symlinks} =
                                  _mark_empty_symlinks($git_svn, $switch_path);
        }
-       $self->{ignore_regex} = eval { command_oneline('config', '--get',
-                            "svn-remote.$git_svn->{repo_id}.ignore-paths") };
+
+       # some options are read globally, but can be overridden locally
+       # per [svn-remote "..."] section.  Command-line options will *NOT*
+       # override options set in an [svn-remote "..."] section
+       $repo_id = $git_svn->{repo_id};
+       my $k = "svn-remote.$repo_id.ignore-paths";
+       my $v = eval { command_oneline('config', '--get', $k) };
+       $self->{ignore_regex} = $v;
+
+       $k = "svn-remote.$repo_id.preserve-empty-dirs";
+       $v = eval { command_oneline('config', '--get', '--bool', $k) };
+       if ($v && $v eq 'true') {
+               $_preserve_empty_dirs = 1;
+               $k = "svn-remote.$repo_id.placeholder-filename";
+               $v = eval { command_oneline('config', '--get', $k) };
+               $_placeholder_filename = $v;
+       }
+
+       # Load the list of placeholder files added during previous invocations.
+       $k = "svn-remote.$repo_id.added-placeholder";
+       $v = eval { command_oneline('config', '--get-all', $k) };
+       if ($_preserve_empty_dirs && $v) {
+               # command() prints errors to stderr, so we only call it if
+               # command_oneline() succeeded.
+               my @v = command('config', '--get-all', $k);
+               $added_placeholder{ dirname($_) } = $_ foreach @v;
+       }
+
        $self->{empty} = {};
        $self->{dir_prop} = {};
        $self->{file_prop} = {};
@@ -4227,6 +4524,8 @@ sub delete_entry {
                $self->{gii}->remove($gpath);
                print "\tD\t$gpath\n" unless $::_q;
        }
+       # Don't add to @deleted_gpath if we're deleting a placeholder file.
+       push @deleted_gpath, $gpath unless $added_placeholder{dirname($path)};
        $self->{empty}->{$path} = 0;
        undef;
 }
@@ -4259,7 +4558,15 @@ sub add_file {
                my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
                delete $self->{empty}->{$dir};
                $mode = '100644';
+
+               if ($added_placeholder{$dir}) {
+                       # Remove our placeholder file, if we created one.
+                       delete_entry($self, $added_placeholder{$dir})
+                               unless $path eq $added_placeholder{$dir};
+                       delete $added_placeholder{$dir}
+               }
        }
+
        { path => $path, mode_a => $mode, mode_b => $mode,
          pool => SVN::Pool->new, action => 'A' };
 }
@@ -4277,6 +4584,7 @@ sub add_directory {
                        chomp;
                        $self->{gii}->remove($_);
                        print "\tD\t$_\n" unless $::_q;
+                       push @deleted_gpath, $gpath;
                }
                command_close_pipe($ls, $ctx);
                $self->{empty}->{$path} = 0;
@@ -4284,6 +4592,13 @@ sub add_directory {
        my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
        delete $self->{empty}->{$dir};
        $self->{empty}->{$path} = 1;
+
+       if ($added_placeholder{$dir}) {
+               # Remove our placeholder file, if we created one.
+               delete_entry($self, $added_placeholder{$dir});
+               delete $added_placeholder{$dir}
+       }
+
 out:
        { path => $path };
 }
@@ -4447,12 +4762,96 @@ sub abort_edit {
 
 sub close_edit {
        my $self = shift;
+
+       if ($_preserve_empty_dirs) {
+               my @empty_dirs;
+
+               # Any entry flagged as empty that also has an associated
+               # dir_prop represents a newly created empty directory.
+               foreach my $i (keys %{$self->{empty}}) {
+                       push @empty_dirs, $i if exists $self->{dir_prop}->{$i};
+               }
+
+               # Search for directories that have become empty due subsequent
+               # file deletes.
+               push @empty_dirs, $self->find_empty_directories();
+
+               # Finally, add a placeholder file to each empty directory.
+               $self->add_placeholder_file($_) foreach (@empty_dirs);
+
+               $self->stash_placeholder_list();
+       }
+
        $self->{git_commit_ok} = 1;
        $self->{nr} = $self->{gii}->{nr};
        delete $self->{gii};
        $self->SUPER::close_edit(@_);
 }
 
+sub find_empty_directories {
+       my ($self) = @_;
+       my @empty_dirs;
+       my %dirs = map { dirname($_) => 1 } @deleted_gpath;
+
+       foreach my $dir (sort keys %dirs) {
+               next if $dir eq ".";
+
+               # If there have been any additions to this directory, there is
+               # no reason to check if it is empty.
+               my $skip_added = 0;
+               foreach my $t (qw/dir_prop file_prop/) {
+                       foreach my $path (keys %{ $self->{$t} }) {
+                               if (exists $self->{$t}->{dirname($path)}) {
+                                       $skip_added = 1;
+                                       last;
+                               }
+                       }
+                       last if $skip_added;
+               }
+               next if $skip_added;
+
+               # Use `git ls-tree` to get the filenames of this directory
+               # that existed prior to this particular commit.
+               my $ls = command('ls-tree', '-z', '--name-only',
+                                $self->{c}, "$dir/");
+               my %files = map { $_ => 1 } split(/\0/, $ls);
+
+               # Remove the filenames that were deleted during this commit.
+               delete $files{$_} foreach (@deleted_gpath);
+
+               # Report the directory if there are no filenames left.
+               push @empty_dirs, $dir unless (scalar %files);
+       }
+       @empty_dirs;
+}
+
+sub add_placeholder_file {
+       my ($self, $dir) = @_;
+       my $path = "$dir/$_placeholder_filename";
+       my $gpath = $self->git_path($path);
+
+       my $fh = $::_repository->temp_acquire($gpath);
+       my $hash = $::_repository->hash_and_insert_object(Git::temp_path($fh));
+       Git::temp_release($fh, 1);
+       $self->{gii}->update('100644', $hash, $gpath) or croak $!;
+
+       # The directory should no longer be considered empty.
+       delete $self->{empty}->{$dir} if exists $self->{empty}->{$dir};
+
+       # Keep track of any placeholder files we create.
+       $added_placeholder{$dir} = $path;
+}
+
+sub stash_placeholder_list {
+       my ($self) = @_;
+       my $k = "svn-remote.$repo_id.added-placeholder";
+       my $v = eval { command_oneline('config', '--get-all', $k) };
+       command_noisy('config', '--unset-all', $k) if $v;
+       foreach (values %added_placeholder) {
+               command_noisy('config', '--add', $k, $_);
+       }
+}
+
 package SVN::Git::Editor;
 use vars qw/@ISA $_rmdir $_cp_similarity $_find_copies_harder $_rename_limit/;
 use strict;
diff --git a/git.c b/git.c
index b660e36660a1fa836a7d0f71a6443bb0b530d644..8e34903a65c8775b19b993d3e9ddf47c23d5254e 100644 (file)
--- a/git.c
+++ b/git.c
@@ -473,6 +473,8 @@ static void execv_dashed_external(const char **argv)
        const char *tmp;
        int status;
 
+       if (use_pager == -1)
+               use_pager = check_pager_config(argv[0]);
        commit_pager_choice();
 
        strbuf_addf(&cmd, "git-%s", argv[0]);
diff --git a/graph.c b/graph.c
index 2f6893dc4f97dfbccad6f22f66861c24021a3c46..7358416a72e855b406e026036cf61bcdd15e5142 100644 (file)
--- a/graph.c
+++ b/graph.c
@@ -347,7 +347,7 @@ static struct commit_list *first_interesting_parent(struct git_graph *graph)
 
 static unsigned short graph_get_current_column_color(const struct git_graph *graph)
 {
-       if (!DIFF_OPT_TST(&graph->revs->diffopt, COLOR_DIFF))
+       if (!want_color(graph->revs->diffopt.use_color))
                return column_colors_max;
        return graph->default_column_color;
 }
diff --git a/grep.c b/grep.c
index 6618cd8b92fc1948304a73e7e99d74ca0035d51e..b29d09c7f6a5a7f9621fb5287eb07e72ece7f484 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -466,7 +466,7 @@ static int word_char(char ch)
 static void output_color(struct grep_opt *opt, const void *data, size_t size,
                         const char *color)
 {
-       if (opt->color && color && color[0]) {
+       if (want_color(opt->color) && color && color[0]) {
                opt->output(opt, color, strlen(color));
                opt->output(opt, data, size);
                opt->output(opt, GIT_COLOR_RESET, strlen(GIT_COLOR_RESET));
index 3af4c71bd0fb8370c6f08b0e3d43adc722cac2f4..8c4c5d2224a2493a648e6a34257bc150f2712dd0 100644 (file)
@@ -56,6 +56,10 @@ int main(int argc, const char **argv)
                commits = 1;
        }
 
+       if (get_all == 0)
+               warning("http-fetch: use without -a is deprecated.\n"
+                       "In a future release, -a will become the default.");
+
        if (argv[arg])
                str_end_url_with_slash(argv[arg], &url);
 
index 6e8f6d09abb6397f1782fa32d9d273fc4fc740fe..376331a76fcde24e6828eba0b88fec26d93fe56f 100644 (file)
@@ -1655,7 +1655,7 @@ static int delete_remote_branch(const char *pattern, int force)
                return error("Remote HEAD is not a symref");
 
        /* Remote branch must not be the remote HEAD */
-       for (i=0; symref && i<MAXDEPTH; i++) {
+       for (i = 0; symref && i < MAXDEPTH; i++) {
                if (!strcmp(remote_ref->name, symref))
                        return error("Remote branch %s is the current HEAD",
                                     remote_ref->name);
index 0fb44e7ed7e8d7a98b8609778191715148f56c8d..39d80c01753527e45716ec762beeb891dfc7d28d 100644 (file)
@@ -12,7 +12,8 @@ static void process_blob(struct rev_info *revs,
                         struct blob *blob,
                         show_object_fn show,
                         struct name_path *path,
-                        const char *name)
+                        const char *name,
+                        void *cb_data)
 {
        struct object *obj = &blob->object;
 
@@ -23,7 +24,7 @@ static void process_blob(struct rev_info *revs,
        if (obj->flags & (UNINTERESTING | SEEN))
                return;
        obj->flags |= SEEN;
-       show(obj, path, name);
+       show(obj, path, name, cb_data);
 }
 
 /*
@@ -52,7 +53,8 @@ static void process_gitlink(struct rev_info *revs,
                            const unsigned char *sha1,
                            show_object_fn show,
                            struct name_path *path,
-                           const char *name)
+                           const char *name,
+                           void *cb_data)
 {
        /* Nothing to do */
 }
@@ -62,7 +64,8 @@ static void process_tree(struct rev_info *revs,
                         show_object_fn show,
                         struct name_path *path,
                         struct strbuf *base,
-                        const char *name)
+                        const char *name,
+                        void *cb_data)
 {
        struct object *obj = &tree->object;
        struct tree_desc desc;
@@ -80,7 +83,7 @@ static void process_tree(struct rev_info *revs,
        if (parse_tree(tree) < 0)
                die("bad tree object %s", sha1_to_hex(obj->sha1));
        obj->flags |= SEEN;
-       show(obj, path, name);
+       show(obj, path, name, cb_data);
        me.up = path;
        me.elem = name;
        me.elem_len = strlen(name);
@@ -106,14 +109,17 @@ static void process_tree(struct rev_info *revs,
                if (S_ISDIR(entry.mode))
                        process_tree(revs,
                                     lookup_tree(entry.sha1),
-                                    show, &me, base, entry.path);
+                                    show, &me, base, entry.path,
+                                    cb_data);
                else if (S_ISGITLINK(entry.mode))
                        process_gitlink(revs, entry.sha1,
-                                       show, &me, entry.path);
+                                       show, &me, entry.path,
+                                       cb_data);
                else
                        process_blob(revs,
                                     lookup_blob(entry.sha1),
-                                    show, &me, entry.path);
+                                    show, &me, entry.path,
+                                    cb_data);
        }
        strbuf_setlen(base, baselen);
        free(tree->buffer);
@@ -185,17 +191,17 @@ void traverse_commit_list(struct rev_info *revs,
                        continue;
                if (obj->type == OBJ_TAG) {
                        obj->flags |= SEEN;
-                       show_object(obj, NULL, name);
+                       show_object(obj, NULL, name, data);
                        continue;
                }
                if (obj->type == OBJ_TREE) {
                        process_tree(revs, (struct tree *)obj, show_object,
-                                    NULL, &base, name);
+                                    NULL, &base, name, data);
                        continue;
                }
                if (obj->type == OBJ_BLOB) {
                        process_blob(revs, (struct blob *)obj, show_object,
-                                    NULL, name);
+                                    NULL, name, data);
                        continue;
                }
                die("unknown pending object %s (%s)",
index d65dbf03e657facb29a2846144eda2fa3687bc2f..3db7bb6fa386df2ccb73b78ee72881f32074a1b8 100644 (file)
@@ -2,11 +2,10 @@
 #define LIST_OBJECTS_H
 
 typedef void (*show_commit_fn)(struct commit *, void *);
-typedef void (*show_object_fn)(struct object *, const struct name_path *, const char *);
-typedef void (*show_edge_fn)(struct commit *);
-
+typedef void (*show_object_fn)(struct object *, const struct name_path *, const char *, void *);
 void traverse_commit_list(struct rev_info *, show_commit_fn, show_object_fn, void *);
 
+typedef void (*show_edge_fn)(struct commit *);
 void mark_edges_uninteresting(struct commit_list *, struct rev_info *, show_edge_fn);
 
 #endif
index e9457019d5ac7aff1e185e195f37b851732f6210..24c295ea1dc91180b9685299f48812593162bdfe 100644 (file)
@@ -18,6 +18,7 @@ enum decoration_type {
        DECORATION_REF_TAG,
        DECORATION_REF_STASH,
        DECORATION_REF_HEAD,
+       DECORATION_GRAFTED,
 };
 
 static char decoration_colors[][COLOR_MAXLEN] = {
@@ -27,11 +28,12 @@ static char decoration_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_BOLD_YELLOW,  /* REF_TAG */
        GIT_COLOR_BOLD_MAGENTA, /* REF_STASH */
        GIT_COLOR_BOLD_CYAN,    /* REF_HEAD */
+       GIT_COLOR_BOLD_BLUE,    /* GRAFTED */
 };
 
 static const char *decorate_get_color(int decorate_use_color, enum decoration_type ix)
 {
-       if (decorate_use_color)
+       if (want_color(decorate_use_color))
                return decoration_colors[ix];
        return "";
 }
@@ -77,7 +79,7 @@ int parse_decorate_color_config(const char *var, const int ofs, const char *valu
  * for showing the commit sha1, use the same check for --decorate
  */
 #define decorate_get_color_opt(o, ix) \
-       decorate_get_color(DIFF_OPT_TST((o), COLOR_DIFF), ix)
+       decorate_get_color((o)->use_color, ix)
 
 static void add_name_decoration(enum decoration_type type, const char *name, struct object *obj)
 {
@@ -90,16 +92,32 @@ static void add_name_decoration(enum decoration_type type, const char *name, str
 
 static int add_ref_decoration(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
 {
-       struct object *obj = parse_object(sha1);
+       struct object *obj;
        enum decoration_type type = DECORATION_NONE;
+
+       if (!prefixcmp(refname, "refs/replace/")) {
+               unsigned char original_sha1[20];
+               if (!read_replace_refs)
+                       return 0;
+               if (get_sha1_hex(refname + 13, original_sha1)) {
+                       warning("invalid replace ref %s", refname);
+                       return 0;
+               }
+               obj = parse_object(original_sha1);
+               if (obj)
+                       add_name_decoration(DECORATION_GRAFTED, "replaced", obj);
+               return 0;
+       }
+
+       obj = parse_object(sha1);
        if (!obj)
                return 0;
 
-       if (!prefixcmp(refname, "refs/heads"))
+       if (!prefixcmp(refname, "refs/heads/"))
                type = DECORATION_REF_LOCAL;
-       else if (!prefixcmp(refname, "refs/remotes"))
+       else if (!prefixcmp(refname, "refs/remotes/"))
                type = DECORATION_REF_REMOTE;
-       else if (!prefixcmp(refname, "refs/tags"))
+       else if (!prefixcmp(refname, "refs/tags/"))
                type = DECORATION_REF_TAG;
        else if (!prefixcmp(refname, "refs/stash"))
                type = DECORATION_REF_STASH;
@@ -118,6 +136,15 @@ static int add_ref_decoration(const char *refname, const unsigned char *sha1, in
        return 0;
 }
 
+static int add_graft_decoration(const struct commit_graft *graft, void *cb_data)
+{
+       struct commit *commit = lookup_commit(graft->sha1);
+       if (!commit)
+               return 0;
+       add_name_decoration(DECORATION_GRAFTED, "grafted", &commit->object);
+       return 0;
+}
+
 void load_ref_decorations(int flags)
 {
        static int loaded;
@@ -125,6 +152,7 @@ void load_ref_decorations(int flags)
                loaded = 1;
                for_each_ref(add_ref_decoration, &flags);
                head_ref(add_ref_decoration, &flags);
+               for_each_commit_graft(add_graft_decoration, NULL);
        }
 }
 
index 0cc1e6fc1498e9fc51c9e382aee6a20e439a4ef0..3efc04e04fd754de85fbab1dbafa7bffb2fa2854 100644 (file)
@@ -66,10 +66,12 @@ static int sha_eq(const unsigned char *a, const unsigned char *b)
 enum rename_type {
        RENAME_NORMAL = 0,
        RENAME_DELETE,
-       RENAME_ONE_FILE_TO_TWO
+       RENAME_ONE_FILE_TO_ONE,
+       RENAME_ONE_FILE_TO_TWO,
+       RENAME_TWO_FILES_TO_ONE
 };
 
-struct rename_df_conflict_info {
+struct rename_conflict_info {
        enum rename_type rename_type;
        struct diff_filepair *pair1;
        struct diff_filepair *pair2;
@@ -77,6 +79,8 @@ struct rename_df_conflict_info {
        const char *branch2;
        struct stage_data *dst_entry1;
        struct stage_data *dst_entry2;
+       struct diff_filespec ren1_other;
+       struct diff_filespec ren2_other;
 };
 
 /*
@@ -88,34 +92,54 @@ struct stage_data {
                unsigned mode;
                unsigned char sha[20];
        } stages[4];
-       struct rename_df_conflict_info *rename_df_conflict_info;
+       struct rename_conflict_info *rename_conflict_info;
        unsigned processed:1;
 };
 
-static inline void setup_rename_df_conflict_info(enum rename_type rename_type,
-                                                struct diff_filepair *pair1,
-                                                struct diff_filepair *pair2,
-                                                const char *branch1,
-                                                const char *branch2,
-                                                struct stage_data *dst_entry1,
-                                                struct stage_data *dst_entry2)
+static inline void setup_rename_conflict_info(enum rename_type rename_type,
+                                             struct diff_filepair *pair1,
+                                             struct diff_filepair *pair2,
+                                             const char *branch1,
+                                             const char *branch2,
+                                             struct stage_data *dst_entry1,
+                                             struct stage_data *dst_entry2,
+                                             struct merge_options *o,
+                                             struct stage_data *src_entry1,
+                                             struct stage_data *src_entry2)
 {
-       struct rename_df_conflict_info *ci = xcalloc(1, sizeof(struct rename_df_conflict_info));
+       struct rename_conflict_info *ci = xcalloc(1, sizeof(struct rename_conflict_info));
        ci->rename_type = rename_type;
        ci->pair1 = pair1;
        ci->branch1 = branch1;
        ci->branch2 = branch2;
 
        ci->dst_entry1 = dst_entry1;
-       dst_entry1->rename_df_conflict_info = ci;
+       dst_entry1->rename_conflict_info = ci;
        dst_entry1->processed = 0;
 
        assert(!pair2 == !dst_entry2);
        if (dst_entry2) {
                ci->dst_entry2 = dst_entry2;
                ci->pair2 = pair2;
-               dst_entry2->rename_df_conflict_info = ci;
-               dst_entry2->processed = 0;
+               dst_entry2->rename_conflict_info = ci;
+       }
+
+       if (rename_type == RENAME_TWO_FILES_TO_ONE) {
+               /*
+                * For each rename, there could have been
+                * modifications on the side of history where that
+                * file was not renamed.
+                */
+               int ostage1 = o->branch1 == branch1 ? 3 : 2;
+               int ostage2 = ostage1 ^ 1;
+
+               ci->ren1_other.path = pair1->one->path;
+               hashcpy(ci->ren1_other.sha1, src_entry1->stages[ostage1].sha);
+               ci->ren1_other.mode = src_entry1->stages[ostage1].mode;
+
+               ci->ren2_other.path = pair2->one->path;
+               hashcpy(ci->ren2_other.sha1, src_entry2->stages[ostage2].sha);
+               ci->ren2_other.mode = src_entry2->stages[ostage2].mode;
        }
 }
 
@@ -230,7 +254,7 @@ struct tree *write_tree_from_memory(struct merge_options *o)
                for (i = 0; i < active_nr; i++) {
                        struct cache_entry *ce = active_cache[i];
                        if (ce_stage(ce))
-                               fprintf(stderr, "BUG: %d %.*s", ce_stage(ce),
+                               fprintf(stderr, "BUG: %d %.*s\n", ce_stage(ce),
                                        (int)ce_namelen(ce), ce->name);
                }
                die("Bug in merge-recursive.c");
@@ -333,44 +357,90 @@ static struct string_list *get_unmerged(void)
        return unmerged;
 }
 
-static void make_room_for_directories_of_df_conflicts(struct merge_options *o,
-                                                     struct string_list *entries)
+static int string_list_df_name_compare(const void *a, const void *b)
 {
-       /* If there are D/F conflicts, and the paths currently exist
-        * in the working copy as a file, we want to remove them to
-        * make room for the corresponding directory.  Such paths will
-        * later be processed in process_df_entry() at the end.  If
-        * the corresponding directory ends up being removed by the
-        * merge, then the file will be reinstated at that time;
-        * otherwise, if the file is not supposed to be removed by the
-        * merge, the contents of the file will be placed in another
-        * unique filename.
+       const struct string_list_item *one = a;
+       const struct string_list_item *two = b;
+       int onelen = strlen(one->string);
+       int twolen = strlen(two->string);
+       /*
+        * Here we only care that entries for D/F conflicts are
+        * adjacent, in particular with the file of the D/F conflict
+        * appearing before files below the corresponding directory.
+        * The order of the rest of the list is irrelevant for us.
         *
-        * NOTE: This function relies on the fact that entries for a
-        * D/F conflict will appear adjacent in the index, with the
-        * entries for the file appearing before entries for paths
-        * below the corresponding directory.
+        * To achieve this, we sort with df_name_compare and provide
+        * the mode S_IFDIR so that D/F conflicts will sort correctly.
+        * We use the mode S_IFDIR for everything else for simplicity,
+        * since in other cases any changes in their order due to
+        * sorting cause no problems for us.
+        */
+       int cmp = df_name_compare(one->string, onelen, S_IFDIR,
+                                 two->string, twolen, S_IFDIR);
+       /*
+        * Now that 'foo' and 'foo/bar' compare equal, we have to make sure
+        * that 'foo' comes before 'foo/bar'.
         */
+       if (cmp)
+               return cmp;
+       return onelen - twolen;
+}
+
+static void record_df_conflict_files(struct merge_options *o,
+                                    struct string_list *entries)
+{
+       /* If there is a D/F conflict and the file for such a conflict
+        * currently exist in the working copy, we want to allow it to be
+        * removed to make room for the corresponding directory if needed.
+        * The files underneath the directories of such D/F conflicts will
+        * be processed before the corresponding file involved in the D/F
+        * conflict.  If the D/F directory ends up being removed by the
+        * merge, then we won't have to touch the D/F file.  If the D/F
+        * directory needs to be written to the working copy, then the D/F
+        * file will simply be removed (in make_room_for_path()) to make
+        * room for the necessary paths.  Note that if both the directory
+        * and the file need to be present, then the D/F file will be
+        * reinstated with a new unique name at the time it is processed.
+        */
+       struct string_list df_sorted_entries;
        const char *last_file = NULL;
        int last_len = 0;
        int i;
 
+       /*
+        * If we're merging merge-bases, we don't want to bother with
+        * any working directory changes.
+        */
+       if (o->call_depth)
+               return;
+
+       /* Ensure D/F conflicts are adjacent in the entries list. */
+       memset(&df_sorted_entries, 0, sizeof(struct string_list));
        for (i = 0; i < entries->nr; i++) {
-               const char *path = entries->items[i].string;
+               struct string_list_item *next = &entries->items[i];
+               string_list_append(&df_sorted_entries, next->string)->util =
+                                  next->util;
+       }
+       qsort(df_sorted_entries.items, entries->nr, sizeof(*entries->items),
+             string_list_df_name_compare);
+
+       string_list_clear(&o->df_conflict_file_set, 1);
+       for (i = 0; i < df_sorted_entries.nr; i++) {
+               const char *path = df_sorted_entries.items[i].string;
                int len = strlen(path);
-               struct stage_data *e = entries->items[i].util;
+               struct stage_data *e = df_sorted_entries.items[i].util;
 
                /*
                 * Check if last_file & path correspond to a D/F conflict;
                 * i.e. whether path is last_file+'/'+<something>.
-                * If so, remove last_file to make room for path and friends.
+                * If so, record that it's okay to remove last_file to make
+                * room for path and friends if needed.
                 */
                if (last_file &&
                    len > last_len &&
                    memcmp(path, last_file, last_len) == 0 &&
                    path[last_len] == '/') {
-                       output(o, 3, "Removing %s to make room for subdirectory; may re-add later.", last_file);
-                       unlink(last_file);
+                       string_list_insert(&o->df_conflict_file_set, last_file);
                }
 
                /*
@@ -386,6 +456,7 @@ static void make_room_for_directories_of_df_conflicts(struct merge_options *o,
                        last_file = NULL;
                }
        }
+       string_list_clear(&df_sorted_entries, 0);
 }
 
 struct rename {
@@ -461,10 +532,21 @@ static struct string_list *get_renames(struct merge_options *o,
        return renames;
 }
 
-static int update_stages_options(const char *path, struct diff_filespec *o,
-                        struct diff_filespec *a, struct diff_filespec *b,
-                        int clear, int options)
+static int update_stages(const char *path, const struct diff_filespec *o,
+                        const struct diff_filespec *a,
+                        const struct diff_filespec *b)
 {
+
+       /*
+        * NOTE: It is usually a bad idea to call update_stages on a path
+        * before calling update_file on that same path, since it can
+        * sometimes lead to spurious "refusing to lose untracked file..."
+        * messages from update_file (via make_room_for path via
+        * would_lose_untracked).  Instead, reverse the order of the calls
+        * (executing update_file first and then update_stages).
+        */
+       int clear = 1;
+       int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;
        if (clear)
                if (remove_file_from_cache(path))
                        return -1;
@@ -480,23 +562,11 @@ static int update_stages_options(const char *path, struct diff_filespec *o,
        return 0;
 }
 
-static int update_stages(const char *path, struct diff_filespec *o,
-                        struct diff_filespec *a, struct diff_filespec *b,
-                        int clear)
+static void update_entry(struct stage_data *entry,
+                        struct diff_filespec *o,
+                        struct diff_filespec *a,
+                        struct diff_filespec *b)
 {
-       int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;
-       return update_stages_options(path, o, a, b, clear, options);
-}
-
-static int update_stages_and_entry(const char *path,
-                                  struct stage_data *entry,
-                                  struct diff_filespec *o,
-                                  struct diff_filespec *a,
-                                  struct diff_filespec *b,
-                                  int clear)
-{
-       int options;
-
        entry->processed = 0;
        entry->stages[1].mode = o->mode;
        entry->stages[2].mode = a->mode;
@@ -504,8 +574,6 @@ static int update_stages_and_entry(const char *path,
        hashcpy(entry->stages[1].sha, o->sha1);
        hashcpy(entry->stages[2].sha, a->sha1);
        hashcpy(entry->stages[3].sha, b->sha1);
-       options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;
-       return update_stages_options(path, o, a, b, clear, options);
 }
 
 static int remove_file(struct merge_options *o, int clean,
@@ -563,7 +631,31 @@ static void flush_buffer(int fd, const char *buf, unsigned long size)
        }
 }
 
-static int would_lose_untracked(const char *path)
+static int dir_in_way(const char *path, int check_working_copy)
+{
+       int pos, pathlen = strlen(path);
+       char *dirpath = xmalloc(pathlen + 2);
+       struct stat st;
+
+       strcpy(dirpath, path);
+       dirpath[pathlen] = '/';
+       dirpath[pathlen+1] = '\0';
+
+       pos = cache_name_pos(dirpath, pathlen+1);
+
+       if (pos < 0)
+               pos = -1 - pos;
+       if (pos < active_nr &&
+           !strncmp(dirpath, active_cache[pos]->name, pathlen+1)) {
+               free(dirpath);
+               return 1;
+       }
+
+       free(dirpath);
+       return check_working_copy && !lstat(path, &st) && S_ISDIR(st.st_mode);
+}
+
+static int was_tracked(const char *path)
 {
        int pos = cache_name_pos(path, strlen(path));
 
@@ -580,18 +672,42 @@ static int would_lose_untracked(const char *path)
                switch (ce_stage(active_cache[pos])) {
                case 0:
                case 2:
-                       return 0;
+                       return 1;
                }
                pos++;
        }
-       return file_exists(path);
+       return 0;
 }
 
-static int make_room_for_path(const char *path)
+static int would_lose_untracked(const char *path)
 {
-       int status;
+       return !was_tracked(path) && file_exists(path);
+}
+
+static int make_room_for_path(struct merge_options *o, const char *path)
+{
+       int status, i;
        const char *msg = "failed to create path '%s'%s";
 
+       /* Unlink any D/F conflict files that are in the way */
+       for (i = 0; i < o->df_conflict_file_set.nr; i++) {
+               const char *df_path = o->df_conflict_file_set.items[i].string;
+               size_t pathlen = strlen(path);
+               size_t df_pathlen = strlen(df_path);
+               if (df_pathlen < pathlen &&
+                   path[df_pathlen] == '/' &&
+                   strncmp(path, df_path, df_pathlen) == 0) {
+                       output(o, 3,
+                              "Removing %s to make room for subdirectory\n",
+                              df_path);
+                       unlink(df_path);
+                       unsorted_string_list_delete_item(&o->df_conflict_file_set,
+                                                        i, 0);
+                       break;
+               }
+       }
+
+       /* Make sure leading directories are created */
        status = safe_create_leading_directories_const(path);
        if (status) {
                if (status == -3) {
@@ -659,7 +775,7 @@ static void update_file_flags(struct merge_options *o,
                        }
                }
 
-               if (make_room_for_path(path) < 0) {
+               if (make_room_for_path(o, path) < 0) {
                        update_wd = 0;
                        free(buf);
                        goto update_index;
@@ -712,9 +828,9 @@ struct merge_file_info {
 
 static int merge_3way(struct merge_options *o,
                      mmbuffer_t *result_buf,
-                     struct diff_filespec *one,
-                     struct diff_filespec *a,
-                     struct diff_filespec *b,
+                     const struct diff_filespec *one,
+                     const struct diff_filespec *a,
+                     const struct diff_filespec *b,
                      const char *branch1,
                      const char *branch2)
 {
@@ -771,12 +887,12 @@ static int merge_3way(struct merge_options *o,
        return merge_status;
 }
 
-static struct merge_file_info merge_file(struct merge_options *o,
-                                        struct diff_filespec *one,
-                                        struct diff_filespec *a,
-                                        struct diff_filespec *b,
-                                        const char *branch1,
-                                        const char *branch2)
+static struct merge_file_info 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;
        result.merge = 0;
@@ -845,94 +961,303 @@ static struct merge_file_info merge_file(struct merge_options *o,
        return result;
 }
 
+static struct merge_file_info
+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)
+{
+       char *side1 = NULL;
+       char *side2 = NULL;
+       struct merge_file_info mfi;
+
+       if (filename1) {
+               side1 = xmalloc(strlen(branch1) + strlen(filename1) + 2);
+               sprintf(side1, "%s:%s", branch1, filename1);
+       }
+       if (filename2) {
+               side2 = xmalloc(strlen(branch2) + strlen(filename2) + 2);
+               sprintf(side2, "%s:%s", branch2, filename2);
+       }
+
+       mfi = merge_file_1(o, one, a, b,
+                          side1 ? side1 : branch1, side2 ? side2 : branch2);
+       free(side1);
+       free(side2);
+       return mfi;
+}
+
+static struct merge_file_info merge_file(struct merge_options *o,
+                                        const char *path,
+                                        const unsigned char *o_sha, int o_mode,
+                                        const unsigned char *a_sha, int a_mode,
+                                        const unsigned char *b_sha, int b_mode,
+                                        const char *branch1,
+                                        const char *branch2)
+{
+       struct diff_filespec one, a, b;
+
+       one.path = a.path = b.path = (char *)path;
+       hashcpy(one.sha1, o_sha);
+       one.mode = o_mode;
+       hashcpy(a.sha1, a_sha);
+       a.mode = a_mode;
+       hashcpy(b.sha1, b_sha);
+       b.mode = b_mode;
+       return merge_file_1(o, &one, &a, &b, branch1, branch2);
+}
+
+static void handle_change_delete(struct merge_options *o,
+                                const char *path,
+                                const unsigned char *o_sha, int o_mode,
+                                const unsigned char *a_sha, int a_mode,
+                                const unsigned char *b_sha, int b_mode,
+                                const char *change, const char *change_past)
+{
+       char *renamed = NULL;
+       if (dir_in_way(path, !o->call_depth)) {
+               renamed = unique_path(o, path, a_sha ? o->branch1 : o->branch2);
+       }
+
+       if (o->call_depth) {
+               /*
+                * We cannot arbitrarily accept either a_sha or b_sha as
+                * correct; since there is no true "middle point" between
+                * them, simply reuse the base version for virtual merge base.
+                */
+               remove_file_from_cache(path);
+               update_file(o, 0, o_sha, o_mode, renamed ? renamed : path);
+       } else if (!a_sha) {
+               output(o, 1, "CONFLICT (%s/delete): %s deleted in %s "
+                      "and %s in %s. Version %s of %s left in tree%s%s.",
+                      change, path, o->branch1,
+                      change_past, o->branch2, o->branch2, path,
+                      NULL == renamed ? "" : " at ",
+                      NULL == renamed ? "" : renamed);
+               update_file(o, 0, b_sha, b_mode, renamed ? renamed : path);
+       } else {
+               output(o, 1, "CONFLICT (%s/delete): %s deleted in %s "
+                      "and %s in %s. Version %s of %s left in tree%s%s.",
+                      change, path, o->branch2,
+                      change_past, o->branch1, o->branch1, path,
+                      NULL == renamed ? "" : " at ",
+                      NULL == renamed ? "" : renamed);
+               if (renamed)
+                       update_file(o, 0, a_sha, a_mode, renamed);
+               /*
+                * No need to call update_file() on path when !renamed, since
+                * that would needlessly touch path.  We could call
+                * update_file_flags() with update_cache=0 and update_wd=0,
+                * but that's a no-op.
+                */
+       }
+       free(renamed);
+}
+
 static void conflict_rename_delete(struct merge_options *o,
                                   struct diff_filepair *pair,
                                   const char *rename_branch,
                                   const char *other_branch)
 {
-       char *dest_name = pair->two->path;
-       int df_conflict = 0;
-       struct stat st;
+       const struct diff_filespec *orig = pair->one;
+       const struct diff_filespec *dest = pair->two;
+       const unsigned char *a_sha = NULL;
+       const unsigned char *b_sha = NULL;
+       int a_mode = 0;
+       int b_mode = 0;
+
+       if (rename_branch == o->branch1) {
+               a_sha = dest->sha1;
+               a_mode = dest->mode;
+       } else {
+               b_sha = dest->sha1;
+               b_mode = dest->mode;
+       }
 
-       output(o, 1, "CONFLICT (rename/delete): Rename %s->%s in %s "
-              "and deleted in %s",
-              pair->one->path, pair->two->path, rename_branch,
-              other_branch);
-       if (!o->call_depth)
-               update_stages(dest_name, NULL,
-                             rename_branch == o->branch1 ? pair->two : NULL,
-                             rename_branch == o->branch1 ? NULL : pair->two,
-                             1);
-       if (lstat(dest_name, &st) == 0 && S_ISDIR(st.st_mode)) {
-               dest_name = unique_path(o, dest_name, rename_branch);
-               df_conflict = 1;
+       handle_change_delete(o,
+                            o->call_depth ? orig->path : dest->path,
+                            orig->sha1, orig->mode,
+                            a_sha, a_mode,
+                            b_sha, b_mode,
+                            "rename", "renamed");
+
+       if (o->call_depth) {
+               remove_file_from_cache(dest->path);
+       } else {
+               update_stages(dest->path, NULL,
+                             rename_branch == o->branch1 ? dest : NULL,
+                             rename_branch == o->branch1 ? NULL : dest);
        }
-       update_file(o, 0, pair->two->sha1, pair->two->mode, dest_name);
-       if (df_conflict)
-               free(dest_name);
+
 }
 
-static void conflict_rename_rename_1to2(struct merge_options *o,
-                                       struct diff_filepair *pair1,
-                                       const char *branch1,
-                                       struct diff_filepair *pair2,
-                                       const char *branch2)
+static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,
+                                                struct stage_data *entry,
+                                                int stage)
 {
-       /* One file was renamed in both branches, but to different names. */
-       char *del[2];
-       int delp = 0;
-       const char *ren1_dst = pair1->two->path;
-       const char *ren2_dst = pair2->two->path;
-       const char *dst_name1 = ren1_dst;
-       const char *dst_name2 = ren2_dst;
-       struct stat st;
-       if (lstat(ren1_dst, &st) == 0 && S_ISDIR(st.st_mode)) {
-               dst_name1 = del[delp++] = unique_path(o, ren1_dst, branch1);
-               output(o, 1, "%s is a directory in %s adding as %s instead",
-                      ren1_dst, branch2, dst_name1);
+       unsigned char *sha = entry->stages[stage].sha;
+       unsigned mode = entry->stages[stage].mode;
+       if (mode == 0 || is_null_sha1(sha))
+               return NULL;
+       hashcpy(target->sha1, sha);
+       target->mode = mode;
+       return target;
+}
+
+static void handle_file(struct merge_options *o,
+                       struct diff_filespec *rename,
+                       int stage,
+                       struct rename_conflict_info *ci)
+{
+       char *dst_name = rename->path;
+       struct stage_data *dst_entry;
+       const char *cur_branch, *other_branch;
+       struct diff_filespec other;
+       struct diff_filespec *add;
+
+       if (stage == 2) {
+               dst_entry = ci->dst_entry1;
+               cur_branch = ci->branch1;
+               other_branch = ci->branch2;
+       } else {
+               dst_entry = ci->dst_entry2;
+               cur_branch = ci->branch2;
+               other_branch = ci->branch1;
        }
-       if (lstat(ren2_dst, &st) == 0 && S_ISDIR(st.st_mode)) {
-               dst_name2 = del[delp++] = unique_path(o, ren2_dst, branch2);
-               output(o, 1, "%s is a directory in %s adding as %s instead",
-                      ren2_dst, branch1, dst_name2);
+
+       add = filespec_from_entry(&other, dst_entry, stage ^ 1);
+       if (add) {
+               char *add_name = unique_path(o, rename->path, other_branch);
+               update_file(o, 0, add->sha1, add->mode, add_name);
+
+               remove_file(o, 0, rename->path, 0);
+               dst_name = unique_path(o, rename->path, cur_branch);
+       } else {
+               if (dir_in_way(rename->path, !o->call_depth)) {
+                       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);
+               }
        }
+       update_file(o, 0, rename->sha1, rename->mode, dst_name);
+       if (stage == 2)
+               update_stages(rename->path, NULL, rename, add);
+       else
+               update_stages(rename->path, NULL, add, rename);
+
+       if (dst_name != rename->path)
+               free(dst_name);
+}
+
+static void conflict_rename_rename_1to2(struct merge_options *o,
+                                       struct rename_conflict_info *ci)
+{
+       /* One file was renamed in both branches, but to different names. */
+       struct diff_filespec *one = ci->pair1->one;
+       struct diff_filespec *a = ci->pair1->two;
+       struct diff_filespec *b = ci->pair2->two;
+
+       output(o, 1, "CONFLICT (rename/rename): "
+              "Rename \"%s\"->\"%s\" in branch \"%s\" "
+              "rename \"%s\"->\"%s\" in \"%s\"%s",
+              one->path, a->path, ci->branch1,
+              one->path, b->path, ci->branch2,
+              o->call_depth ? " (left unresolved)" : "");
        if (o->call_depth) {
-               remove_file_from_cache(dst_name1);
-               remove_file_from_cache(dst_name2);
+               struct merge_file_info mfi;
+               struct diff_filespec other;
+               struct diff_filespec *add;
+               mfi = merge_file(o, one->path,
+                                one->sha1, one->mode,
+                                a->sha1, a->mode,
+                                b->sha1, b->mode,
+                                ci->branch1, ci->branch2);
                /*
-                * Uncomment to leave the conflicting names in the resulting tree
-                *
-                * update_file(o, 0, pair1->two->sha1, pair1->two->mode, dst_name1);
-                * update_file(o, 0, pair2->two->sha1, pair2->two->mode, dst_name2);
+                * FIXME: For rename/add-source conflicts (if we could detect
+                * such), this is wrong.  We should instead find a unique
+                * pathname and then either rename the add-source file to that
+                * unique path, or use that unique path instead of src here.
                 */
-       } else {
-               update_stages(ren1_dst, NULL, pair1->two, NULL, 1);
-               update_stages(ren2_dst, NULL, NULL, pair2->two, 1);
+               update_file(o, 0, mfi.sha, mfi.mode, one->path);
 
-               update_file(o, 0, pair1->two->sha1, pair1->two->mode, dst_name1);
-               update_file(o, 0, pair2->two->sha1, pair2->two->mode, dst_name2);
+               /*
+                * Above, we put the merged content at the merge-base's
+                * path.  Now we usually need to delete both a->path and
+                * b->path.  However, the rename on each side of the merge
+                * could also be involved in a rename/add conflict.  In
+                * such cases, we should keep the added file around,
+                * resolving the conflict at that path in its favor.
+                */
+               add = filespec_from_entry(&other, ci->dst_entry1, 2 ^ 1);
+               if (add)
+                       update_file(o, 0, add->sha1, add->mode, a->path);
+               else
+                       remove_file_from_cache(a->path);
+               add = filespec_from_entry(&other, ci->dst_entry2, 3 ^ 1);
+               if (add)
+                       update_file(o, 0, add->sha1, add->mode, b->path);
+               else
+                       remove_file_from_cache(b->path);
+       } else {
+               handle_file(o, a, 2, ci);
+               handle_file(o, b, 3, ci);
        }
-       while (delp--)
-               free(del[delp]);
 }
 
 static void conflict_rename_rename_2to1(struct merge_options *o,
-                                       struct rename *ren1,
-                                       const char *branch1,
-                                       struct rename *ren2,
-                                       const char *branch2)
+                                       struct rename_conflict_info *ci)
 {
-       /* Two files were renamed to the same thing. */
-       char *new_path1 = unique_path(o, ren1->pair->two->path, branch1);
-       char *new_path2 = unique_path(o, ren2->pair->two->path, branch2);
-       output(o, 1, "Renaming %s to %s and %s to %s instead",
-              ren1->pair->one->path, new_path1,
-              ren2->pair->one->path, new_path2);
-       remove_file(o, 0, ren1->pair->two->path, 0);
-       update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1);
-       update_file(o, 0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2);
-       free(new_path2);
-       free(new_path1);
+       /* Two files, a & b, were renamed to the same thing, c. */
+       struct diff_filespec *a = ci->pair1->one;
+       struct diff_filespec *b = ci->pair2->one;
+       struct diff_filespec *c1 = ci->pair1->two;
+       struct diff_filespec *c2 = ci->pair2->two;
+       char *path = c1->path; /* == c2->path */
+       struct merge_file_info mfi_c1;
+       struct merge_file_info mfi_c2;
+
+       output(o, 1, "CONFLICT (rename/rename): "
+              "Rename %s->%s in %s. "
+              "Rename %s->%s in %s",
+              a->path, c1->path, ci->branch1,
+              b->path, c2->path, ci->branch2);
+
+       remove_file(o, 1, a->path, would_lose_untracked(a->path));
+       remove_file(o, 1, b->path, would_lose_untracked(b->path));
+
+       mfi_c1 = merge_file_special_markers(o, a, c1, &ci->ren1_other,
+                                           o->branch1, c1->path,
+                                           o->branch2, ci->ren1_other.path);
+       mfi_c2 = merge_file_special_markers(o, b, &ci->ren2_other, c2,
+                                           o->branch1, ci->ren2_other.path,
+                                           o->branch2, c2->path);
+
+       if (o->call_depth) {
+               /*
+                * If mfi_c1.clean && mfi_c2.clean, then it might make
+                * sense to do a two-way merge of those results.  But, I
+                * think in all cases, it makes sense to have the virtual
+                * merge base just undo the renames; they can be detected
+                * again later for the non-recursive merge.
+                */
+               remove_file(o, 0, path, 0);
+               update_file(o, 0, mfi_c1.sha, mfi_c1.mode, a->path);
+               update_file(o, 0, mfi_c2.sha, mfi_c2.mode, b->path);
+       } else {
+               char *new_path1 = unique_path(o, path, ci->branch1);
+               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);
+               update_file(o, 0, mfi_c1.sha, mfi_c1.mode, new_path1);
+               update_file(o, 0, mfi_c2.sha, mfi_c2.mode, new_path2);
+               free(new_path2);
+               free(new_path1);
+       }
 }
 
 static int process_renames(struct merge_options *o,
@@ -947,12 +1272,12 @@ static int process_renames(struct merge_options *o,
        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
-                       = sre->dst_entry;
+                       = (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
-                       = sre->dst_entry;
+                       = (void *)sre;
        }
 
        for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) {
@@ -960,6 +1285,7 @@ static int process_renames(struct merge_options *o,
                struct rename *ren1 = NULL, *ren2 = NULL;
                const char *branch1, *branch2;
                const char *ren1_src, *ren1_dst;
+               struct string_list_item *lookup;
 
                if (i >= a_renames->nr) {
                        ren2 = b_renames->items[j++].util;
@@ -991,44 +1317,82 @@ static int process_renames(struct merge_options *o,
                        ren1 = tmp;
                }
 
-               ren1->dst_entry->processed = 1;
-               ren1->src_entry->processed = 1;
-
                if (ren1->processed)
                        continue;
                ren1->processed = 1;
+               ren1->dst_entry->processed = 1;
+               /* BUG: We should only mark src_entry as processed if we
+                * are not dealing with a rename + add-source case.
+                */
+               ren1->src_entry->processed = 1;
 
                ren1_src = ren1->pair->one->path;
                ren1_dst = ren1->pair->two->path;
 
                if (ren2) {
+                       /* One file renamed on both sides */
                        const char *ren2_src = ren2->pair->one->path;
                        const char *ren2_dst = ren2->pair->two->path;
-                       /* Renamed in 1 and renamed in 2 */
+                       enum rename_type rename_type;
                        if (strcmp(ren1_src, ren2_src) != 0)
-                               die("ren1.src != ren2.src");
+                               die("ren1_src != ren2_src");
                        ren2->dst_entry->processed = 1;
                        ren2->processed = 1;
                        if (strcmp(ren1_dst, ren2_dst) != 0) {
-                               setup_rename_df_conflict_info(RENAME_ONE_FILE_TO_TWO,
-                                                             ren1->pair,
-                                                             ren2->pair,
-                                                             branch1,
-                                                             branch2,
-                                                             ren1->dst_entry,
-                                                             ren2->dst_entry);
+                               rename_type = RENAME_ONE_FILE_TO_TWO;
+                               clean_merge = 0;
                        } else {
+                               rename_type = RENAME_ONE_FILE_TO_ONE;
+                               /* BUG: We should only remove ren1_src in
+                                * the base stage (think of rename +
+                                * add-source cases).
+                                */
                                remove_file(o, 1, ren1_src, 1);
-                               update_stages_and_entry(ren1_dst,
-                                                       ren1->dst_entry,
-                                                       ren1->pair->one,
-                                                       ren1->pair->two,
-                                                       ren2->pair->two,
-                                                       1 /* clear */);
+                               update_entry(ren1->dst_entry,
+                                            ren1->pair->one,
+                                            ren1->pair->two,
+                                            ren2->pair->two);
                        }
+                       setup_rename_conflict_info(rename_type,
+                                                  ren1->pair,
+                                                  ren2->pair,
+                                                  branch1,
+                                                  branch2,
+                                                  ren1->dst_entry,
+                                                  ren2->dst_entry,
+                                                  o,
+                                                  NULL,
+                                                  NULL);
+               } else if ((lookup = string_list_lookup(renames2Dst, ren1_dst))) {
+                       /* Two different files renamed to the same thing */
+                       char *ren2_dst;
+                       ren2 = lookup->util;
+                       ren2_dst = ren2->pair->two->path;
+                       if (strcmp(ren1_dst, ren2_dst) != 0)
+                               die("ren1_dst != ren2_dst");
+
+                       clean_merge = 0;
+                       ren2->processed = 1;
+                       /*
+                        * BUG: We should only mark src_entry as processed
+                        * if we are not dealing with a rename + add-source
+                        * case.
+                        */
+                       ren2->src_entry->processed = 1;
+
+                       setup_rename_conflict_info(RENAME_TWO_FILES_TO_ONE,
+                                                  ren1->pair,
+                                                  ren2->pair,
+                                                  branch1,
+                                                  branch2,
+                                                  ren1->dst_entry,
+                                                  ren2->dst_entry,
+                                                  o,
+                                                  ren1->src_entry,
+                                                  ren2->src_entry);
+
                } else {
                        /* Renamed in 1, maybe changed in 2 */
-                       struct string_list_item *item;
                        /* we only use sha1 and mode of these */
                        struct diff_filespec src_other, dst_other;
                        int try_merge;
@@ -1042,7 +1406,12 @@ static int process_renames(struct merge_options *o,
                        int renamed_stage = a_renames == renames1 ? 2 : 3;
                        int other_stage =   a_renames == renames1 ? 3 : 2;
 
-                       remove_file(o, 1, ren1_src, o->call_depth || renamed_stage == 2);
+                       /* BUG: We should only remove ren1_src in the base
+                        * stage and in other_stage (think of rename +
+                        * add-source case).
+                        */
+                       remove_file(o, 1, ren1_src,
+                                   renamed_stage == 2 || !was_tracked(ren1_src));
 
                        hashcpy(src_other.sha1, ren1->src_entry->stages[other_stage].sha);
                        src_other.mode = ren1->src_entry->stages[other_stage].mode;
@@ -1051,27 +1420,33 @@ static int process_renames(struct merge_options *o,
                        try_merge = 0;
 
                        if (sha_eq(src_other.sha1, null_sha1)) {
-                               if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
-                                       ren1->dst_entry->processed = 0;
-                                       setup_rename_df_conflict_info(RENAME_DELETE,
-                                                                     ren1->pair,
-                                                                     NULL,
-                                                                     branch1,
-                                                                     branch2,
-                                                                     ren1->dst_entry,
-                                                                     NULL);
-                               } else {
-                                       clean_merge = 0;
-                                       conflict_rename_delete(o, ren1->pair, branch1, branch2);
-                               }
+                               setup_rename_conflict_info(RENAME_DELETE,
+                                                          ren1->pair,
+                                                          NULL,
+                                                          branch1,
+                                                          branch2,
+                                                          ren1->dst_entry,
+                                                          NULL,
+                                                          o,
+                                                          NULL,
+                                                          NULL);
                        } else if ((dst_other.mode == ren1->pair->two->mode) &&
                                   sha_eq(dst_other.sha1, ren1->pair->two->sha1)) {
-                               /* Added file on the other side
-                                  identical to the file being
-                                  renamed: clean merge */
-                               update_file(o, 1, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst);
+                               /*
+                                * Added file on the other side identical to
+                                * the file being renamed: clean merge.
+                                * Also, there is no need to overwrite the
+                                * file already in the working copy, so call
+                                * update_file_flags() instead of
+                                * update_file().
+                                */
+                               update_file_flags(o,
+                                                 ren1->pair->two->sha1,
+                                                 ren1->pair->two->mode,
+                                                 ren1_dst,
+                                                 1, /* update_cache */
+                                                 0  /* update_wd    */);
                        } else if (!sha_eq(dst_other.sha1, null_sha1)) {
-                               const char *new_path;
                                clean_merge = 0;
                                try_merge = 1;
                                output(o, 1, "CONFLICT (rename/add): Rename %s->%s in %s. "
@@ -1080,40 +1455,19 @@ static int process_renames(struct merge_options *o,
                                       ren1_dst, branch2);
                                if (o->call_depth) {
                                        struct merge_file_info mfi;
-                                       struct diff_filespec one, a, b;
-
-                                       one.path = a.path = b.path =
-                                               (char *)ren1_dst;
-                                       hashcpy(one.sha1, null_sha1);
-                                       one.mode = 0;
-                                       hashcpy(a.sha1, ren1->pair->two->sha1);
-                                       a.mode = ren1->pair->two->mode;
-                                       hashcpy(b.sha1, dst_other.sha1);
-                                       b.mode = dst_other.mode;
-                                       mfi = merge_file(o, &one, &a, &b,
-                                                        branch1,
-                                                        branch2);
+                                       mfi = merge_file(o, ren1_dst, null_sha1, 0,
+                                                        ren1->pair->two->sha1, ren1->pair->two->mode,
+                                                        dst_other.sha1, dst_other.mode,
+                                                        branch1, branch2);
                                        output(o, 1, "Adding merged %s", ren1_dst);
-                                       update_file(o, 0,
-                                                   mfi.sha,
-                                                   mfi.mode,
-                                                   ren1_dst);
+                                       update_file(o, 0, mfi.sha, mfi.mode, ren1_dst);
                                        try_merge = 0;
                                } else {
-                                       new_path = unique_path(o, ren1_dst, branch2);
+                                       char *new_path = unique_path(o, ren1_dst, branch2);
                                        output(o, 1, "Adding as %s instead", new_path);
                                        update_file(o, 0, dst_other.sha1, dst_other.mode, new_path);
+                                       free(new_path);
                                }
-                       } else if ((item = string_list_lookup(renames2Dst, ren1_dst))) {
-                               ren2 = item->util;
-                               clean_merge = 0;
-                               ren2->processed = 1;
-                               output(o, 1, "CONFLICT (rename/rename): "
-                                      "Rename %s->%s in %s. "
-                                      "Rename %s->%s in %s",
-                                      ren1_src, ren1_dst, branch1,
-                                      ren2->pair->one->path, ren2->pair->two->path, branch2);
-                               conflict_rename_rename_2to1(o, ren1, branch1, ren2, branch2);
                        } else
                                try_merge = 1;
 
@@ -1129,16 +1483,17 @@ static int process_renames(struct merge_options *o,
                                        b = ren1->pair->two;
                                        a = &src_other;
                                }
-                               update_stages_and_entry(ren1_dst, ren1->dst_entry, one, a, b, 1);
-                               if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
-                                       setup_rename_df_conflict_info(RENAME_NORMAL,
-                                                                     ren1->pair,
-                                                                     NULL,
-                                                                     branch1,
-                                                                     NULL,
-                                                                     ren1->dst_entry,
-                                                                     NULL);
-                               }
+                               update_entry(ren1->dst_entry, one, a, b);
+                               setup_rename_conflict_info(RENAME_NORMAL,
+                                                          ren1->pair,
+                                                          NULL,
+                                                          branch1,
+                                                          NULL,
+                                                          ren1->dst_entry,
+                                                          NULL,
+                                                          o,
+                                                          NULL,
+                                                          NULL);
                        }
                }
        }
@@ -1200,29 +1555,18 @@ error_return:
        return ret;
 }
 
-static void handle_delete_modify(struct merge_options *o,
+static void handle_modify_delete(struct merge_options *o,
                                 const char *path,
-                                const char *new_path,
+                                unsigned char *o_sha, int o_mode,
                                 unsigned char *a_sha, int a_mode,
                                 unsigned char *b_sha, int b_mode)
 {
-       if (!a_sha) {
-               output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
-                      "and modified in %s. Version %s of %s left in tree%s%s.",
-                      path, o->branch1,
-                      o->branch2, o->branch2, path,
-                      path == new_path ? "" : " at ",
-                      path == new_path ? "" : new_path);
-               update_file(o, 0, b_sha, b_mode, new_path);
-       } else {
-               output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
-                      "and modified in %s. Version %s of %s left in tree%s%s.",
-                      path, o->branch2,
-                      o->branch1, o->branch1, path,
-                      path == new_path ? "" : " at ",
-                      path == new_path ? "" : new_path);
-               update_file(o, 0, a_sha, a_mode, new_path);
-       }
+       handle_change_delete(o,
+                            path,
+                            o_sha, o_mode,
+                            a_sha, a_mode,
+                            b_sha, b_mode,
+                            "modify", "modified");
 }
 
 static int merge_content(struct merge_options *o,
@@ -1230,12 +1574,12 @@ static int merge_content(struct merge_options *o,
                         unsigned char *o_sha, int o_mode,
                         unsigned char *a_sha, int a_mode,
                         unsigned char *b_sha, int b_mode,
-                        const char *df_rename_conflict_branch)
+                        struct rename_conflict_info *rename_conflict_info)
 {
        const char *reason = "content";
+       const char *path1 = NULL, *path2 = NULL;
        struct merge_file_info mfi;
        struct diff_filespec one, a, b;
-       struct stat st;
        unsigned df_conflict_remains = 0;
 
        if (!o_sha) {
@@ -1250,16 +1594,43 @@ static int merge_content(struct merge_options *o,
        hashcpy(b.sha1, b_sha);
        b.mode = b_mode;
 
-       mfi = merge_file(o, &one, &a, &b, o->branch1, o->branch2);
-       if (df_rename_conflict_branch &&
-           lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
-               df_conflict_remains = 1;
+       if (rename_conflict_info) {
+               struct diff_filepair *pair1 = rename_conflict_info->pair1;
+
+               path1 = (o->branch1 == rename_conflict_info->branch1) ?
+                       pair1->two->path : pair1->one->path;
+               /* If rename_conflict_info->pair2 != NULL, we are in
+                * RENAME_ONE_FILE_TO_ONE case.  Otherwise, we have a
+                * normal rename.
+                */
+               path2 = (rename_conflict_info->pair2 ||
+                        o->branch2 == rename_conflict_info->branch1) ?
+                       pair1->two->path : pair1->one->path;
+
+               if (dir_in_way(path, !o->call_depth))
+                       df_conflict_remains = 1;
        }
+       mfi = merge_file_special_markers(o, &one, &a, &b,
+                                        o->branch1, path1,
+                                        o->branch2, path2);
 
        if (mfi.clean && !df_conflict_remains &&
-           sha_eq(mfi.sha, a_sha) && mfi.mode == a.mode)
+           sha_eq(mfi.sha, a_sha) && mfi.mode == a_mode) {
+               int path_renamed_outside_HEAD;
                output(o, 3, "Skipped %s (merged same as existing)", path);
-       else
+               /*
+                * 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(mfi.mode, mfi.sha, path,
+                                     0, (!o->call_depth), 0);
+                       return mfi.clean;
+               }
+       } else
                output(o, 2, "Auto-merging %s", path);
 
        if (!mfi.clean) {
@@ -1267,16 +1638,34 @@ static int merge_content(struct merge_options *o,
                        reason = "submodule";
                output(o, 1, "CONFLICT (%s): Merge conflict in %s",
                                reason, path);
+               if (rename_conflict_info && !df_conflict_remains)
+                       update_stages(path, &one, &a, &b);
        }
 
        if (df_conflict_remains) {
-               const char *new_path;
-               update_file_flags(o, mfi.sha, mfi.mode, path,
-                                 o->call_depth || mfi.clean, 0);
-               new_path = unique_path(o, path, df_rename_conflict_branch);
-               mfi.clean = 0;
+               char *new_path;
+               if (o->call_depth) {
+                       remove_file_from_cache(path);
+               } else {
+                       if (!mfi.clean)
+                               update_stages(path, &one, &a, &b);
+                       else {
+                               int file_from_stage2 = was_tracked(path);
+                               struct diff_filespec merged;
+                               hashcpy(merged.sha1, mfi.sha);
+                               merged.mode = mfi.mode;
+
+                               update_stages(path, NULL,
+                                             file_from_stage2 ? &merged : NULL,
+                                             file_from_stage2 ? NULL : &merged);
+                       }
+
+               }
+               new_path = unique_path(o, path, rename_conflict_info->branch1);
                output(o, 1, "Adding as %s instead", new_path);
-               update_file_flags(o, mfi.sha, mfi.mode, new_path, 0, 1);
+               update_file(o, 0, mfi.sha, mfi.mode, new_path);
+               free(new_path);
+               mfi.clean = 0;
        } else {
                update_file(o, mfi.clean, mfi.sha, mfi.mode, path);
        }
@@ -1301,104 +1690,15 @@ static int process_entry(struct merge_options *o,
        unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
        unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
 
-       if (entry->rename_df_conflict_info)
-               return 1; /* Such cases are handled elsewhere. */
-
-       entry->processed = 1;
-       if (o_sha && (!a_sha || !b_sha)) {
-               /* Case A: Deleted in one */
-               if ((!a_sha && !b_sha) ||
-                   (!b_sha && blob_unchanged(o_sha, a_sha, normalize, path)) ||
-                   (!a_sha && blob_unchanged(o_sha, b_sha, normalize, path))) {
-                       /* Deleted in both or deleted in one and
-                        * unchanged in the other */
-                       if (a_sha)
-                               output(o, 2, "Removing %s", path);
-                       /* do not touch working file if it did not exist */
-                       remove_file(o, 1, path, !a_sha);
-               } else if (string_list_has_string(&o->current_directory_set,
-                                                 path)) {
-                       entry->processed = 0;
-                       return 1; /* Assume clean until processed */
-               } else {
-                       /* Deleted in one and changed in the other */
-                       clean_merge = 0;
-                       handle_delete_modify(o, path, path,
-                                            a_sha, a_mode, b_sha, b_mode);
-               }
-
-       } else if ((!o_sha && a_sha && !b_sha) ||
-                  (!o_sha && !a_sha && b_sha)) {
-               /* Case B: Added in one. */
-               unsigned mode;
-               const unsigned char *sha;
-
-               if (a_sha) {
-                       mode = a_mode;
-                       sha = a_sha;
-               } else {
-                       mode = b_mode;
-                       sha = b_sha;
-               }
-               if (string_list_has_string(&o->current_directory_set, path)) {
-                       /* Handle D->F conflicts after all subfiles */
-                       entry->processed = 0;
-                       return 1; /* Assume clean until processed */
-               } else {
-                       output(o, 2, "Adding %s", path);
-                       update_file(o, 1, sha, mode, path);
-               }
-       } else if (a_sha && b_sha) {
-               /* Case C: Added in both (check for same permissions) and */
-               /* case D: Modified in both, but differently. */
-               clean_merge = merge_content(o, path,
-                                           o_sha, o_mode, a_sha, a_mode, b_sha, b_mode,
-                                           NULL);
-       } else if (!o_sha && !a_sha && !b_sha) {
-               /*
-                * this entry was deleted altogether. a_mode == 0 means
-                * we had that path and want to actively remove it.
-                */
-               remove_file(o, 1, path, !a_mode);
-       } else
-               die("Fatal merge failure, shouldn't happen.");
-
-       return clean_merge;
-}
-
-/*
- * Per entry merge function for D/F (and/or rename) conflicts.  In the
- * cases we can cleanly resolve D/F conflicts, process_entry() can
- * clean out all the files below the directory for us.  All D/F
- * conflict cases must be handled here at the end to make sure any
- * directories that can be cleaned out, are.
- *
- * Some rename conflicts may also be handled here that don't necessarily
- * involve D/F conflicts, since the code to handle them is generic enough
- * to handle those rename conflicts with or without D/F conflicts also
- * being involved.
- */
-static int process_df_entry(struct merge_options *o,
-                           const char *path, struct stage_data *entry)
-{
-       int clean_merge = 1;
-       unsigned o_mode = entry->stages[1].mode;
-       unsigned a_mode = entry->stages[2].mode;
-       unsigned b_mode = entry->stages[3].mode;
-       unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode);
-       unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
-       unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
-       struct stat st;
-
        entry->processed = 1;
-       if (entry->rename_df_conflict_info) {
-               struct rename_df_conflict_info *conflict_info = entry->rename_df_conflict_info;
-               char *src;
+       if (entry->rename_conflict_info) {
+               struct rename_conflict_info *conflict_info = entry->rename_conflict_info;
                switch (conflict_info->rename_type) {
                case RENAME_NORMAL:
+               case RENAME_ONE_FILE_TO_ONE:
                        clean_merge = merge_content(o, path,
                                                    o_sha, o_mode, a_sha, a_mode, b_sha, b_mode,
-                                                   conflict_info->branch1);
+                                                   conflict_info);
                        break;
                case RENAME_DELETE:
                        clean_merge = 0;
@@ -1407,39 +1707,39 @@ static int process_df_entry(struct merge_options *o,
                                               conflict_info->branch2);
                        break;
                case RENAME_ONE_FILE_TO_TWO:
-                       src = conflict_info->pair1->one->path;
                        clean_merge = 0;
-                       output(o, 1, "CONFLICT (rename/rename): "
-                              "Rename \"%s\"->\"%s\" in branch \"%s\" "
-                              "rename \"%s\"->\"%s\" in \"%s\"%s",
-                              src, conflict_info->pair1->two->path, conflict_info->branch1,
-                              src, conflict_info->pair2->two->path, conflict_info->branch2,
-                              o->call_depth ? " (left unresolved)" : "");
-                       if (o->call_depth) {
-                               remove_file_from_cache(src);
-                               update_file(o, 0, conflict_info->pair1->one->sha1,
-                                           conflict_info->pair1->one->mode, src);
-                       }
-                       conflict_rename_rename_1to2(o, conflict_info->pair1,
-                                                   conflict_info->branch1,
-                                                   conflict_info->pair2,
-                                                   conflict_info->branch2);
-                       conflict_info->dst_entry2->processed = 1;
+                       conflict_rename_rename_1to2(o, conflict_info);
+                       break;
+               case RENAME_TWO_FILES_TO_ONE:
+                       clean_merge = 0;
+                       conflict_rename_rename_2to1(o, conflict_info);
                        break;
                default:
                        entry->processed = 0;
                        break;
                }
        } else if (o_sha && (!a_sha || !b_sha)) {
-               /* Modify/delete; deleted side may have put a directory in the way */
-               const char *new_path = path;
-               if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode))
-                       new_path = unique_path(o, path, a_sha ? o->branch1 : o->branch2);
-               clean_merge = 0;
-               handle_delete_modify(o, path, new_path,
-                                    a_sha, a_mode, b_sha, b_mode);
-       } else if (!o_sha && !!a_sha != !!b_sha) {
-               /* directory -> (directory, file) */
+               /* Case A: Deleted in one */
+               if ((!a_sha && !b_sha) ||
+                   (!b_sha && blob_unchanged(o_sha, a_sha, normalize, path)) ||
+                   (!a_sha && blob_unchanged(o_sha, b_sha, normalize, path))) {
+                       /* Deleted in both or deleted in one and
+                        * unchanged in the other */
+                       if (a_sha)
+                               output(o, 2, "Removing %s", path);
+                       /* do not touch working file if it did not exist */
+                       remove_file(o, 1, path, !a_sha);
+               } else {
+                       /* Modify/delete; deleted side may have put a directory in the way */
+                       clean_merge = 0;
+                       handle_modify_delete(o, path, o_sha, o_mode,
+                                            a_sha, a_mode, b_sha, b_mode);
+               }
+       } else if ((!o_sha && a_sha && !b_sha) ||
+                  (!o_sha && !a_sha && b_sha)) {
+               /* Case B: Added in one. */
+               /* [nothing|directory] -> ([nothing|directory], file) */
+
                const char *add_branch;
                const char *other_branch;
                unsigned mode;
@@ -1459,21 +1759,37 @@ static int process_df_entry(struct merge_options *o,
                        sha = b_sha;
                        conf = "directory/file";
                }
-               if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
-                       const char *new_path = unique_path(o, path, add_branch);
+               if (dir_in_way(path, !o->call_depth)) {
+                       char *new_path = unique_path(o, path, add_branch);
                        clean_merge = 0;
                        output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. "
                               "Adding %s as %s",
                               conf, path, other_branch, path, new_path);
+                       if (o->call_depth)
+                               remove_file_from_cache(path);
                        update_file(o, 0, sha, mode, new_path);
+                       if (o->call_depth)
+                               remove_file_from_cache(path);
+                       free(new_path);
                } else {
                        output(o, 2, "Adding %s", path);
-                       update_file(o, 1, sha, mode, path);
+                       /* do not overwrite file if already present */
+                       update_file_flags(o, sha, mode, path, 1, !a_sha);
                }
-       } else {
-               entry->processed = 0;
-               return 1; /* not handled; assume clean until processed */
-       }
+       } else if (a_sha && b_sha) {
+               /* Case C: Added in both (check for same permissions) and */
+               /* case D: Modified in both, but differently. */
+               clean_merge = merge_content(o, path,
+                                           o_sha, o_mode, a_sha, a_mode, b_sha, b_mode,
+                                           NULL);
+       } else if (!o_sha && !a_sha && !b_sha) {
+               /*
+                * this entry was deleted altogether. a_mode == 0 means
+                * we had that path and want to actively remove it.
+                */
+               remove_file(o, 1, path, !a_mode);
+       } else
+               die("Fatal merge failure, shouldn't happen.");
 
        return clean_merge;
 }
@@ -1517,24 +1833,17 @@ int merge_trees(struct merge_options *o,
                get_files_dirs(o, merge);
 
                entries = get_unmerged();
-               make_room_for_directories_of_df_conflicts(o, entries);
+               record_df_conflict_files(o, entries);
                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);
-               for (i = 0; i < entries->nr; i++) {
+               for (i = entries->nr-1; 0 <= i; i--) {
                        const char *path = entries->items[i].string;
                        struct stage_data *e = entries->items[i].util;
                        if (!e->processed
                                && !process_entry(o, path, e))
                                clean = 0;
                }
-               for (i = 0; i < entries->nr; i++) {
-                       const char *path = entries->items[i].string;
-                       struct stage_data *e = entries->items[i].util;
-                       if (!e->processed
-                               && !process_df_entry(o, path, e))
-                               clean = 0;
-               }
                for (i = 0; i < entries->nr; i++) {
                        struct stage_data *e = entries->items[i].util;
                        if (!e->processed)
@@ -1601,12 +1910,10 @@ int merge_recursive(struct merge_options *o,
 
        merged_common_ancestors = pop_commit(&ca);
        if (merged_common_ancestors == NULL) {
-               /* if there is no common ancestor, make an empty tree */
-               struct tree *tree = xcalloc(1, sizeof(struct tree));
+               /* if there is no common ancestor, use an empty tree */
+               struct tree *tree;
 
-               tree->object.parsed = 1;
-               tree->object.type = OBJ_TREE;
-               pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1);
+               tree = lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN);
                merged_common_ancestors = make_virtual_commit(tree, "ancestor");
        }
 
@@ -1743,6 +2050,8 @@ void init_merge_options(struct merge_options *o)
        o->current_file_set.strdup_strings = 1;
        memset(&o->current_directory_set, 0, sizeof(struct string_list));
        o->current_directory_set.strdup_strings = 1;
+       memset(&o->df_conflict_file_set, 0, sizeof(struct string_list));
+       o->df_conflict_file_set.strdup_strings = 1;
 }
 
 int parse_merge_opt(struct merge_options *o, const char *s)
index 7e1e972b13cb8a9f2fae084f769fffede24c8dbb..58f3435e9e854ab82c2fd0c10c55520bc69e5ed0 100644 (file)
@@ -26,6 +26,7 @@ struct merge_options {
        struct strbuf obuf;
        struct string_list current_file_set;
        struct string_list current_directory_set;
+       struct string_list df_conflict_file_set;
 };
 
 /* merge_trees() but with recursive ancestor consolidation */
diff --git a/mergetools/araxis b/mergetools/araxis
new file mode 100644 (file)
index 0000000..64f97c5
--- /dev/null
@@ -0,0 +1,20 @@
+diff_cmd () {
+       "$merge_tool_path" -wait -2 "$LOCAL" "$REMOTE" >/dev/null 2>&1
+}
+
+merge_cmd () {
+       touch "$BACKUP"
+       if $base_present
+       then
+               "$merge_tool_path" -wait -merge -3 -a1 \
+                       "$BASE" "$LOCAL" "$REMOTE" "$MERGED" >/dev/null 2>&1
+       else
+               "$merge_tool_path" -wait -2 \
+                       "$LOCAL" "$REMOTE" "$MERGED" >/dev/null 2>&1
+       fi
+       check_unchanged
+}
+
+translate_merge_tool_path() {
+       echo compare
+}
diff --git a/mergetools/bc3 b/mergetools/bc3
new file mode 100644 (file)
index 0000000..27b3dd4
--- /dev/null
@@ -0,0 +1,20 @@
+diff_cmd () {
+       "$merge_tool_path" "$LOCAL" "$REMOTE"
+}
+
+merge_cmd () {
+       touch "$BACKUP"
+       if $base_present
+       then
+               "$merge_tool_path" "$LOCAL" "$REMOTE" "$BASE" \
+                       -mergeoutput="$MERGED"
+       else
+               "$merge_tool_path" "$LOCAL" "$REMOTE" \
+                       -mergeoutput="$MERGED"
+       fi
+       check_unchanged
+}
+
+translate_merge_tool_path() {
+       echo bcompare
+}
diff --git a/mergetools/defaults b/mergetools/defaults
new file mode 100644 (file)
index 0000000..1d8f2a3
--- /dev/null
@@ -0,0 +1,46 @@
+# Redefined by builtin tools
+can_merge () {
+       return 0
+}
+
+can_diff () {
+       return 0
+}
+
+diff_cmd () {
+       merge_tool_cmd="$(get_merge_tool_cmd "$1")"
+       if test -z "$merge_tool_cmd"
+       then
+               status=1
+               break
+       fi
+       ( eval $merge_tool_cmd )
+       status=$?
+       return $status
+}
+
+merge_cmd () {
+       merge_tool_cmd="$(get_merge_tool_cmd "$1")"
+       if test -z "$merge_tool_cmd"
+       then
+               status=1
+               break
+       fi
+       trust_exit_code="$(git config --bool \
+               mergetool."$1".trustExitCode || echo false)"
+       if test "$trust_exit_code" = "false"
+       then
+               touch "$BACKUP"
+               ( eval $merge_tool_cmd )
+               status=$?
+               check_unchanged
+       else
+               ( eval $merge_tool_cmd )
+               status=$?
+       fi
+       return $status
+}
+
+translate_merge_tool_path () {
+       echo "$1"
+}
diff --git a/mergetools/diffuse b/mergetools/diffuse
new file mode 100644 (file)
index 0000000..02e0843
--- /dev/null
@@ -0,0 +1,17 @@
+diff_cmd () {
+       "$merge_tool_path" "$LOCAL" "$REMOTE" | cat
+}
+
+merge_cmd () {
+       touch "$BACKUP"
+       if $base_present
+       then
+               "$merge_tool_path" \
+                       "$LOCAL" "$MERGED" "$REMOTE" \
+                       "$BASE" | cat
+       else
+               "$merge_tool_path" \
+                       "$LOCAL" "$MERGED" "$REMOTE" | cat
+       fi
+       check_unchanged
+}
diff --git a/mergetools/ecmerge b/mergetools/ecmerge
new file mode 100644 (file)
index 0000000..13c2e43
--- /dev/null
@@ -0,0 +1,16 @@
+diff_cmd () {
+       "$merge_tool_path" --default --mode=diff2 "$LOCAL" "$REMOTE"
+}
+
+merge_cmd () {
+       touch "$BACKUP"
+       if $base_present
+       then
+               "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" \
+                       --default --mode=merge3 --to="$MERGED"
+       else
+               "$merge_tool_path" "$LOCAL" "$REMOTE" \
+                       --default --mode=merge2 --to="$MERGED"
+       fi
+       check_unchanged
+}
diff --git a/mergetools/emerge b/mergetools/emerge
new file mode 100644 (file)
index 0000000..f96d9e5
--- /dev/null
@@ -0,0 +1,23 @@
+diff_cmd () {
+       "$merge_tool_path" -f emerge-files-command "$LOCAL" "$REMOTE"
+}
+
+merge_cmd () {
+       if $base_present
+       then
+               "$merge_tool_path" \
+                       -f emerge-files-with-ancestor-command \
+                       "$LOCAL" "$REMOTE" "$BASE" \
+                       "$(basename "$MERGED")"
+       else
+               "$merge_tool_path" \
+                       -f emerge-files-command \
+                       "$LOCAL" "$REMOTE" \
+                       "$(basename "$MERGED")"
+       fi
+       status=$?
+}
+
+translate_merge_tool_path() {
+       echo emacs
+}
diff --git a/mergetools/kdiff3 b/mergetools/kdiff3
new file mode 100644 (file)
index 0000000..28fead4
--- /dev/null
@@ -0,0 +1,24 @@
+diff_cmd () {
+       "$merge_tool_path" --auto \
+               --L1 "$MERGED (A)" --L2 "$MERGED (B)" \
+               "$LOCAL" "$REMOTE" >/dev/null 2>&1
+}
+
+merge_cmd () {
+       if $base_present
+       then
+               "$merge_tool_path" --auto \
+                       --L1 "$MERGED (Base)" \
+                       --L2 "$MERGED (Local)" \
+                       --L3 "$MERGED (Remote)" \
+                       -o "$MERGED" "$BASE" "$LOCAL" "$REMOTE" \
+               >/dev/null 2>&1
+       else
+               "$merge_tool_path" --auto \
+                       --L1 "$MERGED (Local)" \
+                       --L2 "$MERGED (Remote)" \
+                       -o "$MERGED" "$LOCAL" "$REMOTE" \
+               >/dev/null 2>&1
+       fi
+       status=$?
+}
diff --git a/mergetools/kompare b/mergetools/kompare
new file mode 100644 (file)
index 0000000..433686c
--- /dev/null
@@ -0,0 +1,7 @@
+can_merge () {
+       return 1
+}
+
+diff_cmd () {
+       "$merge_tool_path" "$LOCAL" "$REMOTE"
+}
diff --git a/mergetools/meld b/mergetools/meld
new file mode 100644 (file)
index 0000000..eaa115c
--- /dev/null
@@ -0,0 +1,32 @@
+diff_cmd () {
+       "$merge_tool_path" "$LOCAL" "$REMOTE"
+}
+
+merge_cmd () {
+       if test -z "${meld_has_output_option:+set}"
+       then
+               check_meld_for_output_version
+       fi
+       touch "$BACKUP"
+       if test "$meld_has_output_option" = true
+       then
+               "$merge_tool_path" --output "$MERGED" \
+                       "$LOCAL" "$BASE" "$REMOTE"
+       else
+               "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"
+       fi
+       check_unchanged
+}
+
+# Check whether 'meld --output <file>' is supported
+check_meld_for_output_version () {
+       meld_path="$(git config mergetool.meld.path)"
+       meld_path="${meld_path:-meld}"
+
+       if "$meld_path" --output /dev/null --help >/dev/null 2>&1
+       then
+               meld_has_output_option=true
+       else
+               meld_has_output_option=false
+       fi
+}
diff --git a/mergetools/opendiff b/mergetools/opendiff
new file mode 100644 (file)
index 0000000..0942b2a
--- /dev/null
@@ -0,0 +1,16 @@
+diff_cmd () {
+       "$merge_tool_path" "$LOCAL" "$REMOTE" | cat
+}
+
+merge_cmd () {
+       touch "$BACKUP"
+       if $base_present
+       then
+               "$merge_tool_path" "$LOCAL" "$REMOTE" \
+                       -ancestor "$BASE" -merge "$MERGED" | cat
+       else
+               "$merge_tool_path" "$LOCAL" "$REMOTE" \
+                       -merge "$MERGED" | cat
+       fi
+       check_unchanged
+}
diff --git a/mergetools/p4merge b/mergetools/p4merge
new file mode 100644 (file)
index 0000000..1a45c1b
--- /dev/null
@@ -0,0 +1,10 @@
+diff_cmd () {
+       "$merge_tool_path" "$LOCAL" "$REMOTE"
+}
+
+merge_cmd () {
+       touch "$BACKUP"
+       $base_present || >"$BASE"
+       "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED"
+       check_unchanged
+}
diff --git a/mergetools/tkdiff b/mergetools/tkdiff
new file mode 100644 (file)
index 0000000..618c438
--- /dev/null
@@ -0,0 +1,12 @@
+diff_cmd () {
+       "$merge_tool_path" "$LOCAL" "$REMOTE"
+}
+
+merge_cmd () {
+       if $base_present
+       then
+               "$merge_tool_path" -a "$BASE" -o "$MERGED" "$LOCAL" "$REMOTE"
+       else
+               "$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE"
+       fi
+}
diff --git a/mergetools/tortoisemerge b/mergetools/tortoisemerge
new file mode 100644 (file)
index 0000000..ed7db49
--- /dev/null
@@ -0,0 +1,17 @@
+can_diff () {
+       return 1
+}
+
+merge_cmd () {
+       if $base_present
+       then
+               touch "$BACKUP"
+               "$merge_tool_path" \
+                       -base:"$BASE" -mine:"$LOCAL" \
+                       -theirs:"$REMOTE" -merged:"$MERGED"
+               check_unchanged
+       else
+               echo "TortoiseMerge cannot be used without a base" 1>&2
+               return 1
+       fi
+}
diff --git a/mergetools/vim b/mergetools/vim
new file mode 100644 (file)
index 0000000..619594a
--- /dev/null
@@ -0,0 +1,44 @@
+diff_cmd () {
+       case "$1" in
+       gvimdiff|vimdiff)
+               "$merge_tool_path" -R -f -d \
+                       -c 'wincmd l' -c 'cd $GIT_PREFIX' "$LOCAL" "$REMOTE"
+               ;;
+       gvimdiff2|vimdiff2)
+               "$merge_tool_path" -R -f -d \
+                       -c 'wincmd l' -c 'cd $GIT_PREFIX' "$LOCAL" "$REMOTE"
+               ;;
+       esac
+}
+
+merge_cmd () {
+       touch "$BACKUP"
+       case "$1" in
+       gvimdiff|vimdiff)
+               if $base_present
+               then
+                       "$merge_tool_path" -f -d -c 'wincmd J' \
+                               "$MERGED" "$LOCAL" "$BASE" "$REMOTE"
+               else
+                       "$merge_tool_path" -f -d -c 'wincmd l' \
+                               "$LOCAL" "$MERGED" "$REMOTE"
+               fi
+               ;;
+       gvimdiff2|vimdiff2)
+               "$merge_tool_path" -f -d -c 'wincmd l' \
+                       "$LOCAL" "$MERGED" "$REMOTE"
+               ;;
+       esac
+       check_unchanged
+}
+
+translate_merge_tool_path() {
+       case "$1" in
+       gvimdiff|gvimdiff2)
+               echo gvim
+               ;;
+       vimdiff|vimdiff2)
+               echo vim
+               ;;
+       esac
+}
diff --git a/mergetools/xxdiff b/mergetools/xxdiff
new file mode 100644 (file)
index 0000000..05b4433
--- /dev/null
@@ -0,0 +1,25 @@
+diff_cmd () {
+       "$merge_tool_path" \
+               -R 'Accel.Search: "Ctrl+F"' \
+               -R 'Accel.SearchForward: "Ctrl-G"' \
+               "$LOCAL" "$REMOTE"
+}
+
+merge_cmd () {
+       touch "$BACKUP"
+       if $base_present
+       then
+               "$merge_tool_path" -X --show-merged-pane \
+                       -R 'Accel.SaveAsMerged: "Ctrl-S"' \
+                       -R 'Accel.Search: "Ctrl+F"' \
+                       -R 'Accel.SearchForward: "Ctrl-G"' \
+                       --merged-file "$MERGED" "$LOCAL" "$BASE" "$REMOTE"
+       else
+               "$merge_tool_path" -X $extra \
+                       -R 'Accel.SaveAsMerged: "Ctrl-S"' \
+                       -R 'Accel.Search: "Ctrl+F"' \
+                       -R 'Accel.SearchForward: "Ctrl-G"' \
+                       --merged-file "$MERGED" "$LOCAL" "$REMOTE"
+       fi
+       check_unchanged
+}
diff --git a/notes.c b/notes.c
index f6ce8489d31e48d30dfbc6aef4edf6dbb8314234..93e9868d5d1aa536b70e981d3a4cd3c7969764d3 100644 (file)
--- a/notes.c
+++ b/notes.c
@@ -1105,7 +1105,7 @@ int remove_note(struct notes_tree *t, const unsigned char *object_sha1)
        hashcpy(l.key_sha1, object_sha1);
        hashclr(l.val_sha1);
        note_tree_remove(t, t->root, 0, &l);
-       if (is_null_sha1(l.val_sha1)) // no note was removed
+       if (is_null_sha1(l.val_sha1)) /* no note was removed */
                return 1;
        t->dirty = 1;
        return 0;
diff --git a/pager.c b/pager.c
index dac358f047550a07dd8d90b2e410feab3eebd534..975955ba82a0dbb128d6733090cd74c2b509ea81 100644 (file)
--- a/pager.c
+++ b/pager.c
@@ -11,8 +11,6 @@
  * something different on Windows.
  */
 
-static int spawned_pager;
-
 #ifndef WIN32
 static void pager_preexec(void)
 {
@@ -78,7 +76,7 @@ void setup_pager(void)
        if (!pager)
                return;
 
-       spawned_pager = 1; /* means we are emitting to terminal */
+       setenv("GIT_PAGER_IN_USE", "true", 1);
 
        /* spawn the pager */
        pager_argv[0] = pager;
@@ -109,10 +107,6 @@ void setup_pager(void)
 int pager_in_use(void)
 {
        const char *env;
-
-       if (spawned_pager)
-               return 1;
-
        env = getenv("GIT_PAGER_IN_USE");
        return env ? git_config_bool("GIT_PAGER_IN_USE", env) : 0;
 }
diff --git a/parse-options-cb.c b/parse-options-cb.c
new file mode 100644 (file)
index 0000000..6db0921
--- /dev/null
@@ -0,0 +1,125 @@
+#include "git-compat-util.h"
+#include "parse-options.h"
+#include "cache.h"
+#include "commit.h"
+#include "color.h"
+#include "string-list.h"
+
+/*----- some often used options -----*/
+
+int parse_opt_abbrev_cb(const struct option *opt, const char *arg, int unset)
+{
+       int v;
+
+       if (!arg) {
+               v = unset ? 0 : DEFAULT_ABBREV;
+       } else {
+               v = strtol(arg, (char **)&arg, 10);
+               if (*arg)
+                       return opterror(opt, "expects a numerical value", 0);
+               if (v && v < MINIMUM_ABBREV)
+                       v = MINIMUM_ABBREV;
+               else if (v > 40)
+                       v = 40;
+       }
+       *(int *)(opt->value) = v;
+       return 0;
+}
+
+int parse_opt_approxidate_cb(const struct option *opt, const char *arg,
+                            int unset)
+{
+       *(unsigned long *)(opt->value) = approxidate(arg);
+       return 0;
+}
+
+int parse_opt_color_flag_cb(const struct option *opt, const char *arg,
+                           int unset)
+{
+       int value;
+
+       if (!arg)
+               arg = unset ? "never" : (const char *)opt->defval;
+       value = git_config_colorbool(NULL, arg);
+       if (value < 0)
+               return opterror(opt,
+                       "expects \"always\", \"auto\", or \"never\"", 0);
+       *(int *)opt->value = value;
+       return 0;
+}
+
+int parse_opt_verbosity_cb(const struct option *opt, const char *arg,
+                          int unset)
+{
+       int *target = opt->value;
+
+       if (unset)
+               /* --no-quiet, --no-verbose */
+               *target = 0;
+       else if (opt->short_name == 'v') {
+               if (*target >= 0)
+                       (*target)++;
+               else
+                       *target = 1;
+       } else {
+               if (*target <= 0)
+                       (*target)--;
+               else
+                       *target = -1;
+       }
+       return 0;
+}
+
+int parse_opt_with_commit(const struct option *opt, const char *arg, int unset)
+{
+       unsigned char sha1[20];
+       struct commit *commit;
+
+       if (!arg)
+               return -1;
+       if (get_sha1(arg, sha1))
+               return error("malformed object name %s", arg);
+       commit = lookup_commit_reference(sha1);
+       if (!commit)
+               return error("no such commit %s", arg);
+       commit_list_insert(commit, opt->value);
+       return 0;
+}
+
+int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
+{
+       int *target = opt->value;
+       *target = unset ? 2 : 1;
+       return 0;
+}
+
+int parse_options_concat(struct option *dst, size_t dst_size, struct option *src)
+{
+       int i, j;
+
+       for (i = 0; i < dst_size; i++)
+               if (dst[i].type == OPTION_END)
+                       break;
+       for (j = 0; i < dst_size; i++, j++) {
+               dst[i] = src[j];
+               if (src[j].type == OPTION_END)
+                       return 0;
+       }
+       return -1;
+}
+
+int parse_opt_string_list(const struct option *opt, const char *arg, int unset)
+{
+       struct string_list *v = opt->value;
+
+       if (unset) {
+               string_list_clear(v, 0);
+               return 0;
+       }
+
+       if (!arg)
+               return -1;
+
+       string_list_append(v, xstrdup(arg));
+       return 0;
+}
index 879ea82a3158ca5e6d4ca397e0c2dd2265e15edc..503ab5d500c8ca2fe30f50601717979e752b9254 100644 (file)
@@ -3,7 +3,6 @@
 #include "cache.h"
 #include "commit.h"
 #include "color.h"
-#include "string-list.h"
 
 static int parse_options_usage(struct parse_opt_ctx_t *ctx,
                               const char * const *usagestr,
@@ -12,14 +11,14 @@ static int parse_options_usage(struct parse_opt_ctx_t *ctx,
 #define OPT_SHORT 1
 #define OPT_UNSET 2
 
-static int optbug(const struct option *opt, const char *reason)
+int optbug(const struct option *opt, const char *reason)
 {
        if (opt->long_name)
                return error("BUG: option '%s' %s", opt->long_name, reason);
        return error("BUG: switch '%c' %s", opt->short_name, reason);
 }
 
-static int opterror(const struct option *opt, const char *reason, int flags)
+int opterror(const struct option *opt, const char *reason, int flags)
 {
        if (flags & OPT_SHORT)
                return error("switch `%c' %s", opt->short_name, reason);
@@ -584,123 +583,3 @@ static int parse_options_usage(struct parse_opt_ctx_t *ctx,
        return usage_with_options_internal(ctx, usagestr, opts, 0, err);
 }
 
-
-/*----- some often used options -----*/
-#include "cache.h"
-
-int parse_opt_abbrev_cb(const struct option *opt, const char *arg, int unset)
-{
-       int v;
-
-       if (!arg) {
-               v = unset ? 0 : DEFAULT_ABBREV;
-       } else {
-               v = strtol(arg, (char **)&arg, 10);
-               if (*arg)
-                       return opterror(opt, "expects a numerical value", 0);
-               if (v && v < MINIMUM_ABBREV)
-                       v = MINIMUM_ABBREV;
-               else if (v > 40)
-                       v = 40;
-       }
-       *(int *)(opt->value) = v;
-       return 0;
-}
-
-int parse_opt_approxidate_cb(const struct option *opt, const char *arg,
-                            int unset)
-{
-       *(unsigned long *)(opt->value) = approxidate(arg);
-       return 0;
-}
-
-int parse_opt_color_flag_cb(const struct option *opt, const char *arg,
-                           int unset)
-{
-       int value;
-
-       if (!arg)
-               arg = unset ? "never" : (const char *)opt->defval;
-       value = git_config_colorbool(NULL, arg, -1);
-       if (value < 0)
-               return opterror(opt,
-                       "expects \"always\", \"auto\", or \"never\"", 0);
-       *(int *)opt->value = value;
-       return 0;
-}
-
-int parse_opt_verbosity_cb(const struct option *opt, const char *arg,
-                          int unset)
-{
-       int *target = opt->value;
-
-       if (unset)
-               /* --no-quiet, --no-verbose */
-               *target = 0;
-       else if (opt->short_name == 'v') {
-               if (*target >= 0)
-                       (*target)++;
-               else
-                       *target = 1;
-       } else {
-               if (*target <= 0)
-                       (*target)--;
-               else
-                       *target = -1;
-       }
-       return 0;
-}
-
-int parse_opt_with_commit(const struct option *opt, const char *arg, int unset)
-{
-       unsigned char sha1[20];
-       struct commit *commit;
-
-       if (!arg)
-               return -1;
-       if (get_sha1(arg, sha1))
-               return error("malformed object name %s", arg);
-       commit = lookup_commit_reference(sha1);
-       if (!commit)
-               return error("no such commit %s", arg);
-       commit_list_insert(commit, opt->value);
-       return 0;
-}
-
-int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
-{
-       int *target = opt->value;
-       *target = unset ? 2 : 1;
-       return 0;
-}
-
-int parse_options_concat(struct option *dst, size_t dst_size, struct option *src)
-{
-       int i, j;
-
-       for (i = 0; i < dst_size; i++)
-               if (dst[i].type == OPTION_END)
-                       break;
-       for (j = 0; i < dst_size; i++, j++) {
-               dst[i] = src[j];
-               if (src[j].type == OPTION_END)
-                       return 0;
-       }
-       return -1;
-}
-
-int parse_opt_string_list(const struct option *opt, const char *arg, int unset)
-{
-       struct string_list *v = opt->value;
-
-       if (unset) {
-               string_list_clear(v, 0);
-               return 0;
-       }
-
-       if (!arg)
-               return -1;
-
-       string_list_append(v, xstrdup(arg));
-       return 0;
-}
index 05eb09b878fd94cfcc549622a35a0b897fa56cb9..59e0b524bdcbe1c061f49b8e5f3c6365a9f2eba4 100644 (file)
@@ -165,6 +165,8 @@ extern NORETURN void usage_msg_opt(const char *msg,
                                   const char * const *usagestr,
                                   const struct option *options);
 
+extern int optbug(const struct option *opt, const char *reason);
+extern int opterror(const struct option *opt, const char *reason, int flags);
 /*----- incremental advanced APIs -----*/
 
 enum {
diff --git a/path.c b/path.c
index 4d73cc9cd26708b4cb41e86fb319b93f526cb2f2..6f3f5d56c0ed76f50d1aa37646d18ae280f1edbb 100644 (file)
--- a/path.c
+++ b/path.c
@@ -139,7 +139,7 @@ char *git_path_submodule(const char *path, const char *fmt, ...)
                strbuf_addch(&buf, '/');
        strbuf_addstr(&buf, ".git");
 
-       git_dir = read_gitfile_gently(buf.buf);
+       git_dir = read_gitfile(buf.buf);
        if (git_dir) {
                strbuf_reset(&buf);
                strbuf_addstr(&buf, git_dir);
diff --git a/quote.c b/quote.c
index 63d3b018183abc05a5231dfd7e134dd7394f7a9b..532fd3b7b7a0c7b6766a7af742b0c7362f307221 100644 (file)
--- a/quote.c
+++ b/quote.c
@@ -325,8 +325,12 @@ static const char *path_relative(const char *in, int len,
 
        if (len < 0)
                len = strlen(in);
-       if (prefix && prefix_len < 0)
-               prefix_len = strlen(prefix);
+       if (prefix_len < 0) {
+               if (prefix)
+                       prefix_len = strlen(prefix);
+               else
+                       prefix_len = 0;
+       }
 
        off = 0;
        i = 0;
index 46a9e60708ad98db5299f9242db05de5915a0ebb..01a0e2505121f10544ee03948e545d07c24f366e 100644 (file)
@@ -1084,7 +1084,7 @@ static void show_file(const char * fmt, const char * name, int in_porcelain,
 {
        if (in_porcelain && *first && header_msg) {
                printf("%s\n", header_msg);
-               *first=0;
+               *first = 0;
        }
        printf(fmt, name);
 }
diff --git a/refs.c b/refs.c
index 6f313a9e0cdec2400a993cf7c3dfa87529a493ae..a615043b34cd6d0507d8a30f7bd69445ec9f2456 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -451,7 +451,7 @@ int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *re
        memcpy(gitdir + len, "/.git", 6);
        len += 5;
 
-       tmp = read_gitfile_gently(gitdir);
+       tmp = read_gitfile(gitdir);
        if (tmp) {
                free(gitdir);
                len = strlen(tmp);
@@ -862,7 +862,7 @@ int for_each_rawref(each_ref_fn fn, void *cb_data)
 
 static inline int bad_ref_char(int ch)
 {
-       if (((unsigned) ch) <= ' ' ||
+       if (((unsigned) ch) <= ' ' || ch == 0x7f ||
            ch == '~' || ch == '^' || ch == ':' || ch == '\\')
                return 1;
        /* 2.13 Pattern Matching Notation */
index 5798aa57b6bf7f47f4fd9c8b943602faaeee4b95..b8cf45a7dd439b83c80bcf7a397e1b8e34c70f67 100644 (file)
@@ -762,9 +762,7 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs)
                argv[argc++] = "--thin";
        if (options.dry_run)
                argv[argc++] = "--dry-run";
-       if (options.verbosity < 0)
-               argv[argc++] = "--quiet";
-       else if (options.verbosity > 1)
+       if (options.verbosity > 1)
                argv[argc++] = "--verbose";
        argv[argc++] = url;
        for (i = 0; i < nr_spec; i++)
index c46cfaa3e4d2f06fd67ccd71c7ba47891796fd60..9bae329c153f33b5aa39f2dab2748dc56b2c3824 100644 (file)
@@ -40,6 +40,47 @@ char *path_name(const struct name_path *path, const char *name)
        return n;
 }
 
+static int show_path_component_truncated(FILE *out, const char *name, int len)
+{
+       int cnt;
+       for (cnt = 0; cnt < len; cnt++) {
+               int ch = name[cnt];
+               if (!ch || ch == '\n')
+                       return -1;
+               fputc(ch, out);
+       }
+       return len;
+}
+
+static int show_path_truncated(FILE *out, const struct name_path *path)
+{
+       int emitted, ours;
+
+       if (!path)
+               return 0;
+       emitted = show_path_truncated(out, path->up);
+       if (emitted < 0)
+               return emitted;
+       if (emitted)
+               fputc('/', out);
+       ours = show_path_component_truncated(out, path->elem, path->elem_len);
+       if (ours < 0)
+               return ours;
+       return ours || emitted;
+}
+
+void show_object_with_name(FILE *out, struct object *obj, const struct name_path *path, const char *component)
+{
+       struct name_path leaf;
+       leaf.up = (struct name_path *)path;
+       leaf.elem = component;
+       leaf.elem_len = strlen(component);
+
+       fprintf(out, "%s ", sha1_to_hex(obj->sha1));
+       show_path_truncated(out, &leaf);
+       fputc('\n', out);
+}
+
 void add_object(struct object *obj,
                struct object_array *p,
                struct name_path *path,
@@ -729,12 +770,16 @@ static void limit_to_ancestry(struct commit_list *bottom, struct commit_list *li
  * to filter the result of "A..B" further to the ones that can actually
  * reach A.
  */
-static struct commit_list *collect_bottom_commits(struct commit_list *list)
+static struct commit_list *collect_bottom_commits(struct rev_info *revs)
 {
-       struct commit_list *elem, *bottom = NULL;
-       for (elem = list; elem; elem = elem->next)
-               if (elem->item->object.flags & UNINTERESTING)
-                       commit_list_insert(elem->item, &bottom);
+       struct commit_list *bottom = NULL;
+       int i;
+       for (i = 0; i < revs->cmdline.nr; i++) {
+               struct rev_cmdline_entry *elem = &revs->cmdline.rev[i];
+               if ((elem->flags & UNINTERESTING) &&
+                   elem->item->type == OBJ_COMMIT)
+                       commit_list_insert((struct commit *)elem->item, &bottom);
+       }
        return bottom;
 }
 
@@ -765,7 +810,7 @@ static int limit_list(struct rev_info *revs)
        struct commit_list *bottom = NULL;
 
        if (revs->ancestry_path) {
-               bottom = collect_bottom_commits(list);
+               bottom = collect_bottom_commits(revs);
                if (!bottom)
                        die("--ancestry-path given but there are no bottom commits");
        }
@@ -822,6 +867,23 @@ static int limit_list(struct rev_info *revs)
        return 0;
 }
 
+static void add_rev_cmdline(struct rev_info *revs,
+                           struct object *item,
+                           const char *name,
+                           int whence,
+                           unsigned flags)
+{
+       struct rev_cmdline_info *info = &revs->cmdline;
+       int nr = info->nr;
+
+       ALLOC_GROW(info->rev, nr + 1, info->alloc);
+       info->rev[nr].item = item;
+       info->rev[nr].name = name;
+       info->rev[nr].whence = whence;
+       info->rev[nr].flags = flags;
+       info->nr++;
+}
+
 struct all_refs_cb {
        int all_flags;
        int warned_bad_reflog;
@@ -834,6 +896,7 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, int flag,
        struct all_refs_cb *cb = cb_data;
        struct object *object = get_reference(cb->all_revs, path, sha1,
                                              cb->all_flags);
+       add_rev_cmdline(cb->all_revs, object, path, REV_CMD_REF, cb->all_flags);
        add_pending_object(cb->all_revs, object, path);
        return 0;
 }
@@ -860,6 +923,7 @@ static void handle_one_reflog_commit(unsigned char *sha1, void *cb_data)
                struct object *o = parse_object(sha1);
                if (o) {
                        o->flags |= cb->all_flags;
+                       /* ??? CMDLINEFLAGS ??? */
                        add_pending_object(cb->all_revs, o, "");
                }
                else if (!cb->warned_bad_reflog) {
@@ -896,12 +960,13 @@ static void handle_reflog(struct rev_info *revs, unsigned flags)
        for_each_reflog(handle_one_reflog, &cb);
 }
 
-static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
+static int add_parents_only(struct rev_info *revs, const char *arg_, int flags)
 {
        unsigned char sha1[20];
        struct object *it;
        struct commit *commit;
        struct commit_list *parents;
+       const char *arg = arg_;
 
        if (*arg == '^') {
                flags ^= UNINTERESTING;
@@ -925,6 +990,7 @@ static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
        for (parents = commit->parents; parents; parents = parents->next) {
                it = &parents->item->object;
                it->flags |= flags;
+               add_rev_cmdline(revs, it, arg_, REV_CMD_PARENTS_ONLY, flags);
                add_pending_object(revs, it, arg);
        }
        return 1;
@@ -1018,7 +1084,7 @@ static void prepare_show_merge(struct rev_info *revs)
        revs->limited = 1;
 }
 
-int handle_revision_arg(const char *arg, struct rev_info *revs,
+int handle_revision_arg(const char *arg_, struct rev_info *revs,
                        int flags,
                        int cant_be_filename)
 {
@@ -1027,6 +1093,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
        struct object *object;
        unsigned char sha1[20];
        int local_flags;
+       const char *arg = arg_;
 
        dotdot = strstr(arg, "..");
        if (dotdot) {
@@ -1035,6 +1102,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
                const char *this = arg;
                int symmetric = *next == '.';
                unsigned int flags_exclude = flags ^ UNINTERESTING;
+               unsigned int a_flags;
 
                *dotdot = 0;
                next += symmetric;
@@ -1069,10 +1137,15 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
                                add_pending_commit_list(revs, exclude,
                                                        flags_exclude);
                                free_commit_list(exclude);
-                               a->object.flags |= flags | SYMMETRIC_LEFT;
+                               a_flags = flags | SYMMETRIC_LEFT;
                        } else
-                               a->object.flags |= flags_exclude;
+                               a_flags = flags_exclude;
+                       a->object.flags |= a_flags;
                        b->object.flags |= flags;
+                       add_rev_cmdline(revs, &a->object, this,
+                                       REV_CMD_LEFT, a_flags);
+                       add_rev_cmdline(revs, &b->object, next,
+                                       REV_CMD_RIGHT, flags);
                        add_pending_object(revs, &a->object, this);
                        add_pending_object(revs, &b->object, next);
                        return 0;
@@ -1103,6 +1176,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
        if (!cant_be_filename)
                verify_non_filename(revs->prefix, arg);
        object = get_reference(revs, arg, sha1, flags ^ local_flags);
+       add_rev_cmdline(revs, object, arg_, REV_CMD_REV, flags ^ local_flags);
        add_pending_object_with_mode(revs, object, arg, mode);
        return 0;
 }
@@ -1342,6 +1416,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->tree_objects = 1;
                revs->blob_objects = 1;
                revs->edge_hint = 1;
+       } else if (!strcmp(arg, "--verify-objects")) {
+               revs->tag_objects = 1;
+               revs->tree_objects = 1;
+               revs->blob_objects = 1;
+               revs->verify_objects = 1;
        } else if (!strcmp(arg, "--unpacked")) {
                revs->unpacked = 1;
        } else if (!prefixcmp(arg, "--unpacked=")) {
index 3d64adad18e2c889b7a7b7367f99c239f958dc70..754f31b1cda81c474f71ab56f8d82e260adee968 100644 (file)
@@ -24,6 +24,23 @@ struct rev_info;
 struct log_info;
 struct string_list;
 
+struct rev_cmdline_info {
+       unsigned int nr;
+       unsigned int alloc;
+       struct rev_cmdline_entry {
+               struct object *item;
+               const char *name;
+               enum {
+                       REV_CMD_REF,
+                       REV_CMD_PARENTS_ONLY,
+                       REV_CMD_LEFT,
+                       REV_CMD_RIGHT,
+                       REV_CMD_REV
+               } whence;
+               unsigned flags;
+       } *rev;
+};
+
 struct rev_info {
        /* Starting list */
        struct commit_list *commits;
@@ -32,6 +49,9 @@ struct rev_info {
        /* Parents of shown commits */
        struct object_array boundary_commits;
 
+       /* The end-points specified by the end user */
+       struct rev_cmdline_info cmdline;
+
        /* Basic information */
        const char *prefix;
        const char *def;
@@ -53,6 +73,7 @@ struct rev_info {
                        tag_objects:1,
                        tree_objects:1,
                        blob_objects:1,
+                       verify_objects:1,
                        edge_hint:1,
                        limited:1,
                        unpacked:1,
@@ -185,6 +206,8 @@ struct name_path {
 
 char *path_name(const struct name_path *path, const char *name);
 
+extern void show_object_with_name(FILE *, struct object *, const struct name_path *, const char *);
+
 extern void add_object(struct object *obj,
                       struct object_array *p,
                       struct name_path *path,
diff --git a/sequencer.c b/sequencer.c
new file mode 100644 (file)
index 0000000..bc2c046
--- /dev/null
@@ -0,0 +1,19 @@
+#include "cache.h"
+#include "sequencer.h"
+#include "strbuf.h"
+#include "dir.h"
+
+void remove_sequencer_state(int aggressive)
+{
+       struct strbuf seq_dir = STRBUF_INIT;
+       struct strbuf seq_old_dir = STRBUF_INIT;
+
+       strbuf_addf(&seq_dir, "%s", git_path(SEQ_DIR));
+       strbuf_addf(&seq_old_dir, "%s", git_path(SEQ_OLD_DIR));
+       remove_dir_recursively(&seq_old_dir, 0);
+       rename(git_path(SEQ_DIR), git_path(SEQ_OLD_DIR));
+       if (aggressive)
+               remove_dir_recursively(&seq_old_dir, 0);
+       strbuf_release(&seq_dir);
+       strbuf_release(&seq_old_dir);
+}
diff --git a/sequencer.h b/sequencer.h
new file mode 100644 (file)
index 0000000..905d295
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef SEQUENCER_H
+#define SEQUENCER_H
+
+#define SEQ_DIR                "sequencer"
+#define SEQ_OLD_DIR    "sequencer-old"
+#define SEQ_HEAD_FILE  "sequencer/head"
+#define SEQ_TODO_FILE  "sequencer/todo"
+#define SEQ_OPTS_FILE  "sequencer/opts"
+
+/*
+ * Removes SEQ_OLD_DIR and renames SEQ_DIR to SEQ_OLD_DIR, ignoring
+ * any errors.  Intended to be used by 'git reset'.
+ *
+ * With the aggressive flag, it additionally removes SEQ_OLD_DIR,
+ * ignoring any errors.  Inteded to be used by the sequencer's
+ * '--reset' subcommand.
+ */
+void remove_sequencer_state(int aggressive);
+
+#endif
diff --git a/setup.c b/setup.c
index 2c51a9a4c7665ce786e58e312c55f9f8b4bca6ae..27c1d4787a2c2efd8420a225b43e36b76464ce42 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -40,34 +40,6 @@ char *prefix_path(const char *prefix, int len, const char *path)
        return sanitized;
 }
 
-/*
- * Unlike prefix_path, this should be used if the named file does
- * not have to interact with index entry; i.e. name of a random file
- * on the filesystem.
- */
-const char *prefix_filename(const char *pfx, int pfx_len, const char *arg)
-{
-       static char path[PATH_MAX];
-#ifndef WIN32
-       if (!pfx_len || is_absolute_path(arg))
-               return arg;
-       memcpy(path, pfx, pfx_len);
-       strcpy(path + pfx_len, arg);
-#else
-       char *p;
-       /* don't add prefix to absolute paths, but still replace '\' by '/' */
-       if (is_absolute_path(arg))
-               pfx_len = 0;
-       else if (pfx_len)
-               memcpy(path, pfx, pfx_len);
-       strcpy(path + pfx_len, arg);
-       for (p = path + pfx_len; *p; p++)
-               if (*p == '\\')
-                       *p = '/';
-#endif
-       return path;
-}
-
 int check_filename(const char *prefix, const char *arg)
 {
        const char *name;
@@ -407,7 +379,7 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
  * Try to read the location of the git directory from the .git file,
  * return path to git directory if found.
  */
-const char *read_gitfile_gently(const char *path)
+const char *read_gitfile(const char *path)
 {
        char *buf;
        char *dir;
@@ -469,7 +441,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,
        if (PATH_MAX - 40 < strlen(gitdirenv))
                die("'$%s' too big", GIT_DIR_ENVIRONMENT);
 
-       gitfile = (char*)read_gitfile_gently(gitdirenv);
+       gitfile = (char*)read_gitfile(gitdirenv);
        if (gitfile) {
                gitfile = xstrdup(gitfile);
                gitdirenv = gitfile;
@@ -693,7 +665,7 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
        if (one_filesystem)
                current_device = get_device_or_die(".", NULL);
        for (;;) {
-               gitfile = (char*)read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT);
+               gitfile = (char*)read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT);
                if (gitfile)
                        gitdirenv = gitfile = xstrdup(gitfile);
                else {
index d5616dca0809bdb9fd6a7a1980b92ded9ae6e230..34013014442e18bd02ae0ce33df34a89ec8d6171 100644 (file)
@@ -248,27 +248,30 @@ static int link_alt_odb_entry(const char * entry, int len, const char * relative
        const char *objdir = get_object_directory();
        struct alternate_object_database *ent;
        struct alternate_object_database *alt;
-       /* 43 = 40-byte + 2 '/' + terminating NUL */
-       int pfxlen = len;
-       int entlen = pfxlen + 43;
-       int base_len = -1;
+       int pfxlen, entlen;
+       struct strbuf pathbuf = STRBUF_INIT;
 
        if (!is_absolute_path(entry) && relative_base) {
-               /* Relative alt-odb */
-               if (base_len < 0)
-                       base_len = strlen(relative_base) + 1;
-               entlen += base_len;
-               pfxlen += base_len;
+               strbuf_addstr(&pathbuf, real_path(relative_base));
+               strbuf_addch(&pathbuf, '/');
        }
-       ent = xmalloc(sizeof(*ent) + entlen);
+       strbuf_add(&pathbuf, entry, len);
 
-       if (!is_absolute_path(entry) && relative_base) {
-               memcpy(ent->base, relative_base, base_len - 1);
-               ent->base[base_len - 1] = '/';
-               memcpy(ent->base + base_len, entry, len);
-       }
-       else
-               memcpy(ent->base, entry, pfxlen);
+       normalize_path_copy(pathbuf.buf, pathbuf.buf);
+
+       pfxlen = strlen(pathbuf.buf);
+
+       /*
+        * The trailing slash after the directory name is given by
+        * this function at the end. Remove duplicates.
+        */
+       while (pfxlen && pathbuf.buf[pfxlen-1] == '/')
+               pfxlen -= 1;
+
+       entlen = pfxlen + 43; /* '/' + 2 hex + '/' + 38 hex + NUL */
+       ent = xmalloc(sizeof(*ent) + entlen);
+       memcpy(ent->base, pathbuf.buf, pfxlen);
+       strbuf_release(&pathbuf);
 
        ent->name = ent->base + pfxlen + 1;
        ent->base[pfxlen + 3] = '/';
@@ -380,7 +383,7 @@ void add_to_alternates_file(const char *reference)
 {
        struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
        int fd = hold_lock_file_for_append(lock, git_path("objects/info/alternates"), LOCK_DIE_ON_ERROR);
-       char *alt = mkpath("%s/objects\n", reference);
+       char *alt = mkpath("%s\n", reference);
        write_or_die(fd, alt, strlen(alt));
        if (commit_lock_file(lock))
                die("could not close alternates file");
@@ -1217,14 +1220,34 @@ static int experimental_loose_object(unsigned char *map)
        unsigned int word;
 
        /*
-        * Is it a zlib-compressed buffer? If so, the first byte
-        * must be 0x78 (15-bit window size, deflated), and the
-        * first 16-bit word is evenly divisible by 31. If so,
-        * we are looking at the official format, not the experimental
-        * one.
+        * We must determine if the buffer contains the standard
+        * zlib-deflated stream or the experimental format based
+        * on the in-pack object format. Compare the header byte
+        * for each format:
+        *
+        * RFC1950 zlib w/ deflate : 0www1000 : 0 <= www <= 7
+        * Experimental pack-based : Stttssss : ttt = 1,2,3,4
+        *
+        * If bit 7 is clear and bits 0-3 equal 8, the buffer MUST be
+        * in standard loose-object format, UNLESS it is a Git-pack
+        * format object *exactly* 8 bytes in size when inflated.
+        *
+        * However, RFC1950 also specifies that the 1st 16-bit word
+        * must be divisible by 31 - this checksum tells us our buffer
+        * is in the standard format, giving a false positive only if
+        * the 1st word of the Git-pack format object happens to be
+        * divisible by 31, ie:
+        *      ((byte0 * 256) + byte1) % 31 = 0
+        *   =>        0ttt10000www1000 % 31 = 0
+        *
+        * As it happens, this case can only arise for www=3 & ttt=1
+        * - ie, a Commit object, which would have to be 8 bytes in
+        * size. As no Commit can be that small, we find that the
+        * combination of these two criteria (bitmask & checksum)
+        * can always correctly determine the buffer format.
         */
        word = (map[0] << 8) + map[1];
-       if (map[0] == 0x78 && !(word % 31))
+       if ((map[0] & 0x8F) == 0x08 && !(word % 31))
                return 0;
        else
                return 1;
index ff5992acc971ac5a67fa3d4def1e0c064b90519f..653b0659be8416a220268ed2bc60694140c8d472 100644 (file)
@@ -501,12 +501,6 @@ struct object *peel_to_type(const char *name, int namelen,
 {
        if (name && !namelen)
                namelen = strlen(name);
-       if (!o) {
-               unsigned char sha1[20];
-               if (get_sha1_1(name, namelen, sha1))
-                       return NULL;
-               o = parse_object(sha1);
-       }
        while (1) {
                if (!o || (!o->parsed && !parse_object(o->sha1)))
                        return NULL;
index 1a7df12e8f233863cd931a960d453d54050f5584..9ff1b597c995780026a32a92fab78a780d60329a 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -30,10 +30,8 @@ void strbuf_init(struct strbuf *sb, size_t hint)
 {
        sb->alloc = sb->len = 0;
        sb->buf = strbuf_slopbuf;
-       if (hint) {
+       if (hint)
                strbuf_grow(sb, hint);
-               sb->buf[0] = '\0';
-       }
 }
 
 void strbuf_release(struct strbuf *sb)
@@ -65,12 +63,15 @@ void strbuf_attach(struct strbuf *sb, void *buf, size_t len, size_t alloc)
 
 void strbuf_grow(struct strbuf *sb, size_t extra)
 {
+       int new_buf = !sb->alloc;
        if (unsigned_add_overflows(extra, 1) ||
            unsigned_add_overflows(sb->len, extra + 1))
                die("you want to use way too much memory");
-       if (!sb->alloc)
+       if (new_buf)
                sb->buf = NULL;
        ALLOC_GROW(sb->buf, sb->len + extra + 1, sb->alloc);
+       if (new_buf)
+               sb->buf[0] = '\0';
 }
 
 void strbuf_trim(struct strbuf *sb)
index 51681189e8380cc43ed26d35526a6dff78f1a24c..d9810aba421cbbc844b45ea5ca713a480a699eb2 100644 (file)
@@ -185,3 +185,12 @@ int unsorted_string_list_has_string(struct string_list *list,
        return unsorted_string_list_lookup(list, string) != NULL;
 }
 
+void unsorted_string_list_delete_item(struct string_list *list, int i, int free_util)
+{
+       if (list->strdup_strings)
+               free(list->items[i].string);
+       if (free_util)
+               free(list->items[i].util);
+       list->items[i] = list->items[list->nr-1];
+       list->nr--;
+}
index bda69839832d2030c15bbd86c4d8d79aa3cc4785..0684cb73bfd27846182479a4e192d682a44f201a 100644 (file)
@@ -44,4 +44,5 @@ void sort_string_list(struct string_list *list);
 int unsorted_string_list_has_string(struct string_list *list, const char *string);
 struct string_list_item *unsorted_string_list_lookup(struct string_list *list,
                                                     const char *string);
+void unsorted_string_list_delete_item(struct string_list *list, int i, int free_util);
 #endif /* STRING_LIST_H */
index 1ba9646d3484fe2a11c1faba339c4e333186085e..08756387e207700861cfa6dac039334ecd53c7a1 100644 (file)
@@ -8,12 +8,17 @@
 #include "diffcore.h"
 #include "refs.h"
 #include "string-list.h"
+#include "sha1-array.h"
 
 static struct string_list config_name_for_path;
 static struct string_list config_fetch_recurse_submodules_for_name;
 static struct string_list config_ignore_for_name;
 static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND;
 static struct string_list changed_submodule_paths;
+static int initialized_fetch_ref_tips;
+static struct sha1_array ref_tips_before_fetch;
+static struct sha1_array ref_tips_after_fetch;
+
 /*
  * The following flag is set if the .gitmodules file is unmerged. We then
  * disable recursion for all submodules where .git/config doesn't have a
@@ -32,7 +37,7 @@ static int add_submodule_odb(const char *path)
        const char *git_dir;
 
        strbuf_addf(&objects_directory, "%s/.git", path);
-       git_dir = read_gitfile_gently(objects_directory.buf);
+       git_dir = read_gitfile(objects_directory.buf);
        if (git_dir) {
                strbuf_reset(&objects_directory);
                strbuf_addstr(&objects_directory, git_dir);
@@ -308,6 +313,114 @@ void set_config_fetch_recurse_submodules(int value)
        config_fetch_recurse_submodules = value;
 }
 
+static int has_remote(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
+{
+       return 1;
+}
+
+static int submodule_needs_pushing(const char *path, const unsigned char sha1[20])
+{
+       if (add_submodule_odb(path) || !lookup_commit_reference(sha1))
+               return 0;
+
+       if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) {
+               struct child_process cp;
+               const char *argv[] = {"rev-list", NULL, "--not", "--remotes", "-n", "1" , NULL};
+               struct strbuf buf = STRBUF_INIT;
+               int needs_pushing = 0;
+
+               argv[1] = sha1_to_hex(sha1);
+               memset(&cp, 0, sizeof(cp));
+               cp.argv = argv;
+               cp.env = local_repo_env;
+               cp.git_cmd = 1;
+               cp.no_stdin = 1;
+               cp.out = -1;
+               cp.dir = path;
+               if (start_command(&cp))
+                       die("Could not run 'git rev-list %s --not --remotes -n 1' command in submodule %s",
+                               sha1_to_hex(sha1), path);
+               if (strbuf_read(&buf, cp.out, 41))
+                       needs_pushing = 1;
+               finish_command(&cp);
+               close(cp.out);
+               strbuf_release(&buf);
+               return needs_pushing;
+       }
+
+       return 0;
+}
+
+static void collect_submodules_from_diff(struct diff_queue_struct *q,
+                                        struct diff_options *options,
+                                        void *data)
+{
+       int i;
+       int *needs_pushing = data;
+
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               if (!S_ISGITLINK(p->two->mode))
+                       continue;
+               if (submodule_needs_pushing(p->two->path, p->two->sha1)) {
+                       *needs_pushing = 1;
+                       break;
+               }
+       }
+}
+
+
+static void commit_need_pushing(struct commit *commit, struct commit_list *parent, int *needs_pushing)
+{
+       const unsigned char (*parents)[20];
+       unsigned int i, n;
+       struct rev_info rev;
+
+       n = commit_list_count(parent);
+       parents = xmalloc(n * sizeof(*parents));
+
+       for (i = 0; i < n; i++) {
+               hashcpy((unsigned char *)(parents + i), parent->item->object.sha1);
+               parent = parent->next;
+       }
+
+       init_revisions(&rev, NULL);
+       rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
+       rev.diffopt.format_callback = collect_submodules_from_diff;
+       rev.diffopt.format_callback_data = needs_pushing;
+       diff_tree_combined(commit->object.sha1, parents, n, 1, &rev);
+
+       free(parents);
+}
+
+int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remotes_name)
+{
+       struct rev_info rev;
+       struct commit *commit;
+       const char *argv[] = {NULL, NULL, "--not", "NULL", NULL};
+       int argc = ARRAY_SIZE(argv) - 1;
+       char *sha1_copy;
+       int needs_pushing = 0;
+       struct strbuf remotes_arg = STRBUF_INIT;
+
+       strbuf_addf(&remotes_arg, "--remotes=%s", remotes_name);
+       init_revisions(&rev, NULL);
+       sha1_copy = xstrdup(sha1_to_hex(new_sha1));
+       argv[1] = sha1_copy;
+       argv[3] = remotes_arg.buf;
+       setup_revisions(argc, argv, &rev, NULL);
+       if (prepare_revision_walk(&rev))
+               die("revision walk setup failed");
+
+       while ((commit = get_revision(&rev)) && !needs_pushing)
+               commit_need_pushing(commit, commit->parents, &needs_pushing);
+
+       free(sha1_copy);
+       strbuf_release(&remotes_arg);
+
+       return needs_pushing;
+}
+
 static int is_submodule_commit_present(const char *path, unsigned char sha1[20])
 {
        int is_present = 0;
@@ -366,16 +479,76 @@ static void submodule_collect_changed_cb(struct diff_queue_struct *q,
        }
 }
 
+static int add_sha1_to_array(const char *ref, const unsigned char *sha1,
+                            int flags, void *data)
+{
+       sha1_array_append(data, sha1);
+       return 0;
+}
+
 void check_for_new_submodule_commits(unsigned char new_sha1[20])
+{
+       if (!initialized_fetch_ref_tips) {
+               for_each_ref(add_sha1_to_array, &ref_tips_before_fetch);
+               initialized_fetch_ref_tips = 1;
+       }
+
+       sha1_array_append(&ref_tips_after_fetch, new_sha1);
+}
+
+struct argv_array {
+       const char **argv;
+       unsigned int argc;
+       unsigned int alloc;
+};
+
+static void init_argv(struct argv_array *array)
+{
+       array->argv = NULL;
+       array->argc = 0;
+       array->alloc = 0;
+}
+
+static void push_argv(struct argv_array *array, const char *value)
+{
+       ALLOC_GROW(array->argv, array->argc + 2, array->alloc);
+       array->argv[array->argc++] = xstrdup(value);
+       array->argv[array->argc] = NULL;
+}
+
+static void clear_argv(struct argv_array *array)
+{
+       int i;
+       for (i = 0; i < array->argc; i++)
+               free((char **)array->argv[i]);
+       free(array->argv);
+       init_argv(array);
+}
+
+static void add_sha1_to_argv(const unsigned char sha1[20], void *data)
+{
+       push_argv(data, sha1_to_hex(sha1));
+}
+
+static void calculate_changed_submodule_paths(void)
 {
        struct rev_info rev;
        struct commit *commit;
-       const char *argv[] = {NULL, NULL, "--not", "--all", NULL};
-       int argc = ARRAY_SIZE(argv) - 1;
+       struct argv_array argv;
+
+       /* No need to check if there are no submodules configured */
+       if (!config_name_for_path.nr)
+               return;
 
        init_revisions(&rev, NULL);
-       argv[1] = xstrdup(sha1_to_hex(new_sha1));
-       setup_revisions(argc, argv, &rev, NULL);
+       init_argv(&argv);
+       push_argv(&argv, "--"); /* argv[0] program name */
+       sha1_array_for_each_unique(&ref_tips_after_fetch,
+                                  add_sha1_to_argv, &argv);
+       push_argv(&argv, "--not");
+       sha1_array_for_each_unique(&ref_tips_before_fetch,
+                                  add_sha1_to_argv, &argv);
+       setup_revisions(argv.argc, argv.argv, &rev, NULL);
        if (prepare_revision_walk(&rev))
                die("revision walk setup failed");
 
@@ -399,7 +572,11 @@ void check_for_new_submodule_commits(unsigned char new_sha1[20])
                        parent = parent->next;
                }
        }
-       free((char *)argv[1]);
+
+       clear_argv(&argv);
+       sha1_array_clear(&ref_tips_before_fetch);
+       sha1_array_clear(&ref_tips_after_fetch);
+       initialized_fetch_ref_tips = 0;
 }
 
 int fetch_populated_submodules(int num_options, const char **options,
@@ -433,6 +610,8 @@ int fetch_populated_submodules(int num_options, const char **options,
        cp.git_cmd = 1;
        cp.no_stdin = 1;
 
+       calculate_changed_submodule_paths();
+
        for (i = 0; i < active_nr; i++) {
                struct strbuf submodule_path = STRBUF_INIT;
                struct strbuf submodule_git_dir = STRBUF_INIT;
@@ -479,7 +658,7 @@ int fetch_populated_submodules(int num_options, const char **options,
                strbuf_addf(&submodule_path, "%s/%s", work_tree, ce->name);
                strbuf_addf(&submodule_git_dir, "%s/.git", submodule_path.buf);
                strbuf_addf(&submodule_prefix, "%s%s/", prefix, ce->name);
-               git_dir = read_gitfile_gently(submodule_git_dir.buf);
+               git_dir = read_gitfile(submodule_git_dir.buf);
                if (!git_dir)
                        git_dir = submodule_git_dir.buf;
                if (is_directory(git_dir)) {
@@ -517,7 +696,7 @@ unsigned is_submodule_modified(const char *path, int ignore_untracked)
        const char *git_dir;
 
        strbuf_addf(&buf, "%s/.git", path);
-       git_dir = read_gitfile_gently(buf.buf);
+       git_dir = read_gitfile(buf.buf);
        if (!git_dir)
                git_dir = buf.buf;
        if (!is_directory(git_dir)) {
index 5350b0d5a0d16e12e96b983e6510cb1d4098f962..799c22d6c6a459756983420960bc099da20336b3 100644 (file)
@@ -29,5 +29,6 @@ int fetch_populated_submodules(int num_options, const char **options,
 unsigned is_submodule_modified(const char *path, int ignore_untracked);
 int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],
                    const unsigned char a[20], const unsigned char b[20]);
+int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remotes_name);
 
 #endif
diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh
new file mode 100755 (executable)
index 0000000..05824fa
--- /dev/null
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+gpg_version=`gpg --version 2>&1`
+if test $? = 127; then
+       say "You do not seem to have gpg installed"
+else
+       # As said here: http://www.gnupg.org/documentation/faqs.html#q6.19
+       # the gpg version 1.0.6 didn't parse trust packets correctly, so for
+       # that version, creation of signed tags using the generated key fails.
+       case "$gpg_version" in
+       'gpg (GnuPG) 1.0.6'*)
+               say "Your version of gpg (1.0.6) is too buggy for testing"
+               ;;
+       *)
+               # key generation info: gpg --homedir t/lib-gpg --gen-key
+               # Type DSA and Elgamal, size 2048 bits, no expiration date.
+               # Name and email: C O Mitter <committer@example.com>
+               # No password given, to enable non-interactive operation.
+               cp -R "$TEST_DIRECTORY"/lib-gpg ./gpghome
+               chmod 0700 gpghome
+               GNUPGHOME="$(pwd)/gpghome"
+               export GNUPGHOME
+               test_set_prereq GPG
+               ;;
+       esac
+fi
+
+sanitize_pgp() {
+       perl -ne '
+               /^-----END PGP/ and $in_pgp = 0;
+               print unless $in_pgp;
+               /^-----BEGIN PGP/ and $in_pgp = 1;
+       '
+}
diff --git a/t/lib-gpg/pubring.gpg b/t/lib-gpg/pubring.gpg
new file mode 100644 (file)
index 0000000..83855fa
Binary files /dev/null and b/t/lib-gpg/pubring.gpg differ
diff --git a/t/lib-gpg/random_seed b/t/lib-gpg/random_seed
new file mode 100644 (file)
index 0000000..8fed133
Binary files /dev/null and b/t/lib-gpg/random_seed differ
diff --git a/t/lib-gpg/secring.gpg b/t/lib-gpg/secring.gpg
new file mode 100644 (file)
index 0000000..d831cd9
Binary files /dev/null and b/t/lib-gpg/secring.gpg differ
diff --git a/t/lib-gpg/trustdb.gpg b/t/lib-gpg/trustdb.gpg
new file mode 100644 (file)
index 0000000..abace96
Binary files /dev/null and b/t/lib-gpg/trustdb.gpg differ
index f87abb5a0682d7a2fc7c3947789301c4157d331d..1d29810a7a94dad15645869c2f4f015a470fa622 100755 (executable)
@@ -40,6 +40,12 @@ check_parse 2008-02 bad
 check_parse 2008-02-14 bad
 check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 +0000'
 check_parse '2008-02-14 20:30:45 -0500' '2008-02-14 20:30:45 -0500'
+check_parse '2008-02-14 20:30:45 -0015' '2008-02-14 20:30:45 -0015'
+check_parse '2008-02-14 20:30:45 -5' '2008-02-14 20:30:45 +0000'
+check_parse '2008-02-14 20:30:45 -5:' '2008-02-14 20:30:45 +0000'
+check_parse '2008-02-14 20:30:45 -05' '2008-02-14 20:30:45 -0500'
+check_parse '2008-02-14 20:30:45 -:30' '2008-02-14 20:30:45 +0000'
+check_parse '2008-02-14 20:30:45 -05:00' '2008-02-14 20:30:45 -0500'
 check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 -0500' EST5
 
 check_approxidate() {
diff --git a/t/t1013-loose-object-format.sh b/t/t1013-loose-object-format.sh
new file mode 100755 (executable)
index 0000000..0a9cedd
--- /dev/null
@@ -0,0 +1,66 @@
+#!/bin/sh
+#
+# Copyright (c) 2011 Roberto Tyley
+#
+
+test_description='Correctly identify and parse loose object headers
+
+There are two file formats for loose objects - the original standard
+format, and the experimental format introduced with Git v1.4.3, later
+deprecated with v1.5.3. Although Git no longer writes the
+experimental format, objects in both formats must be read, with the
+format for a given file being determined by the header.
+
+Detecting file format based on header is not entirely trivial, not
+least because the first byte of a zlib-deflated stream will vary
+depending on how much memory was allocated for the deflation window
+buffer when the object was written out (for example 4KB on Android,
+rather that 32KB on a normal PC).
+
+The loose objects used as test vectors have been generated with the
+following Git versions:
+
+standard format: Git v1.7.4.1
+experimental format: Git v1.4.3 (legacyheaders=false)
+standard format, deflated with 4KB window size: Agit/JGit on Android
+'
+
+. ./test-lib.sh
+
+assert_blob_equals() {
+       printf "%s" "$2" >expected &&
+       git cat-file -p "$1" >actual &&
+       test_cmp expected actual
+}
+
+test_expect_success setup '
+       cp -R "$TEST_DIRECTORY/t1013/objects" .git/
+       git --version
+'
+
+test_expect_success 'read standard-format loose objects' '
+       git cat-file tag 8d4e360d6c70fbd72411991c02a09c442cf7a9fa &&
+       git cat-file commit 6baee0540ea990d9761a3eb9ab183003a71c3696 &&
+       git ls-tree 7a37b887a73791d12d26c0d3e39568a8fb0fa6e8 &&
+       assert_blob_equals "257cc5642cb1a054f08cc83f2d943e56fd3ebe99" "foo$LF"
+'
+
+test_expect_success 'read experimental-format loose objects' '
+       git cat-file tag 76e7fa9941f4d5f97f64fea65a2cba436bc79cbb &&
+       git cat-file commit 7875c6237d3fcdd0ac2f0decc7d3fa6a50b66c09 &&
+       git ls-tree 95b1625de3ba8b2214d1e0d0591138aea733f64f &&
+       assert_blob_equals "2e65efe2a145dda7ee51d1741299f848e5bf752e" "a" &&
+       assert_blob_equals "9ae9e86b7bd6cb1472d9373702d8249973da0832" "ab" &&
+       assert_blob_equals "85df50785d62d3b05ab03d9cbf7e4a0b49449730" "abcd" &&
+       assert_blob_equals "1656f9233d999f61ef23ef390b9c71d75399f435" "abcdefgh" &&
+       assert_blob_equals "1e72a6b2c4a577ab0338860fa9fe87f761fc9bbd" "abcdefghi" &&
+       assert_blob_equals "70e6a83d8dcb26fc8bc0cf702e2ddeb6adca18fd" "abcdefghijklmnop" &&
+       assert_blob_equals "bd15045f6ce8ff75747562173640456a394412c8" "abcdefghijklmnopqrstuvwx"
+'
+
+test_expect_success 'read standard-format objects deflated with smaller window buffer' '
+       git cat-file tag f816d5255855ac160652ee5253b06cd8ee14165a &&
+       git cat-file tag 149cedb5c46929d18e0f118e9fa31927487af3b6
+'
+
+test_done
diff --git a/t/t1013/objects/14/9cedb5c46929d18e0f118e9fa31927487af3b6 b/t/t1013/objects/14/9cedb5c46929d18e0f118e9fa31927487af3b6
new file mode 100644 (file)
index 0000000..472fd14
Binary files /dev/null and b/t/t1013/objects/14/9cedb5c46929d18e0f118e9fa31927487af3b6 differ
diff --git a/t/t1013/objects/16/56f9233d999f61ef23ef390b9c71d75399f435 b/t/t1013/objects/16/56f9233d999f61ef23ef390b9c71d75399f435
new file mode 100644 (file)
index 0000000..c379d74
Binary files /dev/null and b/t/t1013/objects/16/56f9233d999f61ef23ef390b9c71d75399f435 differ
diff --git a/t/t1013/objects/1e/72a6b2c4a577ab0338860fa9fe87f761fc9bbd b/t/t1013/objects/1e/72a6b2c4a577ab0338860fa9fe87f761fc9bbd
new file mode 100644 (file)
index 0000000..9370630
Binary files /dev/null and b/t/t1013/objects/1e/72a6b2c4a577ab0338860fa9fe87f761fc9bbd differ
diff --git a/t/t1013/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 b/t/t1013/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99
new file mode 100644 (file)
index 0000000..bdcf704
Binary files /dev/null and b/t/t1013/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 differ
diff --git a/t/t1013/objects/2e/65efe2a145dda7ee51d1741299f848e5bf752e b/t/t1013/objects/2e/65efe2a145dda7ee51d1741299f848e5bf752e
new file mode 100644 (file)
index 0000000..ad62c43
Binary files /dev/null and b/t/t1013/objects/2e/65efe2a145dda7ee51d1741299f848e5bf752e differ
diff --git a/t/t1013/objects/6b/aee0540ea990d9761a3eb9ab183003a71c3696 b/t/t1013/objects/6b/aee0540ea990d9761a3eb9ab183003a71c3696
new file mode 100644 (file)
index 0000000..3d2f033
Binary files /dev/null and b/t/t1013/objects/6b/aee0540ea990d9761a3eb9ab183003a71c3696 differ
diff --git a/t/t1013/objects/70/e6a83d8dcb26fc8bc0cf702e2ddeb6adca18fd b/t/t1013/objects/70/e6a83d8dcb26fc8bc0cf702e2ddeb6adca18fd
new file mode 100644 (file)
index 0000000..b3f71a6
Binary files /dev/null and b/t/t1013/objects/70/e6a83d8dcb26fc8bc0cf702e2ddeb6adca18fd differ
diff --git a/t/t1013/objects/76/e7fa9941f4d5f97f64fea65a2cba436bc79cbb b/t/t1013/objects/76/e7fa9941f4d5f97f64fea65a2cba436bc79cbb
new file mode 100644 (file)
index 0000000..af4e9a7
--- /dev/null
@@ -0,0 +1,2 @@
\vx\9c%ÌA\ e\820\10@Ñ}O1{cSZ(\98\18ãνá\ 2Ãthª\94\92Z\8cÜÞ Ëÿ\16?\r\ f¦\ 2m×6dµi\9d\19É9\85¤Gå\98h\a´Ø¨ÁZR'Q¶\85\81R\8c¡\88\82\1eø³p\ e\91ç\82ÓqL9âÏ=g¸§\81sIÐo\13opÎÿ\94eÏ«_1»\80³¤$×ç\ 5*Si«ëNwpP\95RBôûÅÁú
\87[(ð®d-\8dø\ 2ÁL9á
\ No newline at end of file
diff --git a/t/t1013/objects/78/75c6237d3fcdd0ac2f0decc7d3fa6a50b66c09 b/t/t1013/objects/78/75c6237d3fcdd0ac2f0decc7d3fa6a50b66c09
new file mode 100644 (file)
index 0000000..3dd28be
Binary files /dev/null and b/t/t1013/objects/78/75c6237d3fcdd0ac2f0decc7d3fa6a50b66c09 differ
diff --git a/t/t1013/objects/7a/37b887a73791d12d26c0d3e39568a8fb0fa6e8 b/t/t1013/objects/7a/37b887a73791d12d26c0d3e39568a8fb0fa6e8
new file mode 100644 (file)
index 0000000..2b97b26
Binary files /dev/null and b/t/t1013/objects/7a/37b887a73791d12d26c0d3e39568a8fb0fa6e8 differ
diff --git a/t/t1013/objects/85/df50785d62d3b05ab03d9cbf7e4a0b49449730 b/t/t1013/objects/85/df50785d62d3b05ab03d9cbf7e4a0b49449730
new file mode 100644 (file)
index 0000000..6dff746
Binary files /dev/null and b/t/t1013/objects/85/df50785d62d3b05ab03d9cbf7e4a0b49449730 differ
diff --git a/t/t1013/objects/8d/4e360d6c70fbd72411991c02a09c442cf7a9fa b/t/t1013/objects/8d/4e360d6c70fbd72411991c02a09c442cf7a9fa
new file mode 100644 (file)
index 0000000..cb41e92
Binary files /dev/null and b/t/t1013/objects/8d/4e360d6c70fbd72411991c02a09c442cf7a9fa differ
diff --git a/t/t1013/objects/95/b1625de3ba8b2214d1e0d0591138aea733f64f b/t/t1013/objects/95/b1625de3ba8b2214d1e0d0591138aea733f64f
new file mode 100644 (file)
index 0000000..7ac46b4
Binary files /dev/null and b/t/t1013/objects/95/b1625de3ba8b2214d1e0d0591138aea733f64f differ
diff --git a/t/t1013/objects/9a/e9e86b7bd6cb1472d9373702d8249973da0832 b/t/t1013/objects/9a/e9e86b7bd6cb1472d9373702d8249973da0832
new file mode 100644 (file)
index 0000000..9d8316d
Binary files /dev/null and b/t/t1013/objects/9a/e9e86b7bd6cb1472d9373702d8249973da0832 differ
diff --git a/t/t1013/objects/bd/15045f6ce8ff75747562173640456a394412c8 b/t/t1013/objects/bd/15045f6ce8ff75747562173640456a394412c8
new file mode 100644 (file)
index 0000000..eebf239
Binary files /dev/null and b/t/t1013/objects/bd/15045f6ce8ff75747562173640456a394412c8 differ
diff --git a/t/t1013/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/t/t1013/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
new file mode 100644 (file)
index 0000000..134cf19
Binary files /dev/null and b/t/t1013/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 differ
diff --git a/t/t1013/objects/f8/16d5255855ac160652ee5253b06cd8ee14165a b/t/t1013/objects/f8/16d5255855ac160652ee5253b06cd8ee14165a
new file mode 100644 (file)
index 0000000..26b75ae
--- /dev/null
@@ -0,0 +1 @@
+H\89\15ÌÁ\ e\820\f\80aÏ{\8aÞ\rI»e\1d&Æø*¥\1d\88\ 1\17ß^¸ýù\ e¿Ë\ 4DåÒ\86wU\87Ò\97¬\1cS±4ª\19\8aÆ\11­ª\9e ,\19\afÅ[ðßVAÛºÎ\1eüxÈÇö6[wtG§Lu\a¸?\97¦²¼Ú×\1f@\89"gì{\86+\12b\b\7fy¾%M
\ No newline at end of file
index 865b8ed26d577e154276887f88c8af9d13e62170..3b1b985996e9a6b52b032ef45c5be9b1c57b60f6 100755 (executable)
@@ -17,8 +17,6 @@ test_expect_success setup '
        cp one original.one &&
        cp dir/two original.two
 '
-LF='
-'
 
 test_expect_success 'update-index and ls-files' '
        git update-index --add one &&
index 1b0f82fa4c7928fc4605ccf31a3ae45e6ac9f38e..ed4275afe3100491ea57025632665182127f33cf 100755 (executable)
@@ -18,6 +18,9 @@ invalid_ref 'foo'
 valid_ref 'foo/bar/baz'
 valid_ref 'refs///heads/foo'
 invalid_ref 'heads/foo/'
+valid_ref '/heads/foo'
+valid_ref '///heads/foo'
+invalid_ref '/foo'
 invalid_ref './foo'
 invalid_ref '.refs/foo'
 invalid_ref 'heads/foo..bar'
@@ -27,6 +30,9 @@ invalid_ref 'heads/foo.lock'
 valid_ref 'heads/foo@bar'
 invalid_ref 'heads/v@{ation'
 invalid_ref 'heads/foo\bar'
+invalid_ref "$(printf 'heads/foo\t')"
+invalid_ref "$(printf 'heads/foo\177')"
+valid_ref "$(printf 'heads/fu\303\237')"
 
 test_expect_success "check-ref-format --branch @{-1}" '
        T=$(git write-tree) &&
@@ -70,7 +76,10 @@ invalid_ref_normalized() {
 
 valid_ref_normalized 'heads/foo' 'heads/foo'
 valid_ref_normalized 'refs///heads/foo' 'refs/heads/foo'
+valid_ref_normalized '/heads/foo' 'heads/foo'
+valid_ref_normalized '///heads/foo' 'heads/foo'
 invalid_ref_normalized 'foo'
+invalid_ref_normalized '/foo'
 invalid_ref_normalized 'heads/foo/../bar'
 invalid_ref_normalized 'heads/./foo'
 invalid_ref_normalized 'heads\foo'
index bb01d5ab8f8ebcaf49ef238d48ae89e3845d5f4b..523ce9c45b75d85a129015a56004473a5fccf926 100755 (executable)
@@ -110,6 +110,42 @@ test_expect_success 'email with embedded > is not okay' '
        grep "error in commit $new" out
 '
 
+test_expect_success 'missing < email delimiter is reported nicely' '
+       git cat-file commit HEAD >basis &&
+       sed "s/<//" basis >bad-email-2 &&
+       new=$(git hash-object -t commit -w --stdin <bad-email-2) &&
+       test_when_finished "remove_object $new" &&
+       git update-ref refs/heads/bogus "$new" &&
+       test_when_finished "git update-ref -d refs/heads/bogus" &&
+       git fsck 2>out &&
+       cat out &&
+       grep "error in commit $new.* - bad name" out
+'
+
+test_expect_success 'missing email is reported nicely' '
+       git cat-file commit HEAD >basis &&
+       sed "s/[a-z]* <[^>]*>//" basis >bad-email-3 &&
+       new=$(git hash-object -t commit -w --stdin <bad-email-3) &&
+       test_when_finished "remove_object $new" &&
+       git update-ref refs/heads/bogus "$new" &&
+       test_when_finished "git update-ref -d refs/heads/bogus" &&
+       git fsck 2>out &&
+       cat out &&
+       grep "error in commit $new.* - missing email" out
+'
+
+test_expect_success '> in name is reported' '
+       git cat-file commit HEAD >basis &&
+       sed "s/ </> </" basis >bad-email-4 &&
+       new=$(git hash-object -t commit -w --stdin <bad-email-4) &&
+       test_when_finished "remove_object $new" &&
+       git update-ref refs/heads/bogus "$new" &&
+       test_when_finished "git update-ref -d refs/heads/bogus" &&
+       git fsck 2>out &&
+       cat out &&
+       grep "error in commit $new" out
+'
+
 test_expect_success 'tag pointing to nonexistent' '
        cat >invalid-tag <<-\EOF &&
        object ffffffffffffffffffffffffffffffffffffffff
index a42e03967b1df3001df24089f2c50008c092ac51..75874e85dfbcae8ea9634693a93524841b741559 100755 (executable)
@@ -118,6 +118,15 @@ test_expect_success 'checkout -b to an existing branch fails' '
        test_must_fail do_checkout branch2 $HEAD2
 '
 
+test_expect_success 'checkout -b to @{-1} fails with the right branch name' '
+       git reset --hard HEAD &&
+       git checkout branch1 &&
+       git checkout branch2 &&
+       echo  >expect "fatal: A branch named '\''branch1'\'' already exists." &&
+       test_must_fail git checkout -b @{-1} 2>actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'checkout -B to an existing branch resets branch to HEAD' '
        git checkout branch1 &&
 
@@ -180,4 +189,12 @@ test_expect_success 'checkout -b <describe>' '
        test_cmp expect actual
 '
 
+test_expect_success 'checkout -B to the current branch fails before merging' '
+       git checkout branch1 &&
+       setup_dirty_mergeable &&
+       git commit -mfooble &&
+       test_must_fail git checkout -B branch1 initial &&
+       test_must_fail test_dirty_mergeable
+'
+
 test_done
diff --git a/t/t3005-ls-files-relative.sh b/t/t3005-ls-files-relative.sh
new file mode 100755 (executable)
index 0000000..3778694
--- /dev/null
@@ -0,0 +1,72 @@
+#!/bin/sh
+
+test_description='ls-files tests with relative paths
+
+This test runs git ls-files with various relative path arguments.
+'
+
+. ./test-lib.sh
+
+new_line='
+'
+sq=\'
+
+test_expect_success 'prepare' '
+       : >never-mind-me &&
+       git add never-mind-me &&
+       mkdir top &&
+       (
+               cd top &&
+               mkdir sub &&
+               x="x xa xbc xdef xghij xklmno" &&
+               y=$(echo "$x" | tr x y) &&
+               touch $x &&
+               touch $y &&
+               cd sub &&
+               git add ../x*
+       )
+'
+
+test_expect_success 'ls-files with mixed levels' '
+       (
+               cd top/sub &&
+               cat >expect <<-EOF &&
+               ../../never-mind-me
+               ../x
+               EOF
+               git ls-files $(cat expect) >actual &&
+               test_cmp expect actual
+       )
+'
+
+test_expect_success 'ls-files -c' '
+       (
+               cd top/sub &&
+               for f in ../y*
+               do
+                       echo "error: pathspec $sq$f$sq did not match any file(s) known to git."
+               done >expect.err &&
+               echo "Did you forget to ${sq}git add${sq}?" >>expect.err &&
+               ls ../x* >expect.out &&
+               test_must_fail git ls-files -c --error-unmatch ../[xy]* >actual.out 2>actual.err &&
+               test_cmp expect.out actual.out &&
+               test_cmp expect.err actual.err
+       )
+'
+
+test_expect_success 'ls-files -o' '
+       (
+               cd top/sub &&
+               for f in ../x*
+               do
+                       echo "error: pathspec $sq$f$sq did not match any file(s) known to git."
+               done >expect.err &&
+               echo "Did you forget to ${sq}git add${sq}?" >>expect.err &&
+               ls ../y* >expect.out &&
+               test_must_fail git ls-files -o --error-unmatch ../[xy]* >actual.out 2>actual.err &&
+               test_cmp expect.out actual.out &&
+               test_cmp expect.err actual.err
+       )
+'
+
+test_done
index 0c02d569520ddc81b32db7d248197562d00bd7ea..55ef1895d7fd15348c47a5dc4a7f93541a1d38c1 100755 (executable)
@@ -267,7 +267,8 @@ test_expect_success 'setup 8' '
                ln -s e a &&
                git add a e &&
                test_tick &&
-               git commit -m "rename a->e, symlink a->e"
+               git commit -m "rename a->e, symlink a->e" &&
+               oln=`printf e | git hash-object --stdin`
        fi
 '
 
@@ -630,16 +631,18 @@ test_expect_success 'merge-recursive copy vs. rename' '
 
 if test_have_prereq SYMLINKS
 then
-       test_expect_success 'merge-recursive rename vs. rename/symlink' '
+       test_expect_failure 'merge-recursive rename vs. rename/symlink' '
 
                git checkout -f rename &&
                git merge rename-ln &&
                ( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
                (
+                       echo "120000 blob $oln  a"
                        echo "100644 blob $o0   b"
                        echo "100644 blob $o0   c"
                        echo "100644 blob $o0   d/e"
                        echo "100644 blob $o0   e"
+                       echo "120000 $oln 0     a"
                        echo "100644 $o0 0      b"
                        echo "100644 $o0 0      c"
                        echo "100644 $o0 0      d/e"
index 9e69c8c926620f06343e64e7b3aa3e4ada5a6b69..7633930bb472e1c9aebf2799f3eaf3bc53c7263c 100755 (executable)
@@ -98,6 +98,18 @@ test_expect_success 'git branch -m q r/q should fail when r exists' '
        test_must_fail git branch -m q r/q
 '
 
+test_expect_success 'git branch -M foo bar should fail when bar is checked out' '
+       git branch bar &&
+       git checkout -b foo &&
+       test_must_fail git branch -M bar foo
+'
+
+test_expect_success 'git branch -M baz bam should succeed when baz is checked out' '
+       git checkout -b baz &&
+       git branch bam &&
+       git branch -M baz bam
+'
+
 mv .git/config .git/config-saved
 
 test_expect_success 'git branch -m q q2 without config should succeed' '
@@ -542,4 +554,17 @@ test_expect_success 'attempt to delete a branch merged to its base' '
        test_must_fail git branch -d my10
 '
 
+test_expect_success 'use set-upstream on the current branch' '
+       git checkout master &&
+       git --bare init myupstream.git &&
+       git push myupstream.git master:refs/heads/frotz &&
+       git remote add origin myupstream.git &&
+       git fetch &&
+       git branch --set-upstream master origin/frotz &&
+
+       test "z$(git config branch.master.remote)" = "zorigin" &&
+       test "z$(git config branch.master.merge)" = "zrefs/heads/frotz"
+
+'
+
 test_done
index 8538813d1d0dfa72008da1d711cf2c042796a6ee..b981572d736a1adf8da5281f31e580982e2059af 100755 (executable)
@@ -527,6 +527,20 @@ test_expect_success 'auto-amend only edited commits after "edit"' '
        git rebase --abort
 '
 
+test_expect_success 'clean error after failed "exec"' '
+       test_tick &&
+       test_when_finished "git rebase --abort || :" &&
+       (
+               FAKE_LINES="1 exec_false" &&
+               export FAKE_LINES &&
+               test_must_fail git rebase -i HEAD^
+       ) &&
+       echo "edited again" > file7 &&
+       git add file7 &&
+       test_must_fail git rebase --continue 2>error &&
+       grep "You have staged changes in your working tree." error
+'
+
 test_expect_success 'rebase a detached HEAD' '
        grandparent=$(git rev-parse HEAD~2) &&
        git checkout $(git rev-parse HEAD) &&
index 9aefe3a1becac200f2c29beee84fab278f9fcfa0..e27f39d1e5b0fb0ac3a1fc6417f0f3240934f07d 100755 (executable)
@@ -16,12 +16,20 @@ test_expect_success setup '
        echo second > file2 &&
        git add file2 &&
        test_tick &&
-       git commit -m "second"
+       git commit -m "second" &&
+
+       git symbolic-ref HEAD refs/heads/third &&
+       rm .git/index file2 &&
+       echo third > file3 &&
+       git add file3 &&
+       test_tick &&
+       git commit -m "third"
 
 '
 
 test_expect_success 'cherry-pick a root commit' '
 
+       git checkout second^0 &&
        git cherry-pick master &&
        echo first >expect &&
        test_cmp expect file1
@@ -50,4 +58,21 @@ test_expect_success 'revert a root commit with an external strategy' '
 
 '
 
+test_expect_success 'cherry-pick two root commits' '
+
+       echo first >expect.file1 &&
+       echo second >expect.file2 &&
+       echo third >expect.file3 &&
+
+       git checkout second^0 &&
+       git cherry-pick master third &&
+
+       test_cmp expect.file1 file1 &&
+       test_cmp expect.file2 file2 &&
+       test_cmp expect.file3 file3 &&
+       git rev-parse --verify HEAD^^ &&
+       test_must_fail git rev-parse --verify HEAD^^^
+
+'
+
 test_done
diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh
new file mode 100755 (executable)
index 0000000..3bca2b3
--- /dev/null
@@ -0,0 +1,214 @@
+#!/bin/sh
+
+test_description='Test cherry-pick continuation features
+
+  + anotherpick: rewrites foo to d
+  + picked: rewrites foo to c
+  + unrelatedpick: rewrites unrelated to reallyunrelated
+  + base: rewrites foo to b
+  + initial: writes foo as a, unrelated as unrelated
+
+'
+
+. ./test-lib.sh
+
+pristine_detach () {
+       git cherry-pick --reset &&
+       git checkout -f "$1^0" &&
+       git read-tree -u --reset HEAD &&
+       git clean -d -f -f -q -x
+}
+
+test_expect_success setup '
+       echo unrelated >unrelated &&
+       git add unrelated &&
+       test_commit initial foo a &&
+       test_commit base foo b &&
+       test_commit unrelatedpick unrelated reallyunrelated &&
+       test_commit picked foo c &&
+       test_commit anotherpick foo d &&
+       git config advice.detachedhead false
+
+'
+
+test_expect_success 'cherry-pick persists data on failure' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick -s base..anotherpick &&
+       test_path_is_dir .git/sequencer &&
+       test_path_is_file .git/sequencer/head &&
+       test_path_is_file .git/sequencer/todo &&
+       test_path_is_file .git/sequencer/opts
+'
+
+test_expect_success 'cherry-pick persists opts correctly' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick -s -m 1 --strategy=recursive -X patience -X ours base..anotherpick &&
+       test_path_is_dir .git/sequencer &&
+       test_path_is_file .git/sequencer/head &&
+       test_path_is_file .git/sequencer/todo &&
+       test_path_is_file .git/sequencer/opts &&
+       echo "true" >expect &&
+       git config --file=.git/sequencer/opts --get-all options.signoff >actual &&
+       test_cmp expect actual &&
+       echo "1" >expect &&
+       git config --file=.git/sequencer/opts --get-all options.mainline >actual &&
+       test_cmp expect actual &&
+       echo "recursive" >expect &&
+       git config --file=.git/sequencer/opts --get-all options.strategy >actual &&
+       test_cmp expect actual &&
+       cat >expect <<-\EOF &&
+       patience
+       ours
+       EOF
+       git config --file=.git/sequencer/opts --get-all options.strategy-option >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick cleans up sequencer state upon success' '
+       pristine_detach initial &&
+       git cherry-pick initial..picked &&
+       test_path_is_missing .git/sequencer
+'
+
+test_expect_success '--reset does not complain when no cherry-pick is in progress' '
+       pristine_detach initial &&
+       git cherry-pick --reset
+'
+
+test_expect_success '--reset cleans up sequencer state' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick base..picked &&
+       git cherry-pick --reset &&
+       test_path_is_missing .git/sequencer
+'
+
+test_expect_success 'cherry-pick cleans up sequencer state when one commit is left' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick base..picked &&
+       test_path_is_missing .git/sequencer &&
+       echo "resolved" >foo &&
+       git add foo &&
+       git commit &&
+       {
+               git rev-list HEAD |
+               git diff-tree --root --stdin |
+               sed "s/$_x40/OBJID/g"
+       } >actual &&
+       cat >expect <<-\EOF &&
+       OBJID
+       :100644 100644 OBJID OBJID M    foo
+       OBJID
+       :100644 100644 OBJID OBJID M    unrelated
+       OBJID
+       :000000 100644 OBJID OBJID A    foo
+       :000000 100644 OBJID OBJID A    unrelated
+       EOF
+       test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick does not implicitly stomp an existing operation' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick base..anotherpick &&
+       test-chmtime -v +0 .git/sequencer >expect &&
+       test_must_fail git cherry-pick unrelatedpick &&
+       test-chmtime -v +0 .git/sequencer >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '--continue complains when no cherry-pick is in progress' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick --continue
+'
+
+test_expect_success '--continue complains when there are unresolved conflicts' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick base..anotherpick &&
+       test_must_fail git cherry-pick --continue
+'
+
+test_expect_success '--continue continues after conflicts are resolved' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick base..anotherpick &&
+       echo "c" >foo &&
+       git add foo &&
+       git commit &&
+       git cherry-pick --continue &&
+       test_path_is_missing .git/sequencer &&
+       {
+               git rev-list HEAD |
+               git diff-tree --root --stdin |
+               sed "s/$_x40/OBJID/g"
+       } >actual &&
+       cat >expect <<-\EOF &&
+       OBJID
+       :100644 100644 OBJID OBJID M    foo
+       OBJID
+       :100644 100644 OBJID OBJID M    foo
+       OBJID
+       :100644 100644 OBJID OBJID M    unrelated
+       OBJID
+       :000000 100644 OBJID OBJID A    foo
+       :000000 100644 OBJID OBJID A    unrelated
+       EOF
+       test_cmp expect actual
+'
+
+test_expect_success '--continue respects opts' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick -x base..anotherpick &&
+       echo "c" >foo &&
+       git add foo &&
+       git commit &&
+       git cherry-pick --continue &&
+       test_path_is_missing .git/sequencer &&
+       git cat-file commit HEAD >anotherpick_msg &&
+       git cat-file commit HEAD~1 >picked_msg &&
+       git cat-file commit HEAD~2 >unrelatedpick_msg &&
+       git cat-file commit HEAD~3 >initial_msg &&
+       test_must_fail grep "cherry picked from" initial_msg &&
+       grep "cherry picked from" unrelatedpick_msg &&
+       grep "cherry picked from" picked_msg &&
+       grep "cherry picked from" anotherpick_msg
+'
+
+test_expect_success '--signoff is not automatically propagated to resolved conflict' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick --signoff base..anotherpick &&
+       echo "c" >foo &&
+       git add foo &&
+       git commit &&
+       git cherry-pick --continue &&
+       test_path_is_missing .git/sequencer &&
+       git cat-file commit HEAD >anotherpick_msg &&
+       git cat-file commit HEAD~1 >picked_msg &&
+       git cat-file commit HEAD~2 >unrelatedpick_msg &&
+       git cat-file commit HEAD~3 >initial_msg &&
+       test_must_fail grep "Signed-off-by:" initial_msg &&
+       grep "Signed-off-by:" unrelatedpick_msg &&
+       test_must_fail grep "Signed-off-by:" picked_msg &&
+       grep "Signed-off-by:" anotherpick_msg
+'
+
+test_expect_success 'malformed instruction sheet 1' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick base..anotherpick &&
+       echo "resolved" >foo &&
+       git add foo &&
+       git commit &&
+       sed "s/pick /pick/" .git/sequencer/todo >new_sheet &&
+       cp new_sheet .git/sequencer/todo &&
+       test_must_fail git cherry-pick --continue
+'
+
+test_expect_success 'malformed instruction sheet 2' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick base..anotherpick &&
+       echo "resolved" >foo &&
+       git add foo &&
+       git commit &&
+       sed "s/pick/revert/" .git/sequencer/todo >new_sheet &&
+       cp new_sheet .git/sequencer/todo &&
+       test_must_fail git cherry-pick --continue
+'
+
+test_done
index c06a5ee7660c3fd7ca15860cfb761b2c4d953e08..1f62c151b0aa63b4c85f9bc76f501d53967b0260 100755 (executable)
@@ -147,7 +147,7 @@ test_commit_autosquash_flags () {
                git commit -a -m "intermediate commit" &&
                test_tick &&
                echo $H $flag >>F &&
-               git commit -a --$flag HEAD~1 $3 &&
+               git commit -a --$flag HEAD~1 &&
                E=$(git cat-file commit '$H-$flag' |
                        sed -ne "s/^encoding //p") &&
                test "z$E" = "z$H" &&
@@ -160,6 +160,6 @@ test_commit_autosquash_flags () {
 
 test_commit_autosquash_flags eucJP fixup
 
-test_commit_autosquash_flags ISO-2022-JP squash '-m "squash message"'
+test_commit_autosquash_flags ISO-2022-JP squash
 
 test_done
index da82b655b3b33520b2c39d3992bc40368bf652be..534ee08a44b9d8501b234f228302e9a266d67f8c 100755 (executable)
@@ -10,8 +10,6 @@ test_description='quoted output'
 FN='濱野'
 GN='純'
 HT='   '
-LF='
-'
 DQ='"'
 
 echo foo 2>/dev/null > "Name and an${HT}HT"
index 7197aae1ed10add49aa72748188fecb4c2ef90a4..fcdb18217a777f5dbb77b0071af7159c6985656d 100755 (executable)
@@ -542,7 +542,7 @@ test_expect_success 'ref with non-existent reflog' '
        echo bar6 > file2 &&
        git add file2 &&
        git stash &&
-       ! "git rev-parse --quiet --verify does-not-exist" &&
+       test_must_fail git rev-parse --quiet --verify does-not-exist &&
        test_must_fail git stash drop does-not-exist &&
        test_must_fail git stash drop does-not-exist@{0} &&
        test_must_fail git stash pop does-not-exist &&
index 4f2eedfd4f87bdf7b6ba8035b203fb9d7bbb2dbe..ef44fb22601b03be78d8d5f5eaa858ca240ec4da 100755 (executable)
@@ -17,19 +17,21 @@ test_expect_success 'stash save --include-untracked some dirty working directory
        echo 3 > file &&
        test_tick &&
        echo 1 > file2 &&
+       mkdir untracked &&
+       echo untracked >untracked/untracked &&
        git stash --include-untracked &&
        git diff-files --quiet &&
        git diff-index --cached --quiet HEAD
 '
 
 cat > expect <<EOF
+?? actual
 ?? expect
-?? output
 EOF
 
 test_expect_success 'stash save --include-untracked cleaned the untracked files' '
-       git status --porcelain > output
-       test_cmp output expect
+       git status --porcelain >actual &&
+       test_cmp expect actual
 '
 
 cat > expect.diff <<EOF
@@ -40,17 +42,26 @@ index 0000000..d00491f
 +++ b/file2
 @@ -0,0 +1 @@
 +1
+diff --git a/untracked/untracked b/untracked/untracked
+new file mode 100644
+index 0000000..5a72eb2
+--- /dev/null
++++ b/untracked/untracked
+@@ -0,0 +1 @@
++untracked
 EOF
 cat > expect.lstree <<EOF
 file2
+untracked
 EOF
 
 test_expect_success 'stash save --include-untracked stashed the untracked files' '
        test "!" -f file2 &&
-       git diff HEAD..stash^3 -- file2 > output &&
-       test_cmp output expect.diff &&
-       git ls-tree --name-only stash^3: > output &&
-       test_cmp output expect.lstree
+       test ! -e untracked &&
+       git diff HEAD stash^3 -- file2 untracked >actual &&
+       test_cmp expect.diff actual &&
+       git ls-tree --name-only stash^3: >actual &&
+       test_cmp expect.lstree actual
 '
 test_expect_success 'stash save --patch --include-untracked fails' '
        test_must_fail git stash --patch --include-untracked
@@ -64,18 +75,21 @@ git clean --force --quiet
 
 cat > expect <<EOF
  M file
+?? actual
 ?? expect
 ?? file2
-?? output
+?? untracked/
 EOF
 
 test_expect_success 'stash pop after save --include-untracked leaves files untracked again' '
        git stash pop &&
-       git status --porcelain > output
-       test_cmp output expect
+       git status --porcelain >actual &&
+       test_cmp expect actual &&
+       test "1" = "`cat file2`" &&
+       test untracked = "`cat untracked/untracked`"
 '
 
-git clean --force --quiet
+git clean --force --quiet -d
 
 test_expect_success 'stash save -u dirty index' '
        echo 4 > file3 &&
@@ -96,8 +110,8 @@ EOF
 
 test_expect_success 'stash save --include-untracked dirty index got stashed' '
        git stash pop --index &&
-       git diff --cached > output &&
-       test_cmp output expect
+       git diff --cached >actual &&
+       test_cmp expect actual
 '
 
 git reset > /dev/null
@@ -125,30 +139,36 @@ test_expect_success 'stash save --include-untracked removed files got stashed' '
 cat > .gitignore <<EOF
 .gitignore
 ignored
+ignored.d/
 EOF
 
 test_expect_success 'stash save --include-untracked respects .gitignore' '
        echo ignored > ignored &&
+       mkdir ignored.d &&
+       echo ignored >ignored.d/untracked &&
        git stash -u &&
        test -s ignored &&
+       test -s ignored.d/untracked &&
        test -s .gitignore
 '
 
 test_expect_success 'stash save -u can stash with only untracked files different' '
        echo 4 > file4 &&
-       git stash -u
+       git stash -u &&
        test "!" -f file4
 '
 
 test_expect_success 'stash save --all does not respect .gitignore' '
        git stash -a &&
        test "!" -f ignored &&
+       test "!" -e ignored.d &&
        test "!" -f .gitignore
 '
 
 test_expect_success 'stash save --all is stash poppable' '
        git stash pop &&
        test -s ignored &&
+       test -s ignored.d/untracked &&
        test -s .gitignore
 '
 
index 92248d24c48a65ab89a5b0ad255357f6034e6ad4..67975129bc3703e16fe52eb916c2e08b7908ff87 100755 (executable)
@@ -179,12 +179,21 @@ test_expect_success 'configuration To: header' '
        grep "^To: R. E. Cipient <rcipient@example.com>\$" patch9
 '
 
+# check_patch <patch>: Verify that <patch> looks like a half-sane
+# patch email to avoid a false positive with !grep
+check_patch () {
+       grep -e "^From:" "$1" &&
+       grep -e "^Date:" "$1" &&
+       grep -e "^Subject:" "$1"
+}
+
 test_expect_success '--no-to overrides config.to' '
 
        git config --replace-all format.to \
                "R. E. Cipient <rcipient@example.com>" &&
        git format-patch --no-to --stdout master..side |
        sed -e "/^\$/q" >patch10 &&
+       check_patch patch10 &&
        ! grep "^To: R. E. Cipient <rcipient@example.com>\$" patch10
 '
 
@@ -195,6 +204,7 @@ test_expect_success '--no-to and --to replaces config.to' '
        git format-patch --no-to --to="Someone Else <else@out.there>" \
                --stdout master..side |
        sed -e "/^\$/q" >patch11 &&
+       check_patch patch11 &&
        ! grep "^To: Someone <someone@out.there>\$" patch11 &&
        grep "^To: Someone Else <else@out.there>\$" patch11
 '
@@ -205,15 +215,17 @@ test_expect_success '--no-cc overrides config.cc' '
                "C. E. Cipient <rcipient@example.com>" &&
        git format-patch --no-cc --stdout master..side |
        sed -e "/^\$/q" >patch12 &&
+       check_patch patch12 &&
        ! grep "^Cc: C. E. Cipient <rcipient@example.com>\$" patch12
 '
 
-test_expect_success '--no-add-headers overrides config.headers' '
+test_expect_success '--no-add-header overrides config.headers' '
 
        git config --replace-all format.headers \
                "Header1: B. E. Cipient <rcipient@example.com>" &&
-       git format-patch --no-add-headers --stdout master..side |
+       git format-patch --no-add-header --stdout master..side |
        sed -e "/^\$/q" >patch13 &&
+       check_patch patch13 &&
        ! grep "^Header1: B. E. Cipient <rcipient@example.com>\$" patch13
 '
 
@@ -445,22 +457,22 @@ test_expect_success 'thread deep cover-letter in-reply-to' '
 '
 
 test_expect_success 'thread via config' '
-       git config format.thread true &&
+       test_config format.thread true &&
        check_threading expect.thread master
 '
 
 test_expect_success 'thread deep via config' '
-       git config format.thread deep &&
+       test_config format.thread deep &&
        check_threading expect.deep master
 '
 
 test_expect_success 'thread config + override' '
-       git config format.thread deep &&
+       test_config format.thread deep &&
        check_threading expect.thread --thread master
 '
 
 test_expect_success 'thread config + --no-thread' '
-       git config format.thread deep &&
+       test_config format.thread deep &&
        check_threading expect.no-threading --no-thread master
 '
 
@@ -480,6 +492,7 @@ test_expect_success 'cover-letter inherits diff options' '
        git mv file foo &&
        git commit -m foo &&
        git format-patch --cover-letter -1 &&
+       check_patch 0000-cover-letter.patch &&
        ! grep "file => foo .* 0 *\$" 0000-cover-letter.patch &&
        git format-patch --cover-letter -1 -M &&
        grep "file => foo .* 0 *\$" 0000-cover-letter.patch
@@ -657,6 +670,7 @@ test_expect_success 'format-patch --no-signature ignores format.signature' '
        git config format.signature "config sig" &&
        git format-patch --stdout --signature="my sig" --no-signature \
                -1 >output &&
+       check_patch output &&
        ! grep "config sig" output &&
        ! grep "my sig" output &&
        ! grep "^-- \$" output
@@ -673,17 +687,20 @@ test_expect_success 'format-patch --signature --cover-letter' '
 test_expect_success 'format.signature="" supresses signatures' '
        git config format.signature "" &&
        git format-patch --stdout -1 >output &&
+       check_patch output &&
        ! grep "^-- \$" output
 '
 
 test_expect_success 'format-patch --no-signature supresses signatures' '
        git config --unset-all format.signature &&
        git format-patch --stdout --no-signature -1 >output &&
+       check_patch output &&
        ! grep "^-- \$" output
 '
 
 test_expect_success 'format-patch --signature="" supresses signatures' '
-       git format-patch --signature="" -1 >output &&
+       git format-patch --stdout --signature="" -1 >output &&
+       check_patch output &&
        ! grep "^-- \$" output
 '
 
@@ -869,4 +886,12 @@ test_expect_success 'empty subject prefix does not have extra space' '
        test_cmp expect actual
 '
 
+test_expect_success 'format patch ignores color.ui' '
+       test_unconfig color.ui &&
+       git format-patch --stdout -1 >expect &&
+       test_config color.ui always &&
+       git format-patch --stdout -1 >actual &&
+       test_cmp expect actual
+'
+
 test_done
diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh
new file mode 100755 (executable)
index 0000000..8341fc4
--- /dev/null
@@ -0,0 +1,104 @@
+#!/bin/sh
+
+test_description='fetch/receive strict mode'
+. ./test-lib.sh
+
+test_expect_success setup '
+       echo hello >greetings &&
+       git add greetings &&
+       git commit -m greetings &&
+
+       S=$(git rev-parse :greetings | sed -e "s|^..|&/|") &&
+       X=$(echo bye | git hash-object -w --stdin | sed -e "s|^..|&/|") &&
+       mv -f .git/objects/$X .git/objects/$S &&
+
+       test_must_fail git fsck
+'
+
+test_expect_success 'fetch without strict' '
+       rm -rf dst &&
+       git init dst &&
+       (
+               cd dst &&
+               git config fetch.fsckobjects false &&
+               git config transfer.fsckobjects false &&
+               test_must_fail git fetch ../.git master
+       )
+'
+
+test_expect_success 'fetch with !fetch.fsckobjects' '
+       rm -rf dst &&
+       git init dst &&
+       (
+               cd dst &&
+               git config fetch.fsckobjects false &&
+               git config transfer.fsckobjects true &&
+               test_must_fail git fetch ../.git master
+       )
+'
+
+test_expect_success 'fetch with fetch.fsckobjects' '
+       rm -rf dst &&
+       git init dst &&
+       (
+               cd dst &&
+               git config fetch.fsckobjects true &&
+               git config transfer.fsckobjects false &&
+               test_must_fail git fetch ../.git master
+       )
+'
+
+test_expect_success 'fetch with transfer.fsckobjects' '
+       rm -rf dst &&
+       git init dst &&
+       (
+               cd dst &&
+               git config transfer.fsckobjects true &&
+               test_must_fail git fetch ../.git master
+       )
+'
+
+test_expect_success 'push without strict' '
+       rm -rf dst &&
+       git init dst &&
+       (
+               cd dst &&
+               git config fetch.fsckobjects false &&
+               git config transfer.fsckobjects false
+       ) &&
+       git push dst master:refs/heads/test
+'
+
+test_expect_success 'push with !receive.fsckobjects' '
+       rm -rf dst &&
+       git init dst &&
+       (
+               cd dst &&
+               git config receive.fsckobjects false &&
+               git config transfer.fsckobjects true
+       ) &&
+       git push dst master:refs/heads/test
+'
+
+test_expect_success 'push with receive.fsckobjects' '
+       rm -rf dst &&
+       git init dst &&
+       (
+               cd dst &&
+               git config receive.fsckobjects true &&
+               git config transfer.fsckobjects false
+       ) &&
+       test_must_fail git push dst master:refs/heads/test
+'
+
+test_expect_success 'push with transfer.fsckobjects' '
+       rm -rf dst &&
+       git init dst &&
+       (
+               cd dst &&
+               git config transfer.fsckobjects true
+       ) &&
+       test_must_fail git push dst master:refs/heads/test
+'
+
+test_done
index faa2e9633783e96609f4a31beb4bcccedb876338..30bec4b5f9f286cd97c57b70e3635c6c5c8cf85a 100755 (executable)
@@ -32,4 +32,91 @@ test_expect_success push '
        )
 '
 
+test_expect_success 'push if submodule has no remote' '
+       (
+               cd work/gar/bage &&
+               >junk2 &&
+               git add junk2 &&
+               git commit -m "Second junk"
+       ) &&
+       (
+               cd work &&
+               git add gar/bage &&
+               git commit -m "Second commit for gar/bage" &&
+               git push --recurse-submodules=check ../pub.git master
+       )
+'
+
+test_expect_success 'push fails if submodule commit not on remote' '
+       (
+               cd work/gar &&
+               git clone --bare bage ../../submodule.git &&
+               cd bage &&
+               git remote add origin ../../../submodule.git &&
+               git fetch &&
+               >junk3 &&
+               git add junk3 &&
+               git commit -m "Third junk"
+       ) &&
+       (
+               cd work &&
+               git add gar/bage &&
+               git commit -m "Third commit for gar/bage" &&
+               test_must_fail git push --recurse-submodules=check ../pub.git master
+       )
+'
+
+test_expect_success 'push succeeds after commit was pushed to remote' '
+       (
+               cd work/gar/bage &&
+               git push origin master
+       ) &&
+       (
+               cd work &&
+               git push --recurse-submodules=check ../pub.git master
+       )
+'
+
+test_expect_success 'push fails when commit on multiple branches if one branch has no remote' '
+       (
+               cd work/gar/bage &&
+               >junk4 &&
+               git add junk4 &&
+               git commit -m "Fourth junk"
+       ) &&
+       (
+               cd work &&
+               git branch branch2 &&
+               git add gar/bage &&
+               git commit -m "Fourth commit for gar/bage" &&
+               git checkout branch2 &&
+               (
+                       cd gar/bage &&
+                       git checkout HEAD~1
+               ) &&
+               >junk1 &&
+               git add junk1 &&
+               git commit -m "First junk" &&
+               test_must_fail git push --recurse-submodules=check ../pub.git
+       )
+'
+
+test_expect_success 'push succeeds if submodule has no remote and is on the first superproject commit' '
+       git init --bare a
+       git clone a a1 &&
+       (
+               cd a1 &&
+               git init b
+               (
+                       cd b &&
+                       >junk &&
+                       git add junk &&
+                       git commit -m "initial"
+               ) &&
+               git add b &&
+               git commit -m "added submodule" &&
+               git push --recurse-submodule=check origin master
+       )
+'
+
 test_done
index a266ca56361347fe751495e1531b5a26e4733493..64767d87055c9ad65a225432b5193497c9fad405 100755 (executable)
@@ -132,8 +132,12 @@ x38="$x5$x5$x5$x5$x5$x5$x5$x1$x1$x1"
 x40="$x38$x2"
 
 test_expect_success 'PUT and MOVE sends object to URLs with SHA-1 hash suffix' '
-       sed -e "s/PUT /OP /" -e "s/MOVE /OP /" "$HTTPD_ROOT_PATH"/access.log |
-       grep -e "\"OP .*/objects/$x2/${x38}_$x40 HTTP/[.0-9]*\" 20[0-9] "
+       sed \
+               -e "s/PUT /OP /" \
+               -e "s/MOVE /OP /" \
+           -e "s|/objects/$x2/${x38}_$x40|WANTED_PATH_REQUEST|" \
+               "$HTTPD_ROOT_PATH"/access.log |
+       grep -e "\"OP .*WANTED_PATH_REQUEST HTTP/[.0-9]*\" 20[0-9] "
 
 '
 
index 151ea531bdfeb5a012e57407749beb4f26de6d2a..e8103144bb026afb12f5b058b9ec399b70abebbd 100755 (executable)
@@ -202,9 +202,36 @@ test_expect_success 'clone separate gitdir: output' '
        test_cmp expected dst/.git
 '
 
+test_expect_success 'clone from .git file' '
+       git clone dst/.git dst2
+'
+
 test_expect_success 'clone separate gitdir where target already exists' '
        rm -rf dst &&
        test_must_fail git clone --separate-git-dir realgitdir src dst
 '
 
+test_expect_success 'clone --reference from original' '
+       git clone --shared --bare src src-1 &&
+       git clone --bare src src-2 &&
+       git clone --reference=src-2 --bare src-1 target-8 &&
+       grep /src-2/ target-8/objects/info/alternates
+'
+
+test_expect_success 'clone with more than one --reference' '
+       git clone --bare src src-3 &&
+       git clone --bare src src-4 &&
+       git clone --reference=src-3 --reference=src-4 src target-9 &&
+       grep /src-3/ target-9/.git/objects/info/alternates &&
+       grep /src-4/ target-9/.git/objects/info/alternates
+'
+
+test_expect_success 'clone from original with relative alternate' '
+       mkdir nest &&
+       git clone --bare src nest/src-5 &&
+       echo ../../../src/.git/objects >nest/src-5/objects/info/alternates &&
+       git clone --bare nest/src-5 target-10 &&
+       grep /src/\\.git/objects target-10/objects/info/alternates
+'
+
 test_done
index 76410293b34ecede219445e09b1eccb8175ee115..39b4cb0ecdc8801b355b815a0566c6e2df5c5a39 100755 (executable)
@@ -70,4 +70,42 @@ test_expect_success 'rev-list --ancestry-patch D..M -- M.t' '
        test_cmp expect actual
 '
 
+#   b---bc
+#  / \ /
+# a   X
+#  \ / \
+#   c---cb
+#
+# All refnames prefixed with 'x' to avoid confusion with the tags
+# generated by test_commit on case-insensitive systems.
+test_expect_success 'setup criss-cross' '
+       mkdir criss-cross &&
+       (cd criss-cross &&
+        git init &&
+        test_commit A &&
+        git checkout -b xb master &&
+        test_commit B &&
+        git checkout -b xc master &&
+        test_commit C &&
+        git checkout -b xbc xb -- &&
+        git merge xc &&
+        git checkout -b xcb xc -- &&
+        git merge xb &&
+        git checkout master)
+'
+
+# no commits in bc descend from cb
+test_expect_success 'criss-cross: rev-list --ancestry-path cb..bc' '
+       (cd criss-cross &&
+        git rev-list --ancestry-path xcb..xbc > actual &&
+        test -z "$(cat actual)")
+'
+
+# no commits in repository descend from cb
+test_expect_success 'criss-cross: rev-list --ancestry-path --all ^cb' '
+       (cd criss-cross &&
+        git rev-list --ancestry-path --all ^xcb > actual &&
+        test -z "$(cat actual)")
+'
+
 test_done
index eec8f4e3edd85e350f5b8d8c86415317cce2aba1..27c3d73961dfc9b50c3eafc7360662076cb31260 100755 (executable)
@@ -59,15 +59,19 @@ test_expect_success 'setup modify/delete + directory/file conflict' '
        git add letters &&
        git commit -m initial &&
 
+       # Throw in letters.txt for sorting order fun
+       # ("letters.txt" sorts between "letters" and "letters/file")
        echo i >>letters &&
-       git add letters &&
+       echo "version 2" >letters.txt &&
+       git add letters letters.txt &&
        git commit -m modified &&
 
        git checkout -b delete HEAD^ &&
        git rm letters &&
        mkdir letters &&
        >letters/file &&
-       git add letters &&
+       echo "version 1" >letters.txt &&
+       git add letters letters.txt &&
        git commit -m deleted
 '
 
@@ -75,25 +79,31 @@ test_expect_success 'modify/delete + directory/file conflict' '
        git checkout delete^0 &&
        test_must_fail git merge modify &&
 
-       test 3 = $(git ls-files -s | wc -l) &&
-       test 2 = $(git ls-files -u | wc -l) &&
-       test 1 = $(git ls-files -o | wc -l) &&
+       test 5 -eq $(git ls-files -s | wc -l) &&
+       test 4 -eq $(git ls-files -u | wc -l) &&
+       test 1 -eq $(git ls-files -o | wc -l) &&
 
        test -f letters/file &&
+       test -f letters.txt &&
        test -f letters~modify
 '
 
 test_expect_success 'modify/delete + directory/file conflict; other way' '
+       # Yes, we really need the double reset since "letters" appears as
+       # both a file and a directory.
+       git reset --hard &&
        git reset --hard &&
        git clean -f &&
        git checkout modify^0 &&
+
        test_must_fail git merge delete &&
 
-       test 3 = $(git ls-files -s | wc -l) &&
-       test 2 = $(git ls-files -u | wc -l) &&
-       test 1 = $(git ls-files -o | wc -l) &&
+       test 5 -eq $(git ls-files -s | wc -l) &&
+       test 4 -eq $(git ls-files -u | wc -l) &&
+       test 1 -eq $(git ls-files -o | wc -l) &&
 
        test -f letters/file &&
+       test -f letters.txt &&
        test -f letters~HEAD
 '
 
index 1ed259d864b4ef4c8c7060fa5f22634a25c8e032..9d8584e957a26cadda2f04d38d27fd0c4b97ae29 100755 (executable)
@@ -252,6 +252,7 @@ test_expect_success 'setup for rename + d/f conflicts' '
        git reset --hard &&
        git checkout --orphan dir-in-way &&
        git rm -rf . &&
+       git clean -fdqx &&
 
        mkdir sub &&
        mkdir dir &&
@@ -302,11 +303,11 @@ test_expect_success 'Rename+D/F conflict; renamed file merges but dir in way' '
        git checkout -q renamed-file-has-no-conflicts^0 &&
        test_must_fail git merge --strategy=recursive dir-in-way >output &&
 
-       grep "CONFLICT (delete/modify): dir/file-in-the-way" output &&
+       grep "CONFLICT (modify/delete): dir/file-in-the-way" output &&
        grep "Auto-merging dir" output &&
        grep "Adding as dir~HEAD instead" output &&
 
-       test 2 -eq "$(git ls-files -u | wc -l)" &&
+       test 3 -eq "$(git ls-files -u | wc -l)" &&
        test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" &&
 
        test_must_fail git diff --quiet &&
@@ -324,11 +325,11 @@ test_expect_success 'Same as previous, but merged other way' '
        test_must_fail git merge --strategy=recursive renamed-file-has-no-conflicts >output 2>errors &&
 
        ! grep "error: refusing to lose untracked file at" errors &&
-       grep "CONFLICT (delete/modify): dir/file-in-the-way" output &&
+       grep "CONFLICT (modify/delete): dir/file-in-the-way" output &&
        grep "Auto-merging dir" output &&
        grep "Adding as dir~renamed-file-has-no-conflicts instead" output &&
 
-       test 2 -eq "$(git ls-files -u | wc -l)" &&
+       test 3 -eq "$(git ls-files -u | wc -l)" &&
        test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" &&
 
        test_must_fail git diff --quiet &&
@@ -350,11 +351,11 @@ cat >expected <<\EOF &&
 8
 9
 10
-<<<<<<< HEAD
+<<<<<<< HEAD:dir
 12
 =======
 11
->>>>>>> dir-not-in-way
+>>>>>>> dir-not-in-way:sub/file
 EOF
 
 test_expect_success 'Rename+D/F conflict; renamed file cannot merge, dir not in way' '
@@ -404,11 +405,11 @@ cat >expected <<\EOF &&
 8
 9
 10
-<<<<<<< HEAD
+<<<<<<< HEAD:sub/file
 11
 =======
 12
->>>>>>> renamed-file-has-conflicts
+>>>>>>> renamed-file-has-conflicts:dir
 EOF
 
 test_expect_success 'Same as previous, but merged other way' '
@@ -609,4 +610,278 @@ test_expect_success 'check handling of differently renamed file with D/F conflic
        ! test -f original
 '
 
+test_expect_success 'setup avoid unnecessary update, normal rename' '
+       git reset --hard &&
+       git checkout --orphan avoid-unnecessary-update-1 &&
+       git rm -rf . &&
+       git clean -fdqx &&
+
+       printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >original &&
+       git add -A &&
+       git commit -m "Common commmit" &&
+
+       git mv original rename &&
+       echo 11 >>rename &&
+       git add -u &&
+       git commit -m "Renamed and modified" &&
+
+       git checkout -b merge-branch-1 HEAD~1 &&
+       echo "random content" >random-file &&
+       git add -A &&
+       git commit -m "Random, unrelated changes"
+'
+
+test_expect_success 'avoid unnecessary update, normal rename' '
+       git checkout -q avoid-unnecessary-update-1^0 &&
+       test-chmtime =1000000000 rename &&
+       test-chmtime -v +0 rename >expect &&
+       git merge merge-branch-1 &&
+       test-chmtime -v +0 rename >actual &&
+       test_cmp expect actual # "rename" should have stayed intact
+'
+
+test_expect_success 'setup to test avoiding unnecessary update, with D/F conflict' '
+       git reset --hard &&
+       git checkout --orphan avoid-unnecessary-update-2 &&
+       git rm -rf . &&
+       git clean -fdqx &&
+
+       mkdir df &&
+       printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >df/file &&
+       git add -A &&
+       git commit -m "Common commmit" &&
+
+       git mv df/file temp &&
+       rm -rf df &&
+       git mv temp df &&
+       echo 11 >>df &&
+       git add -u &&
+       git commit -m "Renamed and modified" &&
+
+       git checkout -b merge-branch-2 HEAD~1 &&
+       >unrelated-change &&
+       git add unrelated-change &&
+       git commit -m "Only unrelated changes"
+'
+
+test_expect_success 'avoid unnecessary update, with D/F conflict' '
+       git checkout -q avoid-unnecessary-update-2^0 &&
+       test-chmtime =1000000000 df &&
+       test-chmtime -v +0 df >expect &&
+       git merge merge-branch-2 &&
+       test-chmtime -v +0 df >actual &&
+       test_cmp expect actual # "df" should have stayed intact
+'
+
+test_expect_success 'setup avoid unnecessary update, dir->(file,nothing)' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       >irrelevant &&
+       mkdir df &&
+       >df/file &&
+       git add -A &&
+       git commit -mA &&
+
+       git checkout -b side
+       git rm -rf df &&
+       git commit -mB &&
+
+       git checkout master &&
+       git rm -rf df &&
+       echo bla >df &&
+       git add -A &&
+       git commit -m "Add a newfile"
+'
+
+test_expect_success 'avoid unnecessary update, dir->(file,nothing)' '
+       git checkout -q master^0 &&
+       test-chmtime =1000000000 df &&
+       test-chmtime -v +0 df >expect &&
+       git merge side &&
+       test-chmtime -v +0 df >actual &&
+       test_cmp expect actual # "df" should have stayed intact
+'
+
+test_expect_success 'setup avoid unnecessary update, modify/delete' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       >irrelevant &&
+       >file &&
+       git add -A &&
+       git commit -mA &&
+
+       git checkout -b side
+       git rm -f file &&
+       git commit -m "Delete file" &&
+
+       git checkout master &&
+       echo bla >file &&
+       git add -A &&
+       git commit -m "Modify file"
+'
+
+test_expect_success 'avoid unnecessary update, modify/delete' '
+       git checkout -q master^0 &&
+       test-chmtime =1000000000 file &&
+       test-chmtime -v +0 file >expect &&
+       test_must_fail git merge side &&
+       test-chmtime -v +0 file >actual &&
+       test_cmp expect actual # "file" should have stayed intact
+'
+
+test_expect_success 'setup avoid unnecessary update, rename/add-dest' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       printf "1\n2\n3\n4\n5\n6\n7\n8\n" >file &&
+       git add -A &&
+       git commit -mA &&
+
+       git checkout -b side
+       cp file newfile &&
+       git add -A &&
+       git commit -m "Add file copy" &&
+
+       git checkout master &&
+       git mv file newfile &&
+       git commit -m "Rename file"
+'
+
+test_expect_success 'avoid unnecessary update, rename/add-dest' '
+       git checkout -q master^0 &&
+       test-chmtime =1000000000 newfile &&
+       test-chmtime -v +0 newfile >expect &&
+       git merge side &&
+       test-chmtime -v +0 newfile >actual &&
+       test_cmp expect actual # "file" should have stayed intact
+'
+
+test_expect_success 'setup merge of rename + small change' '
+       git reset --hard &&
+       git checkout --orphan rename-plus-small-change &&
+       git rm -rf . &&
+       git clean -fdqx &&
+
+       echo ORIGINAL >file &&
+       git add file &&
+
+       test_tick &&
+       git commit -m Initial &&
+       git checkout -b rename_branch &&
+       git mv file renamed_file &&
+       git commit -m Rename &&
+       git checkout rename-plus-small-change &&
+       echo NEW-VERSION >file &&
+       git commit -a -m Reformat
+'
+
+test_expect_success 'merge rename + small change' '
+       git merge rename_branch &&
+
+       test 1 -eq $(git ls-files -s | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+       test $(git rev-parse HEAD:renamed_file) = $(git rev-parse HEAD~1:file)
+'
+
+test_expect_success 'setup for use of extended merge markers' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       printf "1\n2\n3\n4\n5\n6\n7\n8\n" >original_file &&
+       git add original_file &&
+       git commit -mA &&
+
+       git checkout -b rename &&
+       echo 9 >>original_file &&
+       git add original_file &&
+       git mv original_file renamed_file &&
+       git commit -mB &&
+
+       git checkout master &&
+       echo 8.5 >>original_file &&
+       git add original_file &&
+       git commit -mC
+'
+
+cat >expected <<\EOF &&
+1
+2
+3
+4
+5
+6
+7
+8
+<<<<<<< HEAD:renamed_file
+9
+=======
+8.5
+>>>>>>> master^0:original_file
+EOF
+
+test_expect_success 'merge master into rename has correct extended markers' '
+       git checkout rename^0 &&
+       test_must_fail git merge -s recursive master^0 &&
+       test_cmp expected renamed_file
+'
+
+cat >expected <<\EOF &&
+1
+2
+3
+4
+5
+6
+7
+8
+<<<<<<< HEAD:original_file
+8.5
+=======
+9
+>>>>>>> rename^0:renamed_file
+EOF
+
+test_expect_success 'merge rename into master has correct extended markers' '
+       git reset --hard &&
+       git checkout master^0 &&
+       test_must_fail git merge -s recursive rename^0 &&
+       test_cmp expected renamed_file
+'
+
+test_expect_success 'setup spurious "refusing to lose untracked" message' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       > irrelevant_file &&
+       printf "1\n2\n3\n4\n5\n6\n7\n8\n" >original_file &&
+       git add irrelevant_file original_file &&
+       git commit -mA &&
+
+       git checkout -b rename &&
+       git mv original_file renamed_file &&
+       git commit -mB &&
+
+       git checkout master &&
+       git rm original_file &&
+       git commit -mC
+'
+
+test_expect_success 'no spurious "refusing to lose untracked" message' '
+       git checkout master^0 &&
+       test_must_fail git merge rename^0 2>errors.txt &&
+       ! grep "refusing to lose untracked file" errors.txt
+'
+
 test_done
index 62125eca8163f1ca52d57959fb872340479b9002..c6f1f9f8ab2353ec81401828f8500cb7c1a869fb 100755 (executable)
@@ -732,7 +732,7 @@ test_expect_success 'bisect: demonstrate identification of damage boundary' "
        git bisect reset &&
        git checkout broken &&
        git bisect start broken master --no-checkout &&
-       git bisect run sh -c '
+       git bisect run \"\$SHELL_PATH\" -c '
                GOOD=\$(git for-each-ref \"--format=%(objectname)\" refs/bisect/good-*) &&
                git rev-list --objects BISECT_HEAD --not \$GOOD >tmp.\$\$ &&
                git pack-objects --stdout >/dev/null < tmp.\$\$
index 871577d90cc2817ffcc0cfe1ecf76aab6be61723..dfee7d159b3b5198b4773f2d3869e809095b11f7 100755 (executable)
@@ -1,9 +1,15 @@
 #!/bin/sh
 
-test_description='recursive merge corner cases'
+test_description='recursive merge corner cases involving criss-cross merges'
 
 . ./test-lib.sh
 
+get_clean_checkout () {
+       git reset --hard &&
+       git clean -fdqx &&
+       git checkout "$1"
+}
+
 #
 #  L1  L2
 #   o---o
@@ -51,23 +57,15 @@ test_expect_success 'merge simple rename+criss-cross with no modifications' '
 
        test_must_fail git merge -s recursive R2^0 &&
 
-       test 5 = $(git ls-files -s | wc -l) &&
-       test 3 = $(git ls-files -u | wc -l) &&
-       test 0 = $(git ls-files -o | wc -l) &&
+       test 2 = $(git ls-files -s | wc -l) &&
+       test 2 = $(git ls-files -u | wc -l) &&
+       test 2 = $(git ls-files -o | wc -l) &&
 
-       test $(git rev-parse :0:one) = $(git rev-parse L2:one) &&
-       test $(git rev-parse :0:two) = $(git rev-parse R2:two) &&
        test $(git rev-parse :2:three) = $(git rev-parse L2:three) &&
        test $(git rev-parse :3:three) = $(git rev-parse R2:three) &&
 
-       cp two merged &&
-       >empty &&
-       test_must_fail git merge-file \
-               -L "Temporary merge branch 2" \
-               -L "" \
-               -L "Temporary merge branch 1" \
-               merged empty one &&
-       test $(git rev-parse :1:three) = $(git hash-object merged)
+       test $(git rev-parse L2:three) = $(git hash-object three~HEAD) &&
+       test $(git rev-parse R2:three) = $(git hash-object three~R2^0)
 '
 
 #
@@ -126,24 +124,15 @@ test_expect_success 'merge criss-cross + rename merges with basic modification'
 
        test_must_fail git merge -s recursive R2^0 &&
 
-       test 5 = $(git ls-files -s | wc -l) &&
-       test 3 = $(git ls-files -u | wc -l) &&
-       test 0 = $(git ls-files -o | wc -l) &&
+       test 2 = $(git ls-files -s | wc -l) &&
+       test 2 = $(git ls-files -u | wc -l) &&
+       test 2 = $(git ls-files -o | wc -l) &&
 
-       test $(git rev-parse :0:one) = $(git rev-parse L2:one) &&
-       test $(git rev-parse :0:two) = $(git rev-parse R2:two) &&
        test $(git rev-parse :2:three) = $(git rev-parse L2:three) &&
        test $(git rev-parse :3:three) = $(git rev-parse R2:three) &&
 
-       head -n 10 two >merged &&
-       cp one merge-me &&
-       >empty &&
-       test_must_fail git merge-file \
-               -L "Temporary merge branch 2" \
-               -L "" \
-               -L "Temporary merge branch 1" \
-               merged empty merge-me &&
-       test $(git rev-parse :1:three) = $(git hash-object merged)
+       test $(git rev-parse L2:three) = $(git hash-object three~HEAD) &&
+       test $(git rev-parse R2:three) = $(git hash-object three~R2^0)
 '
 
 #
@@ -231,4 +220,557 @@ test_expect_success 'git detects differently handled merges conflict' '
        test $(git rev-parse :1:new_a) = $(git hash-object merged)
 '
 
+#
+# criss-cross + modify/delete:
+#
+#      B   D
+#      o---o
+#     / \ / \
+#  A o   X   ? F
+#     \ / \ /
+#      o---o
+#      C   E
+#
+#   Commit A: file with contents 'A\n'
+#   Commit B: file with contents 'B\n'
+#   Commit C: file not present
+#   Commit D: file with contents 'B\n'
+#   Commit E: file not present
+#
+# Merging commits D & E should result in modify/delete conflict.
+
+test_expect_success 'setup criss-cross + modify/delete resolved differently' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       echo A >file &&
+       git add file &&
+       test_tick &&
+       git commit -m A &&
+
+       git branch B &&
+       git checkout -b C &&
+       git rm file &&
+       test_tick &&
+       git commit -m C &&
+
+       git checkout B &&
+       echo B >file &&
+       git add file &&
+       test_tick &&
+       git commit -m B &&
+
+       git checkout B^0 &&
+       test_must_fail git merge C &&
+       echo B >file &&
+       git add file &&
+       test_tick &&
+       git commit -m D &&
+       git tag D &&
+
+       git checkout C^0 &&
+       test_must_fail git merge B &&
+       git rm file &&
+       test_tick &&
+       git commit -m E &&
+       git tag E
+'
+
+test_expect_success 'git detects conflict merging criss-cross+modify/delete' '
+       git checkout D^0 &&
+
+       test_must_fail git merge -s recursive E^0 &&
+
+       test 2 -eq $(git ls-files -s | wc -l) &&
+       test 2 -eq $(git ls-files -u | wc -l) &&
+
+       test $(git rev-parse :1:file) = $(git rev-parse master:file) &&
+       test $(git rev-parse :2:file) = $(git rev-parse B:file)
+'
+
+test_expect_success 'git detects conflict merging criss-cross+modify/delete, reverse direction' '
+       git reset --hard &&
+       git checkout E^0 &&
+
+       test_must_fail git merge -s recursive D^0 &&
+
+       test 2 -eq $(git ls-files -s | wc -l) &&
+       test 2 -eq $(git ls-files -u | wc -l) &&
+
+       test $(git rev-parse :1:file) = $(git rev-parse master:file) &&
+       test $(git rev-parse :3:file) = $(git rev-parse B:file)
+'
+
+#
+# criss-cross + modify/modify with very contrived file contents:
+#
+#      B   D
+#      o---o
+#     / \ / \
+#  A o   X   ? F
+#     \ / \ /
+#      o---o
+#      C   E
+#
+#   Commit A: file with contents 'A\n'
+#   Commit B: file with contents 'B\n'
+#   Commit C: file with contents 'C\n'
+#   Commit D: file with contents 'D\n'
+#   Commit E: file with contents:
+#      <<<<<<< Temporary merge branch 1
+#      C
+#      =======
+#      B
+#      >>>>>>> Temporary merge branch 2
+#
+# Now, when we merge commits D & E, does git detect the conflict?
+
+test_expect_success 'setup differently handled merges of content conflict' '
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       echo A >file &&
+       git add file &&
+       test_tick &&
+       git commit -m A &&
+
+       git branch B &&
+       git checkout -b C &&
+       echo C >file &&
+       git add file &&
+       test_tick &&
+       git commit -m C &&
+
+       git checkout B &&
+       echo B >file &&
+       git add file &&
+       test_tick &&
+       git commit -m B &&
+
+       git checkout B^0 &&
+       test_must_fail git merge C &&
+       echo D >file &&
+       git add file &&
+       test_tick &&
+       git commit -m D &&
+       git tag D &&
+
+       git checkout C^0 &&
+       test_must_fail git merge B &&
+       cat <<EOF >file &&
+<<<<<<< Temporary merge branch 1
+C
+=======
+B
+>>>>>>> Temporary merge branch 2
+EOF
+       git add file &&
+       test_tick &&
+       git commit -m E &&
+       git tag E
+'
+
+test_expect_failure 'git detects conflict w/ criss-cross+contrived resolution' '
+       git checkout D^0 &&
+
+       test_must_fail git merge -s recursive E^0 &&
+
+       test 3 -eq $(git ls-files -s | wc -l) &&
+       test 3 -eq $(git ls-files -u | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse :2:file) = $(git rev-parse D:file) &&
+       test $(git rev-parse :3:file) = $(git rev-parse E:file)
+'
+
+#
+# criss-cross + d/f conflict via add/add:
+#   Commit A: Neither file 'a' nor directory 'a/' exist.
+#   Commit B: Introduce 'a'
+#   Commit C: Introduce 'a/file'
+#   Commit D: Merge B & C, keeping 'a' and deleting 'a/'
+#
+# Two different later cases:
+#   Commit E1: Merge B & C, deleting 'a' but keeping 'a/file'
+#   Commit E2: Merge B & C, deleting 'a' but keeping a slightly modified 'a/file'
+#
+#      B   D
+#      o---o
+#     / \ / \
+#  A o   X   ? F
+#     \ / \ /
+#      o---o
+#      C   E1 or E2
+#
+# Merging D & E1 requires we first create a virtual merge base X from
+# merging A & B in memory.  Now, if X could keep both 'a' and 'a/file' in
+# the index, then the merge of D & E1 could be resolved cleanly with both
+# 'a' and 'a/file' removed.  Since git does not currently allow creating
+# such a tree, the best we can do is have X contain both 'a~<unique>' and
+# 'a/file' resulting in the merge of D and E1 having a rename/delete
+# conflict for 'a'.  (Although this merge appears to be unsolvable with git
+# currently, git could do a lot better than it currently does with these
+# d/f conflicts, which is the purpose of this test.)
+#
+# Merge of D & E2 has similar issues for path 'a', but should always result
+# in a modify/delete conflict for path 'a/file'.
+#
+# We run each merge in both directions, to check for directional issues
+# with D/F conflict handling.
+#
+
+test_expect_success 'setup differently handled merges of directory/file conflict' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       >ignore-me &&
+       git add ignore-me &&
+       test_tick &&
+       git commit -m A &&
+       git tag A &&
+
+       git branch B &&
+       git checkout -b C &&
+       mkdir a &&
+       echo 10 >a/file &&
+       git add a/file &&
+       test_tick &&
+       git commit -m C &&
+
+       git checkout B &&
+       echo 5 >a &&
+       git add a &&
+       test_tick &&
+       git commit -m B &&
+
+       git checkout B^0 &&
+       test_must_fail git merge C &&
+       git clean -f &&
+       rm -rf a/ &&
+       echo 5 >a &&
+       git add a &&
+       test_tick &&
+       git commit -m D &&
+       git tag D &&
+
+       git checkout C^0 &&
+       test_must_fail git merge B &&
+       git clean -f &&
+       git rm --cached a &&
+       echo 10 >a/file &&
+       git add a/file &&
+       test_tick &&
+       git commit -m E1 &&
+       git tag E1 &&
+
+       git checkout C^0 &&
+       test_must_fail git merge B &&
+       git clean -f &&
+       git rm --cached a &&
+       printf "10\n11\n" >a/file &&
+       git add a/file &&
+       test_tick &&
+       git commit -m E2 &&
+       git tag E2
+'
+
+test_expect_success 'merge of D & E1 fails but has appropriate contents' '
+       get_clean_checkout D^0 &&
+
+       test_must_fail git merge -s recursive E1^0 &&
+
+       test 2 -eq $(git ls-files -s | wc -l) &&
+       test 1 -eq $(git ls-files -u | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
+       test $(git rev-parse :2:a) = $(git rev-parse B:a)
+'
+
+test_expect_success 'merge of E1 & D fails but has appropriate contents' '
+       get_clean_checkout E1^0 &&
+
+       test_must_fail git merge -s recursive D^0 &&
+
+       test 2 -eq $(git ls-files -s | wc -l) &&
+       test 1 -eq $(git ls-files -u | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
+       test $(git rev-parse :3:a) = $(git rev-parse B:a)
+'
+
+test_expect_success 'merge of D & E2 fails but has appropriate contents' '
+       get_clean_checkout D^0 &&
+
+       test_must_fail git merge -s recursive E2^0 &&
+
+       test 4 -eq $(git ls-files -s | wc -l) &&
+       test 3 -eq $(git ls-files -u | wc -l) &&
+       test 1 -eq $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse :2:a) = $(git rev-parse B:a) &&
+       test $(git rev-parse :3:a/file) = $(git rev-parse E2:a/file) &&
+       test $(git rev-parse :1:a/file) = $(git rev-parse C:a/file) &&
+       test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
+
+       test -f a~HEAD
+'
+
+test_expect_success 'merge of E2 & D fails but has appropriate contents' '
+       get_clean_checkout E2^0 &&
+
+       test_must_fail git merge -s recursive D^0 &&
+
+       test 4 -eq $(git ls-files -s | wc -l) &&
+       test 3 -eq $(git ls-files -u | wc -l) &&
+       test 1 -eq $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse :3:a) = $(git rev-parse B:a) &&
+       test $(git rev-parse :2:a/file) = $(git rev-parse E2:a/file) &&
+       test $(git rev-parse :1:a/file) = $(git rev-parse C:a/file)
+       test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
+
+       test -f a~D^0
+'
+
+#
+# criss-cross with rename/rename(1to2)/modify followed by
+# rename/rename(2to1)/modify:
+#
+#      B   D
+#      o---o
+#     / \ / \
+#  A o   X   ? F
+#     \ / \ /
+#      o---o
+#      C   E
+#
+#   Commit A: new file: a
+#   Commit B: rename a->b, modifying by adding a line
+#   Commit C: rename a->c
+#   Commit D: merge B&C, resolving conflict by keeping contents in newname
+#   Commit E: merge B&C, resolving conflict similar to D but adding another line
+#
+# There is a conflict merging B & C, but one of filename not of file
+# content.  Whoever created D and E chose specific resolutions for that
+# conflict resolution.  Now, since: (1) there is no content conflict
+# merging B & C, (2) D does not modify that merged content further, and (3)
+# both D & E resolve the name conflict in the same way, the modification to
+# newname in E should not cause any conflicts when it is merged with D.
+# (Note that this can be accomplished by having the virtual merge base have
+# the merged contents of b and c stored in a file named a, which seems like
+# the most logical choice anyway.)
+#
+# Comment from Junio: I do not necessarily agree with the choice "a", but
+# it feels sound to say "B and C do not agree what the final pathname
+# should be, but we know this content was derived from the common A:a so we
+# use one path whose name is arbitrary in the virtual merge base X between
+# D and E" and then further let the rename detection to notice that that
+# arbitrary path gets renamed between X-D to "newname" and X-E also to
+# "newname" to resolve it as both sides renaming it to the same new
+# name. It is akin to what we do at the content level, i.e. "B and C do not
+# agree what the final contents should be, so we leave the conflict marker
+# but that may cancel out at the final merge stage".
+
+test_expect_success 'setup rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' '
+       git reset --hard &&
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       printf "1\n2\n3\n4\n5\n6\n" >a &&
+       git add a &&
+       git commit -m A &&
+       git tag A &&
+
+       git checkout -b B A &&
+       git mv a b &&
+       echo 7 >>b &&
+       git add -u &&
+       git commit -m B &&
+
+       git checkout -b C A &&
+       git mv a c &&
+       git commit -m C &&
+
+       git checkout -q B^0 &&
+       git merge --no-commit -s ours C^0 &&
+       git mv b newname &&
+       git commit -m "Merge commit C^0 into HEAD" &&
+       git tag D &&
+
+       git checkout -q C^0 &&
+       git merge --no-commit -s ours B^0 &&
+       git mv c newname &&
+       printf "7\n8\n" >>newname &&
+       git add -u &&
+       git commit -m "Merge commit B^0 into HEAD" &&
+       git tag E
+'
+
+test_expect_success 'handle rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' '
+       git checkout D^0 &&
+
+       git merge -s recursive E^0 &&
+
+       test 1 -eq $(git ls-files -s | wc -l) &&
+       test 0 -eq $(git ls-files -u | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse HEAD:newname) = $(git rev-parse E:newname)
+'
+
+#
+# criss-cross with rename/rename(1to2)/add-source + resolvable modify/modify:
+#
+#      B   D
+#      o---o
+#     / \ / \
+#  A o   X   ? F
+#     \ / \ /
+#      o---o
+#      C   E
+#
+#   Commit A: new file: a
+#   Commit B: rename a->b
+#   Commit C: rename a->c, add different a
+#   Commit D: merge B&C, keeping b&c and (new) a modified at beginning
+#   Commit E: merge B&C, keeping b&c and (new) a modified at end
+#
+# Merging commits D & E should result in no conflict; doing so correctly
+# requires getting the virtual merge base (from merging B&C) right, handling
+# renaming carefully (both in the virtual merge base and later), and getting
+# content merge handled.
+
+test_expect_success 'setup criss-cross + rename/rename/add + modify/modify' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       printf "lots\nof\nwords\nand\ncontent\n" >a &&
+       git add a &&
+       git commit -m A &&
+       git tag A &&
+
+       git checkout -b B A &&
+       git mv a b &&
+       git commit -m B &&
+
+       git checkout -b C A &&
+       git mv a c &&
+       printf "2\n3\n4\n5\n6\n7\n" >a &&
+       git add a &&
+       git commit -m C &&
+
+       git checkout B^0 &&
+       git merge --no-commit -s ours C^0 &&
+       git checkout C -- a c &&
+       mv a old_a &&
+       echo 1 >a &&
+       cat old_a >>a &&
+       rm old_a &&
+       git add -u &&
+       git commit -m "Merge commit C^0 into HEAD" &&
+       git tag D &&
+
+       git checkout C^0 &&
+       git merge --no-commit -s ours B^0 &&
+       git checkout B -- b &&
+       echo 8 >>a &&
+       git add -u &&
+       git commit -m "Merge commit B^0 into HEAD" &&
+       git tag E
+'
+
+test_expect_failure 'detect rename/rename/add-source for virtual merge-base' '
+       git checkout D^0 &&
+
+       git merge -s recursive E^0 &&
+
+       test 3 -eq $(git ls-files -s | wc -l) &&
+       test 0 -eq $(git ls-files -u | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse HEAD:b) = $(git rev-parse A:a) &&
+       test $(git rev-parse HEAD:c) = $(git rev-parse A:a) &&
+       test "$(cat a)" = "$(printf "1\n2\n3\n4\n5\n6\n7\n8\n")"
+'
+
+#
+# criss-cross with rename/rename(1to2)/add-dest + simple modify:
+#
+#      B   D
+#      o---o
+#     / \ / \
+#  A o   X   ? F
+#     \ / \ /
+#      o---o
+#      C   E
+#
+#   Commit A: new file: a
+#   Commit B: rename a->b, add c
+#   Commit C: rename a->c
+#   Commit D: merge B&C, keeping A:a and B:c
+#   Commit E: merge B&C, keeping A:a and slightly modified c from B
+#
+# Merging commits D & E should result in no conflict.  The virtual merge
+# base of B & C needs to not delete B:c for that to work, though...
+
+test_expect_success 'setup criss-cross+rename/rename/add-dest + simple modify' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       >a &&
+       git add a &&
+       git commit -m A &&
+       git tag A &&
+
+       git checkout -b B A &&
+       git mv a b &&
+       printf "1\n2\n3\n4\n5\n6\n7\n" >c &&
+       git add c &&
+       git commit -m B &&
+
+       git checkout -b C A &&
+       git mv a c &&
+       git commit -m C &&
+
+       git checkout B^0 &&
+       git merge --no-commit -s ours C^0 &&
+       git mv b a &&
+       git commit -m "D is like B but renames b back to a" &&
+       git tag D &&
+
+       git checkout B^0 &&
+       git merge --no-commit -s ours C^0 &&
+       git mv b a &&
+       echo 8 >>c &&
+       git add c &&
+       git commit -m "E like D but has mod in c" &&
+       git tag E
+'
+
+test_expect_success 'virtual merge base handles rename/rename(1to2)/add-dest' '
+       git checkout D^0 &&
+
+       git merge -s recursive E^0 &&
+
+       test 2 -eq $(git ls-files -s | wc -l) &&
+       test 0 -eq $(git ls-files -u | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse HEAD:a) = $(git rev-parse A:a) &&
+       test $(git rev-parse HEAD:c) = $(git rev-parse E:c)
+'
+
 test_done
index a9b0ac1efc0eac3f50bd9fe26e5900307eddaba2..19de5b16eb530d428f66e5161a3ab48fce34049d 100755 (executable)
@@ -110,4 +110,18 @@ test_expect_success '--set-upstream does not change branch' '
        grep -q "^refs/heads/master$" actual &&
        cmp expect2 actual2
 '
+
+test_expect_success '--set-upstream @{-1}' '
+       git checkout from-master &&
+       git checkout from-master2 &&
+       git config branch.from-master2.merge > expect2 &&
+       git branch --set-upstream @{-1} follower &&
+       git config branch.from-master.merge > actual &&
+       git config branch.from-master2.merge > actual2 &&
+       git branch --set-upstream from-master follower &&
+       git config branch.from-master.merge > expect &&
+       test_cmp expect2 actual2 &&
+       test_cmp expect actual
+'
+
 test_done
diff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh
new file mode 100755 (executable)
index 0000000..32591f9
--- /dev/null
@@ -0,0 +1,578 @@
+#!/bin/sh
+
+test_description="recursive merge corner cases w/ renames but not criss-crosses"
+# t6036 has corner cases that involve both criss-cross merges and renames
+
+. ./test-lib.sh
+
+test_expect_success 'setup rename/delete + untracked file' '
+       echo "A pretty inscription" >ring &&
+       git add ring &&
+       test_tick &&
+       git commit -m beginning &&
+
+       git branch people &&
+       git checkout -b rename-the-ring &&
+       git mv ring one-ring-to-rule-them-all &&
+       test_tick &&
+       git commit -m fullname &&
+
+       git checkout people &&
+       git rm ring &&
+       echo gollum >owner &&
+       git add owner &&
+       test_tick &&
+       git commit -m track-people-instead-of-objects &&
+       echo "Myyy PRECIOUSSS" >ring
+'
+
+test_expect_success "Does git preserve Gollum's precious artifact?" '
+       test_must_fail git merge -s recursive rename-the-ring &&
+
+       # Make sure git did not delete an untracked file
+       test -f ring
+'
+
+# Testcase setup for rename/modify/add-source:
+#   Commit A: new file: a
+#   Commit B: modify a slightly
+#   Commit C: rename a->b, add completely different a
+#
+# We should be able to merge B & C cleanly
+
+test_expect_success 'setup rename/modify/add-source conflict' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       printf "1\n2\n3\n4\n5\n6\n7\n" >a &&
+       git add a &&
+       git commit -m A &&
+       git tag A &&
+
+       git checkout -b B A &&
+       echo 8 >>a &&
+       git add a &&
+       git commit -m B &&
+
+       git checkout -b C A &&
+       git mv a b &&
+       echo something completely different >a &&
+       git add a &&
+       git commit -m C
+'
+
+test_expect_failure 'rename/modify/add-source conflict resolvable' '
+       git checkout B^0 &&
+
+       git merge -s recursive C^0 &&
+
+       test $(git rev-parse B:a) = $(git rev-parse b) &&
+       test $(git rev-parse C:a) = $(git rev-parse a)
+'
+
+test_expect_success 'setup resolvable conflict missed if rename missed' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       printf "1\n2\n3\n4\n5\n" >a &&
+       echo foo >b &&
+       git add a b &&
+       git commit -m A &&
+       git tag A &&
+
+       git checkout -b B A &&
+       git mv a c &&
+       echo "Completely different content" >a &&
+       git add a &&
+       git commit -m B &&
+
+       git checkout -b C A &&
+       echo 6 >>a &&
+       git add a &&
+       git commit -m C
+'
+
+test_expect_failure 'conflict caused if rename not detected' '
+       git checkout -q C^0 &&
+       git merge -s recursive B^0 &&
+
+       test 3 -eq $(git ls-files -s | wc -l) &&
+       test 0 -eq $(git ls-files -u | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       test 6 -eq $(wc -l < c) &&
+       test $(git rev-parse HEAD:a) = $(git rev-parse B:a) &&
+       test $(git rev-parse HEAD:b) = $(git rev-parse A:b)
+'
+
+test_expect_success 'setup conflict resolved wrong if rename missed' '
+       git reset --hard &&
+       git clean -f &&
+
+       git checkout -b D A &&
+       echo 7 >>a &&
+       git add a &&
+       git mv a c &&
+       echo "Completely different content" >a &&
+       git add a &&
+       git commit -m D &&
+
+       git checkout -b E A &&
+       git rm a &&
+       echo "Completely different content" >>a &&
+       git add a &&
+       git commit -m E
+'
+
+test_expect_failure 'missed conflict if rename not detected' '
+       git checkout -q E^0 &&
+       test_must_fail git merge -s recursive D^0
+'
+
+# Tests for undetected rename/add-source causing a file to erroneously be
+# deleted (and for mishandled rename/rename(1to1) causing the same issue).
+#
+# This test uses a rename/rename(1to1)+add-source conflict (1to1 means the
+# same file is renamed on both sides to the same thing; it should trigger
+# the 1to2 logic, which it would do if the add-source didn't cause issues
+# for git's rename detection):
+#   Commit A: new file: a
+#   Commit B: rename a->b
+#   Commit C: rename a->b, add unrelated a
+
+test_expect_success 'setup undetected rename/add-source causes data loss' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       printf "1\n2\n3\n4\n5\n" >a &&
+       git add a &&
+       git commit -m A &&
+       git tag A &&
+
+       git checkout -b B A &&
+       git mv a b &&
+       git commit -m B &&
+
+       git checkout -b C A &&
+       git mv a b &&
+       echo foobar >a &&
+       git add a &&
+       git commit -m C
+'
+
+test_expect_failure 'detect rename/add-source and preserve all data' '
+       git checkout B^0 &&
+
+       git merge -s recursive C^0 &&
+
+       test 2 -eq $(git ls-files -s | wc -l) &&
+       test 2 -eq $(git ls-files -u | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       test -f a &&
+       test -f b &&
+
+       test $(git rev-parse HEAD:b) = $(git rev-parse A:a) &&
+       test $(git rev-parse HEAD:a) = $(git rev-parse C:a)
+'
+
+test_expect_failure 'detect rename/add-source and preserve all data, merge other way' '
+       git checkout C^0 &&
+
+       git merge -s recursive B^0 &&
+
+       test 2 -eq $(git ls-files -s | wc -l) &&
+       test 2 -eq $(git ls-files -u | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       test -f a &&
+       test -f b &&
+
+       test $(git rev-parse HEAD:b) = $(git rev-parse A:a) &&
+       test $(git rev-parse HEAD:a) = $(git rev-parse C:a)
+'
+
+test_expect_success 'setup content merge + rename/directory conflict' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       printf "1\n2\n3\n4\n5\n6\n" >file &&
+       git add file &&
+       test_tick &&
+       git commit -m base &&
+       git tag base &&
+
+       git checkout -b right &&
+       echo 7 >>file &&
+       mkdir newfile &&
+       echo junk >newfile/realfile &&
+       git add file newfile/realfile &&
+       test_tick &&
+       git commit -m right &&
+
+       git checkout -b left-conflict base &&
+       echo 8 >>file &&
+       git add file &&
+       git mv file newfile &&
+       test_tick &&
+       git commit -m left &&
+
+       git checkout -b left-clean base &&
+       echo 0 >newfile &&
+       cat file >>newfile &&
+       git add newfile &&
+       git rm file &&
+       test_tick &&
+       git commit -m left
+'
+
+test_expect_success 'rename/directory conflict + clean content merge' '
+       git reset --hard &&
+       git reset --hard &&
+       git clean -fdqx &&
+
+       git checkout left-clean^0 &&
+
+       test_must_fail git merge -s recursive right^0 &&
+
+       test 2 -eq $(git ls-files -s | wc -l) &&
+       test 1 -eq $(git ls-files -u | wc -l) &&
+       test 1 -eq $(git ls-files -o | wc -l) &&
+
+       echo 0 >expect &&
+       git cat-file -p base:file >>expect &&
+       echo 7 >>expect &&
+       test_cmp expect newfile~HEAD &&
+
+       test $(git rev-parse :2:newfile) = $(git hash-object expect) &&
+
+       test -f newfile/realfile &&
+       test -f newfile~HEAD
+'
+
+test_expect_success 'rename/directory conflict + content merge conflict' '
+       git reset --hard &&
+       git reset --hard &&
+       git clean -fdqx &&
+
+       git checkout left-conflict^0 &&
+
+       test_must_fail git merge -s recursive right^0 &&
+
+       test 4 -eq $(git ls-files -s | wc -l) &&
+       test 3 -eq $(git ls-files -u | wc -l) &&
+       test 1 -eq $(git ls-files -o | wc -l) &&
+
+       git cat-file -p left-conflict:newfile >left &&
+       git cat-file -p base:file    >base &&
+       git cat-file -p right:file   >right &&
+       test_must_fail git merge-file \
+               -L "HEAD:newfile" \
+               -L "" \
+               -L "right^0:file" \
+               left base right &&
+       test_cmp left newfile~HEAD &&
+
+       test $(git rev-parse :1:newfile) = $(git rev-parse base:file) &&
+       test $(git rev-parse :2:newfile) = $(git rev-parse left-conflict:newfile) &&
+       test $(git rev-parse :3:newfile) = $(git rev-parse right:file) &&
+
+       test -f newfile/realfile &&
+       test -f newfile~HEAD
+'
+
+test_expect_success 'setup content merge + rename/directory conflict w/ disappearing dir' '
+       git reset --hard &&
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       mkdir sub &&
+       printf "1\n2\n3\n4\n5\n6\n" >sub/file &&
+       git add sub/file &&
+       test_tick &&
+       git commit -m base &&
+       git tag base &&
+
+       git checkout -b right &&
+       echo 7 >>sub/file &&
+       git add sub/file &&
+       test_tick &&
+       git commit -m right &&
+
+       git checkout -b left base &&
+       echo 0 >newfile &&
+       cat sub/file >>newfile &&
+       git rm sub/file &&
+       mv newfile sub &&
+       git add sub &&
+       test_tick &&
+       git commit -m left
+'
+
+test_expect_success 'disappearing dir in rename/directory conflict handled' '
+       git reset --hard &&
+       git clean -fdqx &&
+
+       git checkout left^0 &&
+
+       git merge -s recursive right^0 &&
+
+       test 1 -eq $(git ls-files -s | wc -l) &&
+       test 0 -eq $(git ls-files -u | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       echo 0 >expect &&
+       git cat-file -p base:sub/file >>expect &&
+       echo 7 >>expect &&
+       test_cmp expect sub &&
+
+       test -f sub
+'
+
+# Test for all kinds of things that can go wrong with rename/rename (2to1):
+#   Commit A: new files: a & b
+#   Commit B: rename a->c, modify b
+#   Commit C: rename b->c, modify a
+#
+# Merging of B & C should NOT be clean.  Questions:
+#   * Both a & b should be removed by the merge; are they?
+#   * The two c's should contain modifications to a & b; do they?
+#   * The index should contain two files, both for c; does it?
+#   * The working copy should have two files, both of form c~<unique>; does it?
+#   * Nothing else should be present.  Is anything?
+
+test_expect_success 'setup rename/rename (2to1) + modify/modify' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       printf "1\n2\n3\n4\n5\n" >a &&
+       printf "5\n4\n3\n2\n1\n" >b &&
+       git add a b &&
+       git commit -m A &&
+       git tag A &&
+
+       git checkout -b B A &&
+       git mv a c &&
+       echo 0 >>b &&
+       git add b &&
+       git commit -m B &&
+
+       git checkout -b C A &&
+       git mv b c &&
+       echo 6 >>a &&
+       git add a &&
+       git commit -m C
+'
+
+test_expect_success 'handle rename/rename (2to1) conflict correctly' '
+       git checkout B^0 &&
+
+       test_must_fail git merge -s recursive C^0 >out &&
+       grep "CONFLICT (rename/rename)" out &&
+
+       test 2 -eq $(git ls-files -s | wc -l) &&
+       test 2 -eq $(git ls-files -u | wc -l) &&
+       test 2 -eq $(git ls-files -u c | wc -l) &&
+       test 3 -eq $(git ls-files -o | wc -l) &&
+
+       test ! -f a &&
+       test ! -f b &&
+       test -f c~HEAD &&
+       test -f c~C^0 &&
+
+       test $(git hash-object c~HEAD) = $(git rev-parse C:a) &&
+       test $(git hash-object c~C^0) = $(git rev-parse B:b)
+'
+
+# Testcase setup for simple rename/rename (1to2) conflict:
+#   Commit A: new file: a
+#   Commit B: rename a->b
+#   Commit C: rename a->c
+test_expect_success 'setup simple rename/rename (1to2) conflict' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       echo stuff >a &&
+       git add a &&
+       test_tick &&
+       git commit -m A &&
+       git tag A &&
+
+       git checkout -b B A &&
+       git mv a b &&
+       test_tick &&
+       git commit -m B &&
+
+       git checkout -b C A &&
+       git mv a c &&
+       test_tick &&
+       git commit -m C
+'
+
+test_expect_success 'merge has correct working tree contents' '
+       git checkout C^0 &&
+
+       test_must_fail git merge -s recursive B^0 &&
+
+       test 3 -eq $(git ls-files -s | wc -l) &&
+       test 3 -eq $(git ls-files -u | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse :1:a) = $(git rev-parse A:a) &&
+       test $(git rev-parse :3:b) = $(git rev-parse A:a) &&
+       test $(git rev-parse :2:c) = $(git rev-parse A:a) &&
+
+       test ! -f a &&
+       test $(git hash-object b) = $(git rev-parse A:a) &&
+       test $(git hash-object c) = $(git rev-parse A:a)
+'
+
+# Testcase setup for rename/rename(1to2)/add-source conflict:
+#   Commit A: new file: a
+#   Commit B: rename a->b
+#   Commit C: rename a->c, add completely different a
+#
+# Merging of B & C should NOT be clean; there's a rename/rename conflict
+
+test_expect_success 'setup rename/rename(1to2)/add-source conflict' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       printf "1\n2\n3\n4\n5\n6\n7\n" >a &&
+       git add a &&
+       git commit -m A &&
+       git tag A &&
+
+       git checkout -b B A &&
+       git mv a b &&
+       git commit -m B &&
+
+       git checkout -b C A &&
+       git mv a c &&
+       echo something completely different >a &&
+       git add a &&
+       git commit -m C
+'
+
+test_expect_failure 'detect conflict with rename/rename(1to2)/add-source merge' '
+       git checkout B^0 &&
+
+       test_must_fail git merge -s recursive C^0 &&
+
+       test 4 -eq $(git ls-files -s | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse 3:a) = $(git rev-parse C:a) &&
+       test $(git rev-parse 1:a) = $(git rev-parse A:a) &&
+       test $(git rev-parse 2:b) = $(git rev-parse B:b) &&
+       test $(git rev-parse 3:c) = $(git rev-parse C:c) &&
+
+       test -f a &&
+       test -f b &&
+       test -f c
+'
+
+test_expect_success 'setup rename/rename(1to2)/add-source resolvable conflict' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       >a &&
+       git add a &&
+       test_tick &&
+       git commit -m base &&
+       git tag A &&
+
+       git checkout -b B A &&
+       git mv a b &&
+       test_tick &&
+       git commit -m one &&
+
+       git checkout -b C A &&
+       git mv a b &&
+       echo important-info >a &&
+       git add a &&
+       test_tick &&
+       git commit -m two
+'
+
+test_expect_failure 'rename/rename/add-source still tracks new a file' '
+       git checkout C^0 &&
+       git merge -s recursive B^0 &&
+
+       test 2 -eq $(git ls-files -s | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse HEAD:a) = $(git rev-parse C:a) &&
+       test $(git rev-parse HEAD:b) = $(git rev-parse A:a)
+'
+
+test_expect_success 'setup rename/rename(1to2)/add-dest conflict' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       echo stuff >a &&
+       git add a &&
+       test_tick &&
+       git commit -m base &&
+       git tag A &&
+
+       git checkout -b B A &&
+       git mv a b &&
+       echo precious-data >c &&
+       git add c &&
+       test_tick &&
+       git commit -m one &&
+
+       git checkout -b C A &&
+       git mv a c &&
+       echo important-info >b &&
+       git add b &&
+       test_tick &&
+       git commit -m two
+'
+
+test_expect_success 'rename/rename/add-dest merge still knows about conflicting file versions' '
+       git checkout C^0 &&
+       test_must_fail git merge -s recursive B^0 &&
+
+       test 5 -eq $(git ls-files -s | wc -l) &&
+       test 2 -eq $(git ls-files -u b | wc -l) &&
+       test 2 -eq $(git ls-files -u c | wc -l) &&
+       test 4 -eq $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse :1:a) = $(git rev-parse A:a) &&
+       test $(git rev-parse :2:b) = $(git rev-parse C:b) &&
+       test $(git rev-parse :3:b) = $(git rev-parse B:b) &&
+       test $(git rev-parse :2:c) = $(git rev-parse C:c) &&
+       test $(git rev-parse :3:c) = $(git rev-parse B:c) &&
+
+       test $(git hash-object c~HEAD) = $(git rev-parse C:c) &&
+       test $(git hash-object c~B\^0) = $(git rev-parse B:c) &&
+       test $(git hash-object b~HEAD) = $(git rev-parse C:b) &&
+       test $(git hash-object b~B\^0) = $(git rev-parse B:b) &&
+
+       test ! -f b &&
+       test ! -f c
+'
+
+test_done
index 7dc8a510c7f33d048cd3268424298fdda2f8c2bb..172178490ad126e07b70a6e3f72e0bf723d2c42b 100755 (executable)
@@ -6,6 +6,7 @@
 test_description='for-each-ref test'
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-gpg.sh
 
 # Mon Jul 3 15:18:43 2006 +0000
 datestamp=1151939923
@@ -37,11 +38,13 @@ test_atom() {
        case "$1" in
                head) ref=refs/heads/master ;;
                 tag) ref=refs/tags/testtag ;;
+                  *) ref=$1 ;;
        esac
        printf '%s\n' "$3" >expected
-       test_expect_${4:-success} "basic atom: $1 $2" "
+       test_expect_${4:-success} $PREREQ "basic atom: $1 $2" "
                git for-each-ref --format='%($2)' $ref >actual &&
-               test_cmp expected actual
+               sanitize_pgp <actual >actual.clean &&
+               test_cmp expected actual.clean
        "
 }
 
@@ -71,7 +74,10 @@ test_atom head taggerdate ''
 test_atom head creator 'C O Mitter <committer@example.com> 1151939923 +0200'
 test_atom head creatordate 'Mon Jul 3 17:18:43 2006 +0200'
 test_atom head subject 'Initial'
+test_atom head contents:subject 'Initial'
 test_atom head body ''
+test_atom head contents:body ''
+test_atom head contents:signature ''
 test_atom head contents 'Initial
 '
 
@@ -101,7 +107,10 @@ test_atom tag taggerdate 'Mon Jul 3 17:18:45 2006 +0200'
 test_atom tag creator 'C O Mitter <committer@example.com> 1151939925 +0200'
 test_atom tag creatordate 'Mon Jul 3 17:18:45 2006 +0200'
 test_atom tag subject 'Tagging at 1151939927'
+test_atom tag contents:subject 'Tagging at 1151939927'
 test_atom tag body ''
+test_atom tag contents:body ''
+test_atom tag contents:signature ''
 test_atom tag contents 'Tagging at 1151939927
 '
 
@@ -359,4 +368,92 @@ test_expect_success 'an unusual tag with an incomplete line' '
 
 '
 
+test_expect_success 'create tag with subject and body content' '
+       cat >>msg <<-\EOF &&
+               the subject line
+
+               first body line
+               second body line
+       EOF
+       git tag -F msg subject-body
+'
+test_atom refs/tags/subject-body subject 'the subject line'
+test_atom refs/tags/subject-body body 'first body line
+second body line
+'
+test_atom refs/tags/subject-body contents 'the subject line
+
+first body line
+second body line
+'
+
+test_expect_success 'create tag with multiline subject' '
+       cat >msg <<-\EOF &&
+               first subject line
+               second subject line
+
+               first body line
+               second body line
+       EOF
+       git tag -F msg multiline
+'
+test_atom refs/tags/multiline subject 'first subject line second subject line'
+test_atom refs/tags/multiline contents:subject 'first subject line second subject line'
+test_atom refs/tags/multiline body 'first body line
+second body line
+'
+test_atom refs/tags/multiline contents:body 'first body line
+second body line
+'
+test_atom refs/tags/multiline contents:signature ''
+test_atom refs/tags/multiline contents 'first subject line
+second subject line
+
+first body line
+second body line
+'
+
+test_expect_success GPG 'create signed tags' '
+       git tag -s -m "" signed-empty &&
+       git tag -s -m "subject line" signed-short &&
+       cat >msg <<-\EOF &&
+       subject line
+
+       body contents
+       EOF
+       git tag -s -F msg signed-long
+'
+
+sig='-----BEGIN PGP SIGNATURE-----
+-----END PGP SIGNATURE-----
+'
+
+PREREQ=GPG
+test_atom refs/tags/signed-empty subject ''
+test_atom refs/tags/signed-empty contents:subject ''
+test_atom refs/tags/signed-empty body "$sig"
+test_atom refs/tags/signed-empty contents:body ''
+test_atom refs/tags/signed-empty contents:signature "$sig"
+test_atom refs/tags/signed-empty contents "$sig"
+
+test_atom refs/tags/signed-short subject 'subject line'
+test_atom refs/tags/signed-short contents:subject 'subject line'
+test_atom refs/tags/signed-short body "$sig"
+test_atom refs/tags/signed-short contents:body ''
+test_atom refs/tags/signed-short contents:signature "$sig"
+test_atom refs/tags/signed-short contents "subject line
+$sig"
+
+test_atom refs/tags/signed-long subject 'subject line'
+test_atom refs/tags/signed-long contents:subject 'subject line'
+test_atom refs/tags/signed-long body "body contents
+$sig"
+test_atom refs/tags/signed-long contents:body 'body contents
+'
+test_atom refs/tags/signed-long contents:signature "$sig"
+test_atom refs/tags/signed-long contents "subject line
+
+body contents
+$sig"
+
 test_done
index 097ce2bc8382e748925aa25c2dc0a1f06f6c7b4c..e93ac73829f332cdbf53b05fcc611d4ea38c4c55 100755 (executable)
@@ -8,6 +8,7 @@ test_description='git tag
 Tests for operations with tags.'
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-gpg.sh
 
 # creating and listing lightweight tags:
 
@@ -585,24 +586,6 @@ test_expect_success \
        test_cmp expect actual
 '
 
-# subsequent tests require gpg; check if it is available
-gpg --version >/dev/null 2>/dev/null
-if [ $? -eq 127 ]; then
-       say "# gpg not found - skipping tag signing and verification tests"
-else
-       # As said here: http://www.gnupg.org/documentation/faqs.html#q6.19
-       # the gpg version 1.0.6 didn't parse trust packets correctly, so for
-       # that version, creation of signed tags using the generated key fails.
-       case "$(gpg --version)" in
-       'gpg (GnuPG) 1.0.6'*)
-               say "Skipping signed tag tests, because a bug in 1.0.6 version"
-               ;;
-       *)
-               test_set_prereq GPG
-               ;;
-       esac
-fi
-
 # trying to verify annotated non-signed tags:
 
 test_expect_success GPG \
@@ -625,16 +608,6 @@ test_expect_success GPG \
 
 # creating and verifying signed tags:
 
-# key generation info: gpg --homedir t/t7004 --gen-key
-# Type DSA and Elgamal, size 2048 bits, no expiration date.
-# Name and email: C O Mitter <committer@example.com>
-# No password given, to enable non-interactive operation.
-
-cp -R "$TEST_DIRECTORY"/t7004 ./gpghome
-chmod 0700 gpghome
-GNUPGHOME="$(pwd)/gpghome"
-export GNUPGHOME
-
 get_tag_header signed-tag $commit commit $time >expect
 echo 'A signed tag message' >>expect
 echo '-----BEGIN PGP SIGNATURE-----' >>expect
diff --git a/t/t7004/pubring.gpg b/t/t7004/pubring.gpg
deleted file mode 100644 (file)
index 83855fa..0000000
Binary files a/t/t7004/pubring.gpg and /dev/null differ
diff --git a/t/t7004/random_seed b/t/t7004/random_seed
deleted file mode 100644 (file)
index 8fed133..0000000
Binary files a/t/t7004/random_seed and /dev/null differ
diff --git a/t/t7004/secring.gpg b/t/t7004/secring.gpg
deleted file mode 100644 (file)
index d831cd9..0000000
Binary files a/t/t7004/secring.gpg and /dev/null differ
diff --git a/t/t7004/trustdb.gpg b/t/t7004/trustdb.gpg
deleted file mode 100644 (file)
index abace96..0000000
Binary files a/t/t7004/trustdb.gpg and /dev/null differ
index ed7575d0fdf9eebf65d808fa810ab4cc03095025..320e1d1dbe62c81e29186d7a32b73447d2f998b8 100755 (executable)
@@ -13,7 +13,7 @@ cleanup_fail() {
 
 test_expect_success 'setup' '
        sane_unset GIT_PAGER GIT_PAGER_IN_USE &&
-       test_might_fail git config --unset core.pager &&
+       test_unconfig core.pager &&
 
        PAGER="cat >paginated.out" &&
        export PAGER &&
@@ -94,21 +94,19 @@ test_expect_success TTY 'no pager with --no-pager' '
 
 test_expect_success TTY 'configuration can disable pager' '
        rm -f paginated.out &&
-       test_might_fail git config --unset pager.grep &&
+       test_unconfig pager.grep &&
        test_terminal git grep initial &&
        test -e paginated.out &&
 
        rm -f paginated.out &&
-       git config pager.grep false &&
-       test_when_finished "git config --unset pager.grep" &&
+       test_config pager.grep false &&
        test_terminal git grep initial &&
        ! test -e paginated.out
 '
 
 test_expect_success TTY 'git config uses a pager if configured to' '
        rm -f paginated.out &&
-       git config pager.config true &&
-       test_when_finished "git config --unset pager.config" &&
+       test_config pager.config true &&
        test_terminal git config --list &&
        test -e paginated.out
 '
@@ -116,8 +114,7 @@ test_expect_success TTY 'git config uses a pager if configured to' '
 test_expect_success TTY 'configuration can enable pager (from subdir)' '
        rm -f paginated.out &&
        mkdir -p subdir &&
-       git config pager.bundle true &&
-       test_when_finished "git config --unset pager.bundle" &&
+       test_config pager.bundle true &&
 
        git bundle create test.bundle --all &&
        rm -f paginated.out subdir/paginated.out &&
@@ -150,7 +147,7 @@ test_expect_success 'tests can detect color' '
 
 test_expect_success 'no color when stdout is a regular file' '
        rm -f colorless.log &&
-       git config color.ui auto ||
+       test_config color.ui auto ||
        cleanup_fail &&
 
        git log >colorless.log &&
@@ -159,7 +156,7 @@ test_expect_success 'no color when stdout is a regular file' '
 
 test_expect_success TTY 'color when writing to a pager' '
        rm -f paginated.out &&
-       git config color.ui auto ||
+       test_config color.ui auto ||
        cleanup_fail &&
 
        (
@@ -170,9 +167,21 @@ test_expect_success TTY 'color when writing to a pager' '
        colorful paginated.out
 '
 
+test_expect_success TTY 'colors are suppressed by color.pager' '
+       rm -f paginated.out &&
+       test_config color.ui auto &&
+       test_config color.pager false &&
+       (
+               TERM=vt100 &&
+               export TERM &&
+               test_terminal git log
+       ) &&
+       ! colorful paginated.out
+'
+
 test_expect_success 'color when writing to a file intended for a pager' '
        rm -f colorful.log &&
-       git config color.ui auto ||
+       test_config color.ui auto ||
        cleanup_fail &&
 
        (
@@ -184,6 +193,17 @@ test_expect_success 'color when writing to a file intended for a pager' '
        colorful colorful.log
 '
 
+test_expect_success TTY 'colors are sent to pager for external commands' '
+       test_config alias.externallog "!git log" &&
+       test_config color.ui auto &&
+       (
+               TERM=vt100 &&
+               export TERM &&
+               test_terminal git -p externallog
+       ) &&
+       colorful paginated.out
+'
+
 # Use this helper to make it easy for the caller of your
 # terminal-using function to specify whether it should fail.
 # If you write
@@ -221,7 +241,7 @@ test_default_pager() {
 
        $test_expectation SIMPLEPAGER,TTY "$cmd - default pager is used by default" "
                sane_unset PAGER GIT_PAGER &&
-               test_might_fail git config --unset core.pager &&
+               test_unconfig core.pager &&
                rm -f default_pager_used ||
                cleanup_fail &&
 
@@ -244,7 +264,7 @@ test_PAGER_overrides() {
 
        $test_expectation TTY "$cmd - PAGER overrides default pager" "
                sane_unset GIT_PAGER &&
-               test_might_fail git config --unset core.pager &&
+               test_unconfig core.pager &&
                rm -f PAGER_used ||
                cleanup_fail &&
 
@@ -277,7 +297,7 @@ test_core_pager() {
 
                PAGER=wc &&
                export PAGER &&
-               git config core.pager 'wc >core.pager_used' &&
+               test_config core.pager 'wc >core.pager_used' &&
                $full_command &&
                ${if_local_config}test -e core.pager_used
        "
@@ -307,7 +327,7 @@ test_pager_subdir_helper() {
                PAGER=wc &&
                stampname=\$(pwd)/core.pager_used &&
                export PAGER stampname &&
-               git config core.pager 'wc >\"\$stampname\"' &&
+               test_config core.pager 'wc >\"\$stampname\"' &&
                mkdir sub &&
                (
                        cd sub &&
@@ -324,7 +344,7 @@ test_GIT_PAGER_overrides() {
                rm -f GIT_PAGER_used ||
                cleanup_fail &&
 
-               git config core.pager wc &&
+               test_config core.pager wc &&
                GIT_PAGER='wc >GIT_PAGER_used' &&
                export GIT_PAGER &&
                $full_command &&
@@ -402,21 +422,21 @@ test_core_pager_subdir    expect_success test_must_fail \
                                         'git -p apply </dev/null'
 
 test_expect_success TTY 'command-specific pager' '
-       unset PAGER GIT_PAGER;
+       sane_unset PAGER GIT_PAGER &&
        echo "foo:initial" >expect &&
        >actual &&
-       git config --unset core.pager &&
-       git config pager.log "sed s/^/foo:/ >actual" &&
+       test_unconfig core.pager &&
+       test_config pager.log "sed s/^/foo:/ >actual" &&
        test_terminal git log --format=%s -1 &&
        test_cmp expect actual
 '
 
 test_expect_success TTY 'command-specific pager overrides core.pager' '
-       unset PAGER GIT_PAGER;
+       sane_unset PAGER GIT_PAGER &&
        echo "foo:initial" >expect &&
        >actual &&
-       git config core.pager "exit 1"
-       git config pager.log "sed s/^/foo:/ >actual" &&
+       test_config core.pager "exit 1"
+       test_config pager.log "sed s/^/foo:/ >actual" &&
        test_terminal git log --format=%s -1 &&
        test_cmp expect actual
 '
@@ -425,9 +445,45 @@ test_expect_success TTY 'command-specific pager overridden by environment' '
        GIT_PAGER="sed s/^/foo:/ >actual" && export GIT_PAGER &&
        >actual &&
        echo "foo:initial" >expect &&
-       git config pager.log "exit 1" &&
+       test_config pager.log "exit 1" &&
        test_terminal git log --format=%s -1 &&
        test_cmp expect actual
 '
 
+test_expect_success 'setup external command' '
+       cat >git-external <<-\EOF &&
+       #!/bin/sh
+       git "$@"
+       EOF
+       chmod +x git-external
+'
+
+test_expect_success TTY 'command-specific pager works for external commands' '
+       sane_unset PAGER GIT_PAGER &&
+       echo "foo:initial" >expect &&
+       >actual &&
+       test_config pager.external "sed s/^/foo:/ >actual" &&
+       test_terminal git --exec-path="`pwd`" external log --format=%s -1 &&
+       test_cmp expect actual
+'
+
+test_expect_success TTY 'sub-commands of externals use their own pager' '
+       sane_unset PAGER GIT_PAGER &&
+       echo "foo:initial" >expect &&
+       >actual &&
+       test_config pager.log "sed s/^/foo:/ >actual" &&
+       test_terminal git --exec-path=. external log --format=%s -1 &&
+       test_cmp expect actual
+'
+
+test_expect_success TTY 'external command pagers override sub-commands' '
+       sane_unset PAGER GIT_PAGER &&
+       >expect &&
+       >actual &&
+       test_config pager.external false &&
+       test_config pager.log "sed s/^/log:/ >actual" &&
+       test_terminal git --exec-path=. external log --format=%s -1 &&
+       test_cmp expect actual
+'
+
 test_done
diff --git a/t/t7106-reset-sequence.sh b/t/t7106-reset-sequence.sh
new file mode 100755 (executable)
index 0000000..4956caa
--- /dev/null
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+test_description='Test interaction of reset --hard with sequencer
+
+  + anotherpick: rewrites foo to d
+  + picked: rewrites foo to c
+  + unrelatedpick: rewrites unrelated to reallyunrelated
+  + base: rewrites foo to b
+  + initial: writes foo as a, unrelated as unrelated
+'
+
+. ./test-lib.sh
+
+pristine_detach () {
+       git cherry-pick --reset &&
+       git checkout -f "$1^0" &&
+       git read-tree -u --reset HEAD &&
+       git clean -d -f -f -q -x
+}
+
+test_expect_success setup '
+       echo unrelated >unrelated &&
+       git add unrelated &&
+       test_commit initial foo a &&
+       test_commit base foo b &&
+       test_commit unrelatedpick unrelated reallyunrelated &&
+       test_commit picked foo c &&
+       test_commit anotherpick foo d &&
+       git config advice.detachedhead false
+
+'
+
+test_expect_success 'reset --hard cleans up sequencer state, providing one-level undo' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick base..anotherpick &&
+       test_path_is_dir .git/sequencer &&
+       git reset --hard &&
+       test_path_is_missing .git/sequencer &&
+       test_path_is_dir .git/sequencer-old &&
+       git reset --hard &&
+       test_path_is_missing .git/sequencer-old
+'
+
+test_done
index 0a46795ae785fd4dfa0e565bfa0153ca107a904d..61f36baa1f3459d68ac30e67680e096093cbe414 100755 (executable)
@@ -53,7 +53,7 @@ cat >expected <<\EOF
 Trying simple merge with c2
 Trying simple merge with c3
 Trying simple merge with c4
-Merge made by octopus.
+Merge made by the 'octopus' strategy.
  c2.c |    1 +
  c3.c |    1 +
  c4.c |    1 +
@@ -72,7 +72,7 @@ test_expect_success 'merge output uses pretty names' '
 cat >expected <<\EOF
 Already up-to-date with c4
 Trying simple merge with c5
-Merge made by octopus.
+Merge made by the 'octopus' strategy.
  c5.c |    1 +
  1 files changed, 1 insertions(+), 0 deletions(-)
  create mode 100644 c5.c
@@ -86,7 +86,7 @@ test_expect_success 'merge up-to-date output uses pretty names' '
 cat >expected <<\EOF
 Fast-forwarding to: c1
 Trying simple merge with c2
-Merge made by octopus.
+Merge made by the 'octopus' strategy.
  c1.c |    1 +
  c2.c |    1 +
  2 files changed, 2 insertions(+), 0 deletions(-)
index 4048d106d4936ae1eef2ca3788a147e659bda65d..395adfc8a946bfd67b8a61cbef1366b78d9d855e 100755 (executable)
@@ -10,9 +10,6 @@ Testing basic diff tool invocation
 
 . ./test-lib.sh
 
-LF='
-'
-
 remove_config_vars()
 {
        # Unset all config variables used by git-difftool
index 3ab43902b3412d887794b7ca41d37b67af657361..8c9539e1b47a817fff98bad1ed1a99e890386ec9 100755 (executable)
@@ -38,4 +38,17 @@ test_expect_success 'verify svn:mergeinfo' '
        test "$mergeinfo" = "/branches/foo:1-10"
 '
 
+test_expect_success 'change svn:mergeinfo multiline' '
+       touch baz &&
+       git add baz &&
+       git commit -m "baz" &&
+       git svn dcommit --mergeinfo="/branches/bar:1-10 /branches/other:3-5,8,10-11"
+'
+
+test_expect_success 'verify svn:mergeinfo multiline' '
+       mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/trunk)
+       test "$mergeinfo" = "/branches/bar:1-10
+/branches/other:3-5,8,10-11"
+'
+
 test_done
diff --git a/t/t9160-git-svn-preserve-empty-dirs.sh b/t/t9160-git-svn-preserve-empty-dirs.sh
new file mode 100755 (executable)
index 0000000..b4a4434
--- /dev/null
@@ -0,0 +1,153 @@
+#!/bin/sh
+#
+# Copyright (c) 2011 Ray Chen
+#
+
+test_description='git svn test (option --preserve-empty-dirs)
+
+This test uses git to clone a Subversion repository that contains empty
+directories, and checks that corresponding directories are created in the
+local Git repository with placeholder files.'
+
+. ./lib-git-svn.sh
+
+say 'define NO_SVN_TESTS to skip git svn tests'
+GIT_REPO=git-svn-repo
+
+test_expect_success 'initialize source svn repo containing empty dirs' '
+       svn_cmd mkdir -m x "$svnrepo"/trunk &&
+       svn_cmd co "$svnrepo"/trunk "$SVN_TREE" &&
+       (
+               cd "$SVN_TREE" &&
+               mkdir -p 1 2 3/a 3/b 4 5 6 &&
+               echo "First non-empty file"  > 2/file1.txt &&
+               echo "Second non-empty file" > 2/file2.txt &&
+               echo "Third non-empty file"  > 3/a/file1.txt &&
+               echo "Fourth non-empty file" > 3/b/file1.txt &&
+               svn_cmd add 1 2 3 4 5 6 &&
+               svn_cmd commit -m "initial commit" &&
+
+               mkdir 4/a &&
+               svn_cmd add 4/a &&
+               svn_cmd commit -m "nested empty directory" &&
+               mkdir 4/a/b &&
+               svn_cmd add 4/a/b &&
+               svn_cmd commit -m "deeply nested empty directory" &&
+               mkdir 4/a/b/c &&
+               svn_cmd add 4/a/b/c &&
+               svn_cmd commit -m "really deeply nested empty directory" &&
+               echo "Kill the placeholder file" > 4/a/b/c/foo &&
+               svn_cmd add 4/a/b/c/foo &&
+               svn_cmd commit -m "Regular file to remove placeholder" &&
+
+               svn_cmd del 2/file2.txt &&
+               svn_cmd del 3/b &&
+               svn_cmd commit -m "delete non-last entry in directory" &&
+
+               svn_cmd del 2/file1.txt &&
+               svn_cmd del 3/a &&
+               svn_cmd commit -m "delete last entry in directory" &&
+
+               echo "Conflict file" > 5/.placeholder &&
+               mkdir 6/.placeholder &&
+               svn_cmd add 5/.placeholder 6/.placeholder &&
+               svn_cmd commit -m "Placeholder Namespace conflict"
+       ) &&
+       rm -rf "$SVN_TREE"
+'
+
+test_expect_success 'clone svn repo with --preserve-empty-dirs' '
+       git svn clone "$svnrepo"/trunk --preserve-empty-dirs "$GIT_REPO"
+'
+
+# "$GIT_REPO"/1 should only contain the placeholder file.
+test_expect_success 'directory empty from inception' '
+       test -f "$GIT_REPO"/1/.gitignore &&
+       test $(find "$GIT_REPO"/1 -type f | wc -l) = "1"
+'
+
+# "$GIT_REPO"/2 and "$GIT_REPO"/3 should only contain the placeholder file.
+test_expect_success 'directory empty from subsequent svn commit' '
+       test -f "$GIT_REPO"/2/.gitignore &&
+       test $(find "$GIT_REPO"/2 -type f | wc -l) = "1" &&
+       test -f "$GIT_REPO"/3/.gitignore &&
+       test $(find "$GIT_REPO"/3 -type f | wc -l) = "1"
+'
+
+# No placeholder files should exist in "$GIT_REPO"/4, even though one was
+# generated for every sub-directory at some point in the repo's history.
+test_expect_success 'add entry to previously empty directory' '
+       test $(find "$GIT_REPO"/4 -type f | wc -l) = "1" &&
+       test -f "$GIT_REPO"/4/a/b/c/foo
+'
+
+# The HEAD~2 commit should not have introduced .gitignore placeholder files.
+test_expect_success 'remove non-last entry from directory' '
+       (
+               cd "$GIT_REPO" &&
+               git checkout HEAD~2
+       ) &&
+       test_must_fail test -f "$GIT_REPO"/2/.gitignore &&
+       test_must_fail test -f "$GIT_REPO"/3/.gitignore
+'
+
+# After re-cloning the repository with --placeholder-file specified, there
+# should be 5 files named ".placeholder" in the local Git repo.
+test_expect_success 'clone svn repo with --placeholder-file specified' '
+       rm -rf "$GIT_REPO" &&
+       git svn clone "$svnrepo"/trunk --preserve-empty-dirs \
+               --placeholder-file=.placeholder "$GIT_REPO" &&
+       find "$GIT_REPO" -type f -name ".placeholder" &&
+       test $(find "$GIT_REPO" -type f -name ".placeholder" | wc -l) = "5"
+'
+
+# "$GIT_REPO"/5/.placeholder should be a file, and non-empty.
+test_expect_success 'placeholder namespace conflict with file' '
+       test -s "$GIT_REPO"/5/.placeholder
+'
+
+# "$GIT_REPO"/6/.placeholder should be a directory, and the "$GIT_REPO"/6 tree
+# should only contain one file: the placeholder.
+test_expect_success 'placeholder namespace conflict with directory' '
+       test -d "$GIT_REPO"/6/.placeholder &&
+       test -f "$GIT_REPO"/6/.placeholder/.placeholder &&
+       test $(find "$GIT_REPO"/6 -type f | wc -l) = "1"
+'
+
+# Prepare a second set of svn commits to test persistence during rebase.
+test_expect_success 'second set of svn commits and rebase' '
+       svn_cmd co "$svnrepo"/trunk "$SVN_TREE" &&
+       (
+               cd "$SVN_TREE" &&
+               mkdir -p 7 &&
+               echo "This should remove placeholder" > 1/file1.txt &&
+               echo "This should not remove placeholder" > 5/file1.txt &&
+               svn_cmd add 7 1/file1.txt 5/file1.txt &&
+               svn_cmd commit -m "subsequent svn commit for persistence tests"
+       ) &&
+       rm -rf "$SVN_TREE" &&
+       (
+               cd "$GIT_REPO" &&
+               git svn rebase
+       )
+'
+
+# Check that --preserve-empty-dirs and --placeholder-file flag state
+# stays persistent over multiple invocations.
+test_expect_success 'flag persistence during subsqeuent rebase' '
+       test -f "$GIT_REPO"/7/.placeholder &&
+       test $(find "$GIT_REPO"/7 -type f | wc -l) = "1"
+'
+
+# Check that placeholder files are properly removed when unnecessary,
+# even across multiple invocations.
+test_expect_success 'placeholder list persistence during subsqeuent rebase' '
+       test -f "$GIT_REPO"/1/file1.txt &&
+       test $(find "$GIT_REPO"/1 -type f | wc -l) = "1" &&
+
+       test -f "$GIT_REPO"/5/file1.txt &&
+       test -f "$GIT_REPO"/5/.placeholder &&
+       test $(find "$GIT_REPO"/5 -type f | wc -l) = "2"
+'
+
+test_done
diff --git a/t/t9161-git-svn-mergeinfo-push.sh b/t/t9161-git-svn-mergeinfo-push.sh
new file mode 100755 (executable)
index 0000000..6ef0c0b
--- /dev/null
@@ -0,0 +1,104 @@
+#!/bin/sh
+#
+# Portions copyright (c) 2007, 2009 Sam Vilain
+# Portions copyright (c) 2011 Bryan Jacobs
+#
+
+test_description='git-svn svn mergeinfo propagation'
+
+. ./lib-git-svn.sh
+
+test_expect_success 'load svn dump' "
+       svnadmin load -q '$rawsvnrepo' \
+         < '$TEST_DIRECTORY/t9161/branches.dump' &&
+       git svn init --minimize-url -R svnmerge \
+         -T trunk -b branches '$svnrepo' &&
+       git svn fetch --all
+       "
+
+test_expect_success 'propagate merge information' '
+       git config svn.pushmergeinfo yes &&
+       git checkout svnb1 &&
+       git merge --no-ff svnb2 &&
+       git svn dcommit
+       '
+
+test_expect_success 'check svn:mergeinfo' '
+       mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/branches/svnb1)
+       test "$mergeinfo" = "/branches/svnb2:3,8"
+       '
+
+test_expect_success 'merge another branch' '
+       git merge --no-ff svnb3 &&
+       git svn dcommit
+       '
+
+test_expect_success 'check primary parent mergeinfo respected' '
+       mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/branches/svnb1)
+       test "$mergeinfo" = "/branches/svnb2:3,8
+/branches/svnb3:4,9"
+       '
+
+test_expect_success 'merge existing merge' '
+       git merge --no-ff svnb4 &&
+       git svn dcommit
+       '
+
+test_expect_success "check both parents' mergeinfo respected" '
+       mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/branches/svnb1)
+       test "$mergeinfo" = "/branches/svnb2:3,8
+/branches/svnb3:4,9
+/branches/svnb4:5-6,10-12
+/branches/svnb5:6,11"
+       '
+
+test_expect_success 'make further commits to branch' '
+       git checkout svnb2 &&
+       touch newb2file &&
+       git add newb2file &&
+       git commit -m "later b2 commit" &&
+       touch newb2file-2 &&
+       git add newb2file-2 &&
+       git commit -m "later b2 commit 2" &&
+       git svn dcommit
+       '
+
+test_expect_success 'second forward merge' '
+       git checkout svnb1 &&
+       git merge --no-ff svnb2 &&
+       git svn dcommit
+       '
+
+test_expect_success 'check new mergeinfo added' '
+       mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/branches/svnb1)
+       test "$mergeinfo" = "/branches/svnb2:3,8,16-17
+/branches/svnb3:4,9
+/branches/svnb4:5-6,10-12
+/branches/svnb5:6,11"
+       '
+
+test_expect_success 'reintegration merge' '
+       git checkout svnb4 &&
+       git merge --no-ff svnb1 &&
+       git svn dcommit
+       '
+
+test_expect_success 'check reintegration mergeinfo' '
+       mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/branches/svnb4)
+       test "$mergeinfo" = "/branches/svnb1:2-4,7-9,13-18
+/branches/svnb2:3,8,16-17
+/branches/svnb3:4,9
+/branches/svnb4:5-6,10-12
+/branches/svnb5:6,11"
+       '
+
+test_expect_success 'dcommit a merge at the top of a stack' '
+       git checkout svnb1 &&
+       touch anotherfile &&
+       git add anotherfile &&
+       git commit -m "a commit" &&
+       git merge svnb4 &&
+       git svn dcommit
+       '
+
+test_done
diff --git a/t/t9161/branches.dump b/t/t9161/branches.dump
new file mode 100644 (file)
index 0000000..e61c3e7
--- /dev/null
@@ -0,0 +1,374 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 1ef08553-f2d1-45df-b38c-19af6b7c926d
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2011-09-02T16:08:02.941384Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 12
+Base commit
+
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:08:27.205062Z
+PROPS-END
+
+Node-path: branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 2
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 19
+Create branch svnb1
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:09:43.628137Z
+PROPS-END
+
+Node-path: branches/svnb1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Revision-number: 3
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 19
+Create branch svnb2
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:09:46.339930Z
+PROPS-END
+
+Node-path: branches/svnb2
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Revision-number: 4
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 19
+Create branch svnb3
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:09:49.394515Z
+PROPS-END
+
+Node-path: branches/svnb3
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Revision-number: 5
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 19
+Create branch svnb4
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:09:54.114607Z
+PROPS-END
+
+Node-path: branches/svnb4
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Revision-number: 6
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 19
+Create branch svnb5
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:09:58.602623Z
+PROPS-END
+
+Node-path: branches/svnb5
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Revision-number: 7
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 9
+b1 commit
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:10:20.292369Z
+PROPS-END
+
+Node-path: branches/svnb1/b1file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 8
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 9
+b2 commit
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:10:38.429199Z
+PROPS-END
+
+Node-path: branches/svnb2/b2file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 9
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 9
+b3 commit
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:10:52.843023Z
+PROPS-END
+
+Node-path: branches/svnb3/b3file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 10
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 9
+b4 commit
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:11:17.489870Z
+PROPS-END
+
+Node-path: branches/svnb4/b4file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 11
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 9
+b5 commit
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:11:32.277404Z
+PROPS-END
+
+Node-path: branches/svnb5/b5file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 12
+Prop-content-length: 192
+Content-length: 192
+
+K 7
+svn:log
+V 90
+Merge remote-tracking branch 'svnb5' into HEAD
+
+* svnb5:
+  b5 commit
+  Create branch svnb5
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:11:54.274722Z
+PROPS-END
+
+Node-path: branches/svnb4
+Node-kind: dir
+Node-action: change
+Prop-content-length: 56
+Content-length: 56
+
+K 13
+svn:mergeinfo
+V 21
+/branches/svnb5:6,11
+
+PROPS-END
+
+
+Node-path: branches/svnb4/b5file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709
+Content-length: 10
+
+PROPS-END
+
+
index f256475020549a157297481e21859e235a5cfcc1..1a6c06631c1c2a8fc29b66c9265541b54e8a9715 100755 (executable)
@@ -94,6 +94,12 @@ data <<EOF
 An annotated tag without a tagger
 EOF
 
+tag series-A-blob
+from :3
+data <<EOF
+An annotated tag that annotates a blob.
+EOF
+
 INPUT_END
 test_expect_success \
     'A: create pack from stdin' \
@@ -151,6 +157,18 @@ test_expect_success 'A: verify tag/series-A' '
        test_cmp expect actual
 '
 
+cat >expect <<EOF
+object $(git rev-parse refs/heads/master:file3)
+type blob
+tag series-A-blob
+
+An annotated tag that annotates a blob.
+EOF
+test_expect_success 'A: verify tag/series-A-blob' '
+       git cat-file tag tags/series-A-blob >actual &&
+       test_cmp expect actual
+'
+
 cat >expect <<EOF
 :2 `git rev-parse --verify master:file2`
 :3 `git rev-parse --verify master:file3`
@@ -169,6 +187,55 @@ test_expect_success \
                </dev/null &&
        test_cmp expect marks.new'
 
+test_tick
+new_blob=$(echo testing | git hash-object --stdin)
+cat >input <<INPUT_END
+tag series-A-blob-2
+from $(git rev-parse refs/heads/master:file3)
+data <<EOF
+Tag blob by sha1.
+EOF
+
+blob
+mark :6
+data <<EOF
+testing
+EOF
+
+commit refs/heads/new_blob
+committer  <> 0 +0000
+data 0
+M 644 :6 new_blob
+#pretend we got sha1 from fast-import
+ls "new_blob"
+
+tag series-A-blob-3
+from $new_blob
+data <<EOF
+Tag new_blob.
+EOF
+INPUT_END
+
+cat >expect <<EOF
+object $(git rev-parse refs/heads/master:file3)
+type blob
+tag series-A-blob-2
+
+Tag blob by sha1.
+object $new_blob
+type blob
+tag series-A-blob-3
+
+Tag new_blob.
+EOF
+
+test_expect_success \
+       'A: tag blob by sha1' \
+       'git fast-import <input &&
+       git cat-file tag tags/series-A-blob-2 >actual &&
+       git cat-file tag tags/series-A-blob-3 >>actual &&
+       test_cmp expect actual'
+
 test_tick
 cat >input <<INPUT_END
 commit refs/heads/verify--import-marks
@@ -324,6 +391,105 @@ test_expect_success \
         test `git rev-parse master` = `git rev-parse TEMP_TAG^`'
 rm -f .git/TEMP_TAG
 
+git gc 2>/dev/null >/dev/null
+git prune 2>/dev/null >/dev/null
+
+cat >input <<INPUT_END
+commit refs/heads/empty-committer-1
+committer  <> $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: accept empty committer' '
+       git fast-import <input &&
+       out=$(git fsck) &&
+       echo "$out" &&
+       test -z "$out"
+'
+git update-ref -d refs/heads/empty-committer-1 || true
+
+git gc 2>/dev/null >/dev/null
+git prune 2>/dev/null >/dev/null
+
+cat >input <<INPUT_END
+commit refs/heads/empty-committer-2
+committer <a@b.com> $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: accept and fixup committer with no name' '
+       git fast-import <input &&
+       out=$(git fsck) &&
+       echo "$out" &&
+       test -z "$out"
+'
+git update-ref -d refs/heads/empty-committer-2 || true
+
+git gc 2>/dev/null >/dev/null
+git prune 2>/dev/null >/dev/null
+
+cat >input <<INPUT_END
+commit refs/heads/invalid-committer
+committer Name email> $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: fail on invalid committer (1)' '
+       test_must_fail git fast-import <input
+'
+git update-ref -d refs/heads/invalid-committer || true
+
+cat >input <<INPUT_END
+commit refs/heads/invalid-committer
+committer Name <e<mail> $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: fail on invalid committer (2)' '
+       test_must_fail git fast-import <input
+'
+git update-ref -d refs/heads/invalid-committer || true
+
+cat >input <<INPUT_END
+commit refs/heads/invalid-committer
+committer Name <email>> $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: fail on invalid committer (3)' '
+       test_must_fail git fast-import <input
+'
+git update-ref -d refs/heads/invalid-committer || true
+
+cat >input <<INPUT_END
+commit refs/heads/invalid-committer
+committer Name <email $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: fail on invalid committer (4)' '
+       test_must_fail git fast-import <input
+'
+git update-ref -d refs/heads/invalid-committer || true
+
+cat >input <<INPUT_END
+commit refs/heads/invalid-committer
+committer Name<email> $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: fail on invalid committer (5)' '
+       test_must_fail git fast-import <input
+'
+git update-ref -d refs/heads/invalid-committer || true
+
 ###
 ### series C
 ###
@@ -734,6 +900,47 @@ test_expect_success \
         git diff-tree --abbrev --raw L^ L >output &&
         test_cmp expect output'
 
+cat >input <<INPUT_END
+blob
+mark :1
+data <<EOF
+the data
+EOF
+
+commit refs/heads/L2
+committer C O Mitter <committer@example.com> 1112912473 -0700
+data <<COMMIT
+init L2
+COMMIT
+M 644 :1 a/b/c
+M 644 :1 a/b/d
+M 644 :1 a/e/f
+
+commit refs/heads/L2
+committer C O Mitter <committer@example.com> 1112912473 -0700
+data <<COMMIT
+update L2
+COMMIT
+C a g
+C a/e g/b
+M 644 :1 g/b/h
+INPUT_END
+
+cat <<EOF >expect
+g/b/f
+g/b/h
+EOF
+
+test_expect_success \
+    'L: nested tree copy does not corrupt deltas' \
+       'git fast-import <input &&
+       git ls-tree L2 g/b/ >tmp &&
+       cat tmp | cut -f 2 >actual &&
+       test_cmp expect actual &&
+       git fsck `git rev-parse L2`'
+
+git update-ref -d refs/heads/L2
+
 ###
 ### series M
 ###
@@ -1882,6 +2089,53 @@ test_expect_success 'R: --import-marks-if-exists' '
        test_cmp expect io.marks
 '
 
+test_expect_success 'R: feature import-marks-if-exists' '
+       rm -f io.marks &&
+       >expect &&
+
+       git fast-import --export-marks=io.marks <<-\EOF &&
+       feature import-marks-if-exists=not_io.marks
+       EOF
+       test_cmp expect io.marks &&
+
+       blob=$(echo hi | git hash-object --stdin) &&
+
+       echo ":1 $blob" >io.marks &&
+       echo ":1 $blob" >expect &&
+       echo ":2 $blob" >>expect &&
+
+       git fast-import --export-marks=io.marks <<-\EOF &&
+       feature import-marks-if-exists=io.marks
+       blob
+       mark :2
+       data 3
+       hi
+
+       EOF
+       test_cmp expect io.marks &&
+
+       echo ":3 $blob" >>expect &&
+
+       git fast-import --import-marks=io.marks \
+                       --export-marks=io.marks <<-\EOF &&
+       feature import-marks-if-exists=not_io.marks
+       blob
+       mark :3
+       data 3
+       hi
+
+       EOF
+       test_cmp expect io.marks &&
+
+       >expect &&
+
+       git fast-import --import-marks-if-exists=not_io.marks \
+                       --export-marks=io.marks <<-\EOF
+       feature import-marks-if-exists=io.marks
+       EOF
+       test_cmp expect io.marks
+'
+
 cat >input << EOF
 feature import-marks=marks.out
 feature export-marks=marks.new
index 97ec9753b45b4f2786e247e2897b3185b7b66df8..01ba041fdefe61a3e712344bfe257a1b9cba3b4b 100755 (executable)
@@ -269,6 +269,203 @@ test_expect_success 'initial import time from top change time' '
        test $p4time = $gittime
 '
 
+# Rename a file and confirm that rename is not detected in P4.
+# Rename the new file again with detectRenames option enabled and confirm that
+# this is detected in P4.
+# Rename the new file again adding an extra line, configure a big threshold in
+# detectRenames and confirm that rename is not detected in P4.
+# Repeat, this time with a smaller threshold and confirm that the rename is
+# detected in P4.
+test_expect_success 'detect renames' '
+       "$GITP4" clone --dest="$git" //depot@all &&
+       test_when_finished cleanup_git &&
+       cd "$git" &&
+       git config git-p4.skipSubmitEditCheck true &&
+
+       git mv file1 file4 &&
+       git commit -a -m "Rename file1 to file4" &&
+       git diff-tree -r -M HEAD &&
+       "$GITP4" submit &&
+       p4 filelog //depot/file4 &&
+       ! p4 filelog //depot/file4 | grep -q "branch from" &&
+
+       git mv file4 file5 &&
+       git commit -a -m "Rename file4 to file5" &&
+       git diff-tree -r -M HEAD &&
+       git config git-p4.detectRenames true &&
+       "$GITP4" submit &&
+       p4 filelog //depot/file5 &&
+       p4 filelog //depot/file5 | grep -q "branch from //depot/file4" &&
+
+       git mv file5 file6 &&
+       echo update >>file6 &&
+       git add file6 &&
+       git commit -a -m "Rename file5 to file6 with changes" &&
+       git diff-tree -r -M HEAD &&
+       level=$(git diff-tree -r -M HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/R0*//") &&
+       test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 &&
+       git config git-p4.detectRenames $((level + 2)) &&
+       "$GITP4" submit &&
+       p4 filelog //depot/file6 &&
+       ! p4 filelog //depot/file6 | grep -q "branch from" &&
+
+       git mv file6 file7 &&
+       echo update >>file7 &&
+       git add file7 &&
+       git commit -a -m "Rename file6 to file7 with changes" &&
+       git diff-tree -r -M HEAD &&
+       level=$(git diff-tree -r -M HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/R0*//") &&
+       test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 &&
+       git config git-p4.detectRenames $((level - 2)) &&
+       "$GITP4" submit &&
+       p4 filelog //depot/file7 &&
+       p4 filelog //depot/file7 | grep -q "branch from //depot/file6"
+'
+
+# Copy a file and confirm that copy is not detected in P4.
+# Copy a file with detectCopies option enabled and confirm that copy is not
+# detected in P4.
+# Modify and copy a file with detectCopies option enabled and confirm that copy
+# is detected in P4.
+# Copy a file with detectCopies and detectCopiesHarder options enabled and
+# confirm that copy is detected in P4.
+# Modify and copy a file, configure a bigger threshold in detectCopies and
+# confirm that copy is not detected in P4.
+# Modify and copy a file, configure a smaller threshold in detectCopies and
+# confirm that copy is detected in P4.
+test_expect_success 'detect copies' '
+       "$GITP4" clone --dest="$git" //depot@all &&
+       test_when_finished cleanup_git &&
+       cd "$git" &&
+       git config git-p4.skipSubmitEditCheck true &&
+
+       cp file2 file8 &&
+       git add file8 &&
+       git commit -a -m "Copy file2 to file8" &&
+       git diff-tree -r -C HEAD &&
+       "$GITP4" submit &&
+       p4 filelog //depot/file8 &&
+       ! p4 filelog //depot/file8 | grep -q "branch from" &&
+
+       cp file2 file9 &&
+       git add file9 &&
+       git commit -a -m "Copy file2 to file9" &&
+       git diff-tree -r -C HEAD &&
+       git config git-p4.detectCopies true &&
+       "$GITP4" submit &&
+       p4 filelog //depot/file9 &&
+       ! p4 filelog //depot/file9 | grep -q "branch from" &&
+
+       echo "file2" >>file2 &&
+       cp file2 file10 &&
+       git add file2 file10 &&
+       git commit -a -m "Modify and copy file2 to file10" &&
+       git diff-tree -r -C HEAD &&
+       "$GITP4" submit &&
+       p4 filelog //depot/file10 &&
+       p4 filelog //depot/file10 | grep -q "branch from //depot/file" &&
+
+       cp file2 file11 &&
+       git add file11 &&
+       git commit -a -m "Copy file2 to file11" &&
+       git diff-tree -r -C --find-copies-harder HEAD &&
+       src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
+       test "$src" = file10 &&
+       git config git-p4.detectCopiesHarder true &&
+       "$GITP4" submit &&
+       p4 filelog //depot/file11 &&
+       p4 filelog //depot/file11 | grep -q "branch from //depot/file" &&
+
+       cp file2 file12 &&
+       echo "some text" >>file12 &&
+       git add file12 &&
+       git commit -a -m "Copy file2 to file12 with changes" &&
+       git diff-tree -r -C --find-copies-harder HEAD &&
+       level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") &&
+       test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 &&
+       src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
+       test "$src" = file10 &&
+       git config git-p4.detectCopies $((level + 2)) &&
+       "$GITP4" submit &&
+       p4 filelog //depot/file12 &&
+       ! p4 filelog //depot/file12 | grep -q "branch from" &&
+
+       cp file2 file13 &&
+       echo "different text" >>file13 &&
+       git add file13 &&
+       git commit -a -m "Copy file2 to file13 with changes" &&
+       git diff-tree -r -C --find-copies-harder HEAD &&
+       level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") &&
+       test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 &&
+       src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
+       test "$src" = file10 &&
+       git config git-p4.detectCopies $((level - 2)) &&
+       "$GITP4" submit &&
+       p4 filelog //depot/file13 &&
+       p4 filelog //depot/file13 | grep -q "branch from //depot/file"
+'
+
+# Create a simple branch structure in P4 depot to check if it is correctly
+# cloned.
+test_expect_success 'add simple p4 branches' '
+       cd "$cli" &&
+       mkdir branch1 &&
+       cd branch1 &&
+       echo file1 >file1 &&
+       echo file2 >file2 &&
+       p4 add file1 file2 &&
+       p4 submit -d "branch1" &&
+       p4 integrate //depot/branch1/... //depot/branch2/... &&
+       p4 submit -d "branch2" &&
+       echo file3 >file3 &&
+       p4 add file3 &&
+       p4 submit -d "add file3 in branch1" &&
+       p4 open file2 &&
+       echo update >>file2 &&
+       p4 submit -d "update file2 in branch1" &&
+       p4 integrate //depot/branch1/... //depot/branch3/... &&
+       p4 submit -d "branch3" &&
+       cd "$TRASH_DIRECTORY"
+'
+
+# Configure branches through git-config and clone them.
+# All files are tested to make sure branches were cloned correctly.
+# Finally, make an update to branch1 on P4 side to check if it is imported
+# correctly by git-p4.
+test_expect_success 'git-p4 clone simple branches' '
+       test_when_finished cleanup_git &&
+       test_create_repo "$git" &&
+       cd "$git" &&
+       git config git-p4.branchList branch1:branch2 &&
+       git config --add git-p4.branchList branch1:branch3 &&
+       "$GITP4" clone --dest=. --detect-branches //depot@all &&
+       git log --all --graph --decorate --stat &&
+       git reset --hard p4/depot/branch1 &&
+       test -f file1 &&
+       test -f file2 &&
+       test -f file3 &&
+       grep -q update file2 &&
+       git reset --hard p4/depot/branch2 &&
+       test -f file1 &&
+       test -f file2 &&
+       test ! -f file3 &&
+       ! grep -q update file2 &&
+       git reset --hard p4/depot/branch3 &&
+       test -f file1 &&
+       test -f file2 &&
+       test -f file3 &&
+       grep -q update file2 &&
+       cd "$cli" &&
+       cd branch1 &&
+       p4 edit file2 &&
+       echo file2_ >>file2 &&
+       p4 submit -d "update file2 in branch1" &&
+       cd "$git" &&
+       git reset --hard p4/depot/branch1 &&
+       "$GITP4" rebase &&
+       grep -q file2_ file2
+'
+
 test_expect_success 'shutdown' '
        pid=`pgrep -f p4d` &&
        test -n "$pid" &&
index df25f1792923a65aab2c4ce88e7b528effb381f5..bdd9513b84301275330d3dd7e49af05081ef9cd7 100644 (file)
@@ -92,6 +92,10 @@ _x40="$_x05$_x05$_x05$_x05$_x05$_x05$_x05$_x05"
 # Zero SHA-1
 _z40=0000000000000000000000000000000000000000
 
+# Line feed
+LF='
+'
+
 # Each test should start with something like this, after copyright notices:
 #
 # test_description='Description of this test...
@@ -357,6 +361,24 @@ test_chmod () {
        git update-index --add "--chmod=$@"
 }
 
+# Unset a configuration variable, but don't fail if it doesn't exist.
+test_unconfig () {
+       git config --unset-all "$@"
+       config_status=$?
+       case "$config_status" in
+       5) # ok, nothing to unset
+               config_status=0
+               ;;
+       esac
+       return $config_status
+}
+
+# Set git config, automatically unsetting it after the test is over.
+test_config () {
+       test_when_finished "test_unconfig '$1'" &&
+       git config "$@"
+}
+
 # Use test_set_prereq to tell that a particular prerequisite is available.
 # The prerequisite can later be checked for in two ways:
 #
@@ -444,20 +466,26 @@ test_debug () {
        test "$debug" = "" || eval "$1"
 }
 
+test_eval_ () {
+       # This is a separate function because some tests use
+       # "return" to end a test_expect_success block early.
+       eval >&3 2>&4 "$*"
+}
+
 test_run_ () {
        test_cleanup=:
        expecting_failure=$2
-       eval >&3 2>&4 "$1"
+       test_eval_ "$1"
        eval_ret=$?
 
        if test -z "$immediate" || test $eval_ret = 0 || test -n "$expecting_failure"
        then
-               eval >&3 2>&4 "$test_cleanup"
+               test_eval_ "$test_cleanup"
        fi
        if test "$verbose" = "t" && test -n "$HARNESS_ACTIVE"; then
                echo ""
        fi
-       return 0
+       return "$eval_ret"
 }
 
 test_skip () {
@@ -502,8 +530,7 @@ test_expect_failure () {
        if ! test_skip "$@"
        then
                say >&3 "checking known breakage: $2"
-               test_run_ "$2" expecting_failure
-               if [ "$?" = 0 -a "$eval_ret" = 0 ]
+               if test_run_ "$2" expecting_failure
                then
                        test_known_broken_ok_ "$1"
                else
@@ -521,8 +548,7 @@ test_expect_success () {
        if ! test_skip "$@"
        then
                say >&3 "expecting success: $2"
-               test_run_ "$2"
-               if [ "$?" = 0 -a "$eval_ret" = 0 ]
+               if test_run_ "$2"
                then
                        test_ok_ "$1"
                else
@@ -924,6 +950,8 @@ then
        do
                make_valgrind_symlink $file
        done
+       # special-case the mergetools loadables
+       make_symlink "$GIT_BUILD_DIR"/mergetools "$GIT_VALGRIND/bin/mergetools"
        OLDIFS=$IFS
        IFS=:
        for path in $PATH
diff --git a/templates/hooks--post-commit.sample b/templates/hooks--post-commit.sample
deleted file mode 100755 (executable)
index 2266821..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/sh
-#
-# An example hook script that is called after a successful
-# commit is made.
-#
-# To enable this hook, rename this file to "post-commit".
-
-: Nothing
diff --git a/templates/hooks--post-receive.sample b/templates/hooks--post-receive.sample
deleted file mode 100755 (executable)
index 7a83e17..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/sh
-#
-# An example hook script for the "post-receive" event.
-#
-# The "post-receive" script is run after receive-pack has accepted a pack
-# and the repository has been updated.  It is passed arguments in through
-# stdin in the form
-#  <oldrev> <newrev> <refname>
-# For example:
-#  aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
-#
-# see contrib/hooks/ for a sample, or uncomment the next line and
-# rename the file to "post-receive".
-
-#. /usr/share/doc/git-core/contrib/hooks/post-receive-email
index 98c577804f177b1c6f9df0e34e5fc3a656a81d27..e1940615e28ebc3b16f80b8f647f216031f64ee8 100644 (file)
@@ -10,6 +10,7 @@
 #include "refs.h"
 #include "branch.h"
 #include "url.h"
+#include "submodule.h"
 
 /* rsync support */
 
@@ -431,7 +432,8 @@ static int fetch_refs_from_bundle(struct transport *transport,
                               int nr_heads, struct ref **to_fetch)
 {
        struct bundle_transport_data *data = transport->data;
-       return unbundle(&data->header, data->fd);
+       return unbundle(&data->header, data->fd,
+                       transport->progress ? BUNDLE_VERBOSE : 0);
 }
 
 static int close_bundle(struct transport *transport)
@@ -482,18 +484,14 @@ static int set_git_option(struct git_transport_options *opts,
 static int connect_setup(struct transport *transport, int for_push, int verbose)
 {
        struct git_transport_data *data = transport->data;
-       struct strbuf sb = STRBUF_INIT;
 
        if (data->conn)
                return 0;
 
-       strbuf_addstr(&sb, for_push ? data->options.receivepack :
-                                data->options.uploadpack);
-       if (for_push && transport->verbose < 0)
-               strbuf_addstr(&sb, " --quiet");
-       data->conn = git_connect(data->fd, transport->url, sb.buf,
+       data->conn = git_connect(data->fd, transport->url,
+                                for_push ? data->options.receivepack :
+                                data->options.uploadpack,
                                 verbose ? CONNECT_VERBOSE : 0);
-       strbuf_release(&sb);
 
        return 0;
 }
@@ -1045,6 +1043,14 @@ int transport_push(struct transport *transport,
                        flags & TRANSPORT_PUSH_MIRROR,
                        flags & TRANSPORT_PUSH_FORCE);
 
+               if ((flags & TRANSPORT_RECURSE_SUBMODULES_CHECK) && !is_bare_repository()) {
+                       struct ref *ref = remote_refs;
+                       for (; ref; ref = ref->next)
+                               if (!is_null_sha1(ref->new_sha1) &&
+                                   check_submodule_needs_pushing(ref->new_sha1,transport->remote->name))
+                                       die("There are unpushed submodules, aborting.");
+               }
+
                push_ret = transport->push_refs(transport, remote_refs, flags);
                err = push_had_errors(remote_refs);
                ret = push_ret | err;
index 161d724bba5c132950b45866286c558148560d01..059b3303e20f8335cea388dfccfc2740d3c3d43e 100644 (file)
@@ -101,6 +101,7 @@ struct transport {
 #define TRANSPORT_PUSH_MIRROR 8
 #define TRANSPORT_PUSH_PORCELAIN 16
 #define TRANSPORT_PUSH_SET_UPSTREAM 32
+#define TRANSPORT_RECURSE_SUBMODULES_CHECK 64
 
 #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
 
index 33f749e1e77484694e7b8f40a65755a7818c4abb..808bb55ba3f21d113e6358f6874e2e20cb33bbea 100644 (file)
@@ -309,6 +309,18 @@ static void free_extended_entry(struct tree_desc_x *t)
        }
 }
 
+static inline int prune_traversal(struct name_entry *e,
+                                 struct traverse_info *info,
+                                 struct strbuf *base,
+                                 int still_interesting)
+{
+       if (!info->pathspec || still_interesting == 2)
+               return 2;
+       if (still_interesting < 0)
+               return still_interesting;
+       return tree_entry_interesting(e, base, 0, info->pathspec);
+}
+
 int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
 {
        int ret = 0;
@@ -316,10 +328,18 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
        struct name_entry *entry = xmalloc(n*sizeof(*entry));
        int i;
        struct tree_desc_x *tx = xcalloc(n, sizeof(*tx));
+       struct strbuf base = STRBUF_INIT;
+       int interesting = 1;
 
        for (i = 0; i < n; i++)
                tx[i].d = t[i];
 
+       if (info->prev) {
+               strbuf_grow(&base, info->pathlen);
+               make_traverse_path(base.buf, info->prev, &info->name);
+               base.buf[info->pathlen-1] = '/';
+               strbuf_setlen(&base, info->pathlen);
+       }
        for (;;) {
                unsigned long mask, dirmask;
                const char *first = NULL;
@@ -376,16 +396,22 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
                        mask |= 1ul << i;
                        if (S_ISDIR(entry[i].mode))
                                dirmask |= 1ul << i;
+                       e = &entry[i];
                }
                if (!mask)
                        break;
-               ret = info->fn(n, mask, dirmask, entry, info);
-               if (ret < 0) {
-                       error = ret;
-                       if (!info->show_all_errors)
-                               break;
+               interesting = prune_traversal(e, info, &base, interesting);
+               if (interesting < 0)
+                       break;
+               if (interesting) {
+                       ret = info->fn(n, mask, dirmask, entry, info);
+                       if (ret < 0) {
+                               error = ret;
+                               if (!info->show_all_errors)
+                                       break;
+                       }
+                       mask &= ret;
                }
-               mask &= ret;
                ret = 0;
                for (i = 0; i < n; i++)
                        if (mask & (1ul << i))
@@ -395,6 +421,7 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
        for (i = 0; i < n; i++)
                free_extended_entry(tx + i);
        free(tx);
+       strbuf_release(&base);
        return error;
 }
 
index 39524b7dba6a1d0b63c4cd2b42db59a27a030b21..0089581e1dd55800302799a7381d4a7ad01bd79d 100644 (file)
@@ -44,6 +44,7 @@ struct traverse_info {
        struct traverse_info *prev;
        struct name_entry name;
        int pathlen;
+       struct pathspec *pathspec;
 
        unsigned long conflicts;
        traverse_callback_t fn;
index cc616c3f991a655d10fcac15055fcdfafa8620fd..670b46473883505c39c5353c65e0eeb93aec21c3 100644 (file)
@@ -444,6 +444,7 @@ static int traverse_trees_recursive(int n, unsigned long dirmask,
 
        newinfo = *info;
        newinfo.prev = info;
+       newinfo.pathspec = info->pathspec;
        newinfo.name = *p;
        newinfo.pathlen += tree_entry_len(p->path, p->sha1) + 1;
        newinfo.conflicts |= df_conflicts;
@@ -1040,6 +1041,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
                info.fn = unpack_callback;
                info.data = o;
                info.show_all_errors = o->show_all_errors;
+               info.pathspec = o->pathspec;
 
                if (o->prefix) {
                        /*
index 79989483079970e9dad42512e522eef8ea2a75a4..5e432f576eb2304a63510a61a71182e11f777092 100644 (file)
@@ -52,6 +52,7 @@ struct unpack_trees_options {
        const char *prefix;
        int cache_bottom;
        struct dir_struct *dir;
+       struct pathspec *pathspec;
        merge_fn_t fn;
        const char *msgs[NB_UNPACK_TREES_ERROR_TYPES];
        /*
index 6420918abea3a9a33ad967d263153e4548daab29..470cffd7c14a9f28010423a44327084219598a35 100644 (file)
@@ -84,22 +84,11 @@ static void show_commit(struct commit *commit, void *data)
        commit->buffer = NULL;
 }
 
-static void show_object(struct object *obj, const struct name_path *path, const char *component)
+static void show_object(struct object *obj,
+                       const struct name_path *path, const char *component,
+                       void *cb_data)
 {
-       /* An object with name "foo\n0000000..." can be used to
-        * confuse downstream git-pack-objects very badly.
-        */
-       const char *name = path_name(path, component);
-       const char *ep = strchr(name, '\n');
-       if (ep) {
-               fprintf(pack_pipe, "%s %.*s\n", sha1_to_hex(obj->sha1),
-                      (int) (ep - name),
-                      name);
-       }
-       else
-               fprintf(pack_pipe, "%s %s\n",
-                               sha1_to_hex(obj->sha1), name);
-       free((char *)name);
+       show_object_with_name(pack_pipe, obj, path, component);
 }
 
 static void show_edge(struct commit *commit)
@@ -533,6 +522,8 @@ static void check_non_tip(void)
        namebuf[41] = '\n';
        for (i = get_max_object_index(); 0 < i; ) {
                o = get_indexed_object(--i);
+               if (!o)
+                       continue;
                if (!(o->flags & OUR_REF))
                        continue;
                memcpy(namebuf + 1, sha1_to_hex(o->sha1), 40);
index 02377729c401fa6e041a713a89fe986fa0f562a0..8836a527d0b1980bd4ebdd50b3225f7ce37ccf79 100644 (file)
@@ -26,7 +26,9 @@ static char default_wt_status_colors[][COLOR_MAXLEN] = {
 
 static const char *color(int slot, struct wt_status *s)
 {
-       const char *c = s->use_color > 0 ? s->color_palette[slot] : "";
+       const char *c = "";
+       if (want_color(s->use_color))
+               c = s->color_palette[slot];
        if (slot == WT_STATUS_ONBRANCH && color_is_nil(c))
                c = s->color_palette[WT_STATUS_HEADER];
        return c;
@@ -681,7 +683,7 @@ static void wt_status_print_verbose(struct wt_status *s)
         * will have checked isatty on stdout).
         */
        if (s->fp != stdout)
-               DIFF_OPT_CLR(&rev.diffopt, COLOR_DIFF);
+               rev.diffopt.use_color = 0;
        run_diff_index(&rev, 1);
 }
 
index 620fc9a657e2246d3a382c916c2cdd4f820c0c44..5a33d1a86964472a82a63f18ab3c9b4da9b23165 100644 (file)
@@ -36,6 +36,7 @@ typedef struct s_xdlclass {
        char const *line;
        long size;
        long idx;
+       long len1, len2;
 } xdlclass_t;
 
 typedef struct s_xdlclassifier {
@@ -43,6 +44,8 @@ typedef struct s_xdlclassifier {
        long hsize;
        xdlclass_t **rchash;
        chastore_t ncha;
+       xdlclass_t **rcrecs;
+       long alloc;
        long count;
        long flags;
 } xdlclassifier_t;
@@ -52,15 +55,15 @@ typedef struct s_xdlclassifier {
 
 static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags);
 static void xdl_free_classifier(xdlclassifier_t *cf);
-static int xdl_classify_record(xdlclassifier_t *cf, xrecord_t **rhash, unsigned int hbits,
-                              xrecord_t *rec);
-static int xdl_prepare_ctx(mmfile_t *mf, long narec, xpparam_t const *xpp,
+static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,
+                              unsigned int hbits, xrecord_t *rec);
+static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
                           xdlclassifier_t *cf, xdfile_t *xdf);
 static void xdl_free_ctx(xdfile_t *xdf);
 static int xdl_clean_mmatch(char const *dis, long i, long s, long e);
-static int xdl_cleanup_records(xdfile_t *xdf1, xdfile_t *xdf2);
+static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);
 static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2);
-static int xdl_optimize_ctxs(xdfile_t *xdf1, xdfile_t *xdf2);
+static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);
 
 
 
@@ -82,6 +85,14 @@ static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) {
        }
        memset(cf->rchash, 0, cf->hsize * sizeof(xdlclass_t *));
 
+       cf->alloc = size;
+       if (!(cf->rcrecs = (xdlclass_t **) xdl_malloc(cf->alloc * sizeof(xdlclass_t *)))) {
+
+               xdl_free(cf->rchash);
+               xdl_cha_free(&cf->ncha);
+               return -1;
+       }
+
        cf->count = 0;
 
        return 0;
@@ -90,16 +101,18 @@ static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) {
 
 static void xdl_free_classifier(xdlclassifier_t *cf) {
 
+       xdl_free(cf->rcrecs);
        xdl_free(cf->rchash);
        xdl_cha_free(&cf->ncha);
 }
 
 
-static int xdl_classify_record(xdlclassifier_t *cf, xrecord_t **rhash, unsigned int hbits,
-                              xrecord_t *rec) {
+static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,
+                              unsigned int hbits, xrecord_t *rec) {
        long hi;
        char const *line;
        xdlclass_t *rcrec;
+       xdlclass_t **rcrecs;
 
        line = rec->ptr;
        hi = (long) XDL_HASHLONG(rec->ha, cf->hbits);
@@ -115,13 +128,25 @@ static int xdl_classify_record(xdlclassifier_t *cf, xrecord_t **rhash, unsigned
                        return -1;
                }
                rcrec->idx = cf->count++;
+               if (cf->count > cf->alloc) {
+                       cf->alloc *= 2;
+                       if (!(rcrecs = (xdlclass_t **) xdl_realloc(cf->rcrecs, cf->alloc * sizeof(xdlclass_t *)))) {
+
+                               return -1;
+                       }
+                       cf->rcrecs = rcrecs;
+               }
+               cf->rcrecs[rcrec->idx] = rcrec;
                rcrec->line = line;
                rcrec->size = rec->size;
                rcrec->ha = rec->ha;
+               rcrec->len1 = rcrec->len2 = 0;
                rcrec->next = cf->rchash[hi];
                cf->rchash[hi] = rcrec;
        }
 
+       (pass == 1) ? rcrec->len1++ : rcrec->len2++;
+
        rec->ha = (unsigned long) rcrec->idx;
 
        hi = (long) XDL_HASHLONG(rec->ha, hbits);
@@ -132,7 +157,7 @@ static int xdl_classify_record(xdlclassifier_t *cf, xrecord_t **rhash, unsigned
 }
 
 
-static int xdl_prepare_ctx(mmfile_t *mf, long narec, xpparam_t const *xpp,
+static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
                           xdlclassifier_t *cf, xdfile_t *xdf) {
        unsigned int hbits;
        long nrec, hsize, bsize;
@@ -185,7 +210,7 @@ static int xdl_prepare_ctx(mmfile_t *mf, long narec, xpparam_t const *xpp,
                        recs[nrec++] = crec;
 
                        if (!(xpp->flags & XDF_HISTOGRAM_DIFF) &&
-                               xdl_classify_record(cf, rhash, hbits, crec) < 0)
+                               xdl_classify_record(pass, cf, rhash, hbits, crec) < 0)
                                goto abort;
                }
        }
@@ -239,6 +264,8 @@ int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
        long enl1, enl2, sample;
        xdlclassifier_t cf;
 
+       memset(&cf, 0, sizeof(cf));
+
        /*
         * For histogram diff, we can afford a smaller sample size and
         * thus a poorer estimate of the number of lines, as the hash
@@ -257,30 +284,30 @@ int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
                return -1;
        }
 
-       if (xdl_prepare_ctx(mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {
+       if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {
 
                xdl_free_classifier(&cf);
                return -1;
        }
-       if (xdl_prepare_ctx(mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {
+       if (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {
 
                xdl_free_ctx(&xe->xdf1);
                xdl_free_classifier(&cf);
                return -1;
        }
 
-       if (!(xpp->flags & XDF_HISTOGRAM_DIFF))
-               xdl_free_classifier(&cf);
-
        if (!(xpp->flags & XDF_PATIENCE_DIFF) &&
                        !(xpp->flags & XDF_HISTOGRAM_DIFF) &&
-                       xdl_optimize_ctxs(&xe->xdf1, &xe->xdf2) < 0) {
+                       xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) {
 
                xdl_free_ctx(&xe->xdf2);
                xdl_free_ctx(&xe->xdf1);
                return -1;
        }
 
+       if (!(xpp->flags & XDF_HISTOGRAM_DIFF))
+               xdl_free_classifier(&cf);
+
        return 0;
 }
 
@@ -355,11 +382,10 @@ static int xdl_clean_mmatch(char const *dis, long i, long s, long e) {
  * matches on the other file. Also, lines that have multiple matches
  * might be potentially discarded if they happear in a run of discardable.
  */
-static int xdl_cleanup_records(xdfile_t *xdf1, xdfile_t *xdf2) {
-       long i, nm, rhi, nreff, mlim;
-       unsigned long hav;
+static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) {
+       long i, nm, nreff;
        xrecord_t **recs;
-       xrecord_t *rec;
+       xdlclass_t *rcrec;
        char *dis, *dis1, *dis2;
 
        if (!(dis = (char *) xdl_malloc(xdf1->nrec + xdf2->nrec + 2))) {
@@ -370,26 +396,16 @@ static int xdl_cleanup_records(xdfile_t *xdf1, xdfile_t *xdf2) {
        dis1 = dis;
        dis2 = dis1 + xdf1->nrec + 1;
 
-       if ((mlim = xdl_bogosqrt(xdf1->nrec)) > XDL_MAX_EQLIMIT)
-               mlim = XDL_MAX_EQLIMIT;
        for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) {
-               hav = (*recs)->ha;
-               rhi = (long) XDL_HASHLONG(hav, xdf2->hbits);
-               for (nm = 0, rec = xdf2->rhash[rhi]; rec; rec = rec->next)
-                       if (rec->ha == hav && ++nm == mlim)
-                               break;
-               dis1[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1;
+               rcrec = cf->rcrecs[(*recs)->ha];
+               nm = rcrec ? rcrec->len2 : 0;
+               dis1[i] = (nm == 0) ? 0: 1;
        }
 
-       if ((mlim = xdl_bogosqrt(xdf2->nrec)) > XDL_MAX_EQLIMIT)
-               mlim = XDL_MAX_EQLIMIT;
        for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) {
-               hav = (*recs)->ha;
-               rhi = (long) XDL_HASHLONG(hav, xdf1->hbits);
-               for (nm = 0, rec = xdf1->rhash[rhi]; rec; rec = rec->next)
-                       if (rec->ha == hav && ++nm == mlim)
-                               break;
-               dis2[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1;
+               rcrec = cf->rcrecs[(*recs)->ha];
+               nm = rcrec ? rcrec->len1 : 0;
+               dis2[i] = (nm == 0) ? 0: 1;
        }
 
        for (nreff = 0, i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart];
@@ -451,10 +467,10 @@ static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) {
 }
 
 
-static int xdl_optimize_ctxs(xdfile_t *xdf1, xdfile_t *xdf2) {
+static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) {
 
        if (xdl_trim_ends(xdf1, xdf2) < 0 ||
-           xdl_cleanup_records(xdf1, xdf2) < 0) {
+           xdl_cleanup_records(cf, xdf1, xdf2) < 0) {
 
                return -1;
        }