]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'mz/pull-rebase-rebased'
authorJunio C Hamano <gitster@pobox.com>
Mon, 13 Dec 2010 05:49:51 +0000 (21:49 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 13 Dec 2010 05:49:51 +0000 (21:49 -0800)
* mz/pull-rebase-rebased:
  Use reflog in 'pull --rebase . foo'

150 files changed:
.gitignore
Documentation/CodingGuidelines
Documentation/RelNotes/1.7.0.8.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.1.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.2.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.3.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.3.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.4.txt
Documentation/config.txt
Documentation/git-blame.txt
Documentation/git-cherry-pick.txt
Documentation/git-clone.txt
Documentation/git-commit.txt
Documentation/git-diff.txt
Documentation/git-difftool.txt
Documentation/git-merge.txt
Documentation/git-notes.txt
Documentation/git-pull.txt
Documentation/git-remote-ext.txt [new file with mode: 0644]
Documentation/git-remote-fd.txt [new file with mode: 0644]
Documentation/git-revert.txt
Documentation/git-rm.txt
Documentation/git-send-email.txt
Documentation/git.txt
Documentation/gitignore.txt
Makefile
builtin.h
builtin/add.c
builtin/apply.c
builtin/blame.c
builtin/branch.c
builtin/checkout-index.c
builtin/clone.c
builtin/commit.c
builtin/diff.c
builtin/gc.c
builtin/grep.c
builtin/log.c
builtin/ls-files.c
builtin/mailinfo.c
builtin/merge.c
builtin/notes.c
builtin/pack-objects.c
builtin/remote-ext.c [new file with mode: 0644]
builtin/remote-fd.c [new file with mode: 0644]
builtin/revert.c
builtin/tag.c
builtin/update-index.c
builtin/verify-tag.c
cache.h
commit.c
commit.h
compat/mingw.h
config.c
config.mak.in
configure.ac
contrib/completion/git-completion.bash
contrib/hooks/post-receive-email
daemon.c
diff.c
dir.c
dir.h
environment.c
fast-import.c
git-add--interactive.perl
git-compat-util.h
git-instaweb.sh
git-parse-remote.sh
git-pull.sh
git-rebase--interactive.sh
git-rebase.sh
git-sh-setup.sh
git-svn.perl
git.c
gitweb/gitweb.perl
gitweb/static/gitweb.css
http.c
merge-recursive.c
name-hash.c
notes-cache.c
notes-merge.c [new file with mode: 0644]
notes-merge.h [new file with mode: 0644]
notes.c
notes.h
path.c
pretty.c
read-cache.c
sha1_file.c
sha1_name.c
strbuf.c
symlinks.c
t/annotate-tests.sh
t/lib-httpd.sh
t/lib-httpd/apache.conf
t/lib-httpd/passwd [new file with mode: 0644]
t/t2006-checkout-index-basic.sh [new file with mode: 0755]
t/t2107-update-index-basic.sh [new file with mode: 0755]
t/t3004-ls-files-basic.sh [new file with mode: 0755]
t/t3030-merge-recursive.sh
t/t3200-branch.sh
t/t3301-notes.sh
t/t3303-notes-subtrees.sh
t/t3308-notes-merge.sh [new file with mode: 0755]
t/t3309-notes-merge-auto-resolve.sh [new file with mode: 0755]
t/t3310-notes-merge-manual-resolve.sh [new file with mode: 0755]
t/t3311-notes-merge-fanout.sh [new file with mode: 0755]
t/t3404-rebase-interactive.sh
t/t3415-rebase-autosquash.sh
t/t3501-revert-cherry-pick.sh
t/t3509-cherry-pick-merge-df.sh
t/t3900-i18n-commit.sh
t/t4019-diff-wserror.sh
t/t4120-apply-popt.sh
t/t4132-apply-removal.sh
t/t4135-apply-weird-filenames.sh
t/t4135/damaged-tz.diff [new file with mode: 0644]
t/t4135/funny-tz.diff [new file with mode: 0644]
t/t4202-log.sh
t/t5550-http-fetch.sh
t/t5551-http-fetch.sh
t/t6020-merge-df.sh
t/t6022-merge-rename.sh
t/t6032-merge-large-rename.sh
t/t6036-recursive-corner-cases.sh
t/t6500-gc.sh [new file with mode: 0755]
t/t7004-tag.sh
t/t7006-pager.sh
t/t7300-clean.sh
t/t7500-commit.sh
t/t7500/edit-content [new file with mode: 0755]
t/t7508-status.sh
t/t7600-merge.sh
t/t7607-merge-overwrite.sh
t/t7609-merge-abort.sh [new file with mode: 0755]
t/t7609-merge-co-error-msgs.sh
t/t8002-blame.sh
t/t9143-git-svn-gc.sh
t/t9158-git-svn-mergeinfo.sh [new file with mode: 0644]
t/t9300-fast-import.sh
t/t9301-fast-import-notes.sh
tag.c
tag.h
thread-utils.h
transport-helper.c
transport.h
unpack-trees.c
unpack-trees.h
wrapper.c
wt-status.c
zlib.c [new file with mode: 0644]

index 20560b810b0471c3b7d012bf0f14d7885e2b83ec..87b833c9d8940a61ff77ec6bd35241b3f85decb5 100644 (file)
 /git-remote-https
 /git-remote-ftp
 /git-remote-ftps
+/git-remote-fd
+/git-remote-ext
 /git-remote-testgit
 /git-repack
 /git-replace
index 09ffc46563cba1057b37ba4a5701858fb95c5dda..1b1c45df5ce3ef30e588555bbd5ab404c2b7397c 100644 (file)
@@ -31,6 +31,10 @@ But if you must have a list of rules, here they are.
 
 For shell scripts specifically (not exhaustive):
 
+ - We use tabs for indentation.
+
+ - Case arms are indented at the same depth as case and esac lines.
+
  - We prefer $( ... ) for command substitution; unlike ``, it
    properly nests.  It should have been the way Bourne spelled
    it from day one, but unfortunately isn't.
@@ -139,3 +143,55 @@ For C programs:
 
  - When we pass <string, length> pair to functions, we should try to
    pass them in that order.
+
+Writing Documentation:
+
+ Every user-visible change should be reflected in the documentation.
+ The same general rule as for code applies -- imitate the existing
+ conventions.  A few commented examples follow to provide reference
+ when writing or modifying command usage strings and synopsis sections
+ in the manual pages:
+
+ Placeholders are enclosed in angle brackets:
+   <file>
+   --sort=<key>
+   --abbrev[=<n>]
+
+ Possibility of multiple occurences is indicated by three dots:
+   <file>...
+   (One or more of <file>.)
+
+ Optional parts are enclosed in square brackets:
+   [<extra>]
+   (Zero or one <extra>.)
+
+   --exec-path[=<path>]
+   (Option with an optional argument.  Note that the "=" is inside the
+   brackets.)
+
+   [<patch>...]
+   (Zero or more of <patch>.  Note that the dots are inside, not
+   outside the brackets.)
+
+ Multiple alternatives are indicated with vertical bar:
+   [-q | --quiet]
+   [--utf8 | --no-utf8]
+
+ Parentheses are used for grouping:
+   [(<rev>|<range>)...]
+   (Any number of either <rev> or <range>.  Parens are needed to make
+   it clear that "..." pertains to both <rev> and <range>.)
+
+   [(-p <parent>)...]
+   (Any number of option -p, each with one <parent> argument.)
+
+   git remote set-head <name> (-a | -d | <branch>)
+   (One and only one of "-a", "-d" or "<branch>" _must_ (no square
+   brackets) be provided.)
+
+ And a somewhat more contrived example:
+   --diff-filter=[(A|C|D|M|R|T|U|X|B)...[*]]
+   Here "=" is outside the brackets, because "--diff-filter=" is a
+   valid usage.  "*" has its own pair of brackets, because it can
+   (optionally) be specified only when one or more of the letters is
+   also provided.
diff --git a/Documentation/RelNotes/1.7.0.8.txt b/Documentation/RelNotes/1.7.0.8.txt
new file mode 100644 (file)
index 0000000..7f05b48
--- /dev/null
@@ -0,0 +1,10 @@
+Git v1.7.0.8 Release Notes
+==========================
+
+This is primarily to backport support for the new "add.ignoreErrors"
+name given to the existing "add.ignore-errors" configuration variable.
+
+The next version, Git 1.7.4, and future versions, will support both
+old and incorrect name and the new corrected name, but without this
+backport, users who want to use the new name "add.ignoreErrors" in
+their repositories cannot use older versions of Git.
diff --git a/Documentation/RelNotes/1.7.1.3.txt b/Documentation/RelNotes/1.7.1.3.txt
new file mode 100644 (file)
index 0000000..5b18518
--- /dev/null
@@ -0,0 +1,10 @@
+Git v1.7.1.3 Release Notes
+==========================
+
+This is primarily to backport support for the new "add.ignoreErrors"
+name given to the existing "add.ignore-errors" configuration variable.
+
+The next version, Git 1.7.4, and future versions, will support both
+old and incorrect name and the new corrected name, but without this
+backport, users who want to use the new name "add.ignoreErrors" in
+their repositories cannot use older versions of Git.
diff --git a/Documentation/RelNotes/1.7.2.4.txt b/Documentation/RelNotes/1.7.2.4.txt
new file mode 100644 (file)
index 0000000..f7950a4
--- /dev/null
@@ -0,0 +1,10 @@
+Git v1.7.2.4 Release Notes
+==========================
+
+This is primarily to backport support for the new "add.ignoreErrors"
+name given to the existing "add.ignore-errors" configuration variable.
+
+The next version, Git 1.7.4, and future versions, will support both
+old and incorrect name and the new corrected name, but without this
+backport, users who want to use the new name "add.ignoreErrors" in
+their repositories cannot use older versions of Git.
diff --git a/Documentation/RelNotes/1.7.3.3.txt b/Documentation/RelNotes/1.7.3.3.txt
new file mode 100644 (file)
index 0000000..9b2b244
--- /dev/null
@@ -0,0 +1,54 @@
+Git v1.7.3.3 Release Notes
+==========================
+
+In addition to the usual fixes, this release also includes support for
+the new "add.ignoreErrors" name given to the existing "add.ignore-errors"
+configuration variable.
+
+The next version, Git 1.7.4, and future versions, will support both
+old and incorrect name and the new corrected name, but without this
+backport, users who want to use the new name "add.ignoreErrors" in
+their repositories cannot use older versions of Git.
+
+Fixes since v1.7.3.2
+--------------------
+
+ * "git apply" segfaulted when a bogus input is fed to it.
+
+ * Running "git cherry-pick --ff" on a root commit segfaulted.
+
+ * "diff", "blame" and friends incorrectly applied textconv filters to
+   symlinks.
+
+ * Highlighting of whitespace breakage in "diff" output was showing
+   incorrect amount of whitespaces when blank-at-eol is set and the line
+   consisted only of whitespaces and a TAB.
+
+ * "diff" was overly inefficient when trying to find the line to use for
+   the function header (i.e. equivalent to --show-c-function of GNU diff).
+
+ * "git imap-send" depends on libcrypto but our build rule relied on the
+   linker to implicitly link it via libssl, which was wrong.
+
+ * "git merge-file" can be called from within a subdirectory now.
+
+ * "git repack -f" expanded and recompressed non-delta objects in the
+   existing pack, which was wasteful.  Use new "-F" option if you really
+   want to (e.g. when changing the pack.compression level).
+
+ * "git rev-list --format="...%x00..." incorrectly chopped its output
+   at NUL.
+
+ * "git send-email" did not correctly remove duplicate mail addresses from
+   the Cc: header that appear on the To: header.
+
+ * The completion script (in contrib/completion) ignored lightweight tags
+   in __git_ps1().
+
+ * "git-blame" mode (in contrib/emacs) didn't say (require 'format-spec)
+   even though it depends on it; it didn't work with Emacs 22 or older
+   unless Gnus is used.
+
+ * "git-p4" (in contrib/) did not correctly handle deleted files.
+
+Other minor fixes and documentation updates are also included.
diff --git a/Documentation/RelNotes/1.7.3.4.txt b/Documentation/RelNotes/1.7.3.4.txt
new file mode 100644 (file)
index 0000000..925178f
--- /dev/null
@@ -0,0 +1,32 @@
+Git v1.7.3.4 Release Notes
+==========================
+
+Fixes since v1.7.3.3
+--------------------
+
+ * Smart HTTP transport used to incorrectly retry redirected POST
+   request with GET request.
+
+ * "git apply" did not correctly handle patches that only change modes
+   if told to apply while stripping leading paths with -p option.
+
+ * "git apply" can deal with patches with timezone formatted with a
+   colon between the hours and minutes part (e.g. "-08:00" instead of
+   "-0800").
+
+ * "git cherry-pick" or "git revert" refused to work when a path that
+   would be modified by the operation was stat-dirty without a real
+   difference in the contents of the file.
+
+ * "git diff --check" reported an incorrect line number for added
+   blank lines at the end of file.
+
+ * Setting log.decorate configuration variable to "0" or "1" to mean
+   "false" or "true" did not work.
+
+ * "git tag -v" did not work with GPG signatures in rfc1991 mode.
+
+ * The post-receive-email sample hook was accidentally broken in 1.7.3.3
+   update.
+
+Other minor fixes and documentation updates are also included.
index 9f946e218dd67e4943bd8a7164467cad94bab40a..c1d06694e129d492ab21a540bb37bdf4044fb107 100644 (file)
@@ -15,9 +15,21 @@ Updates since v1.7.3
    /etc/gitattributes; core.attributesfile configuration variable can
    be used to customize the path to this file.
 
+ * The thread structure generated by "git send-email" has changed
+   slightly.  Setting the cover letter of the latest series as a reply
+   to the cover letter of the previous series with --in-reply-to used
+   to make the new cover letter and all the patches replies to the
+   cover letter of the previous series; this has been changed to make
+   the patches in the new series replies to the new cover letter.
+
  * Bash completion script in contrib/ has been adjusted to be also
    usable by zsh.
 
+ * "git blame" learned --show-email option to display the e-mail
+   addresses instead of the names of authors.
+
+ * "git daemon" can be built in MinGW environment.
+
  * "git daemon" can take more than one --listen option to listen to
    multiple addresses.
 
@@ -41,6 +53,13 @@ Updates since v1.7.3
  * "git merge --log" used to limit the resulting merge log to 20
    entries; this is now customizable by giving e.g. "--log=47".
 
+ * "git merge" may work better when all files were moved out of a
+   directory in one branch while a new file is created in place of that
+   directory in the other branch.
+
+ * "git rebase --autosquash" can use SHA-1 object names to name which
+   commit to fix up (e.g. "fixup! e83c5163").
+
  * The default "recursive" merge strategy learned --rename-threshold
    option to influence the rename detection, similar to the -M option
    of "git diff".  E.g. "git merge -Xrename-threshold=50% ..." to use
@@ -73,33 +92,28 @@ Fixes since v1.7.3
 All of the fixes in v1.7.3.X maintenance series are included in this
 release, unless otherwise noted.
 
- * "diff" and friends incorrectly applied textconv filters to symlinks
-   (d391c0ff).
-
- * "git apply" segfaulted when a bogus input is fed to it (24305cd70).
-
- * Running "git cherry-pick --ff" on a root commit segfaulted (6355e50).
+ * "git checkout" removed an untracked file "foo" from the working
+    tree when switching to a branch that contains a tracked path
+    "foo/bar".  Prevent this, just like the case where the conflicting
+    path were "foo" (c752e7f..7980872d).
 
  * "git log --author=me --author=her" did not find commits written by
    me or by her; instead it looked for commits written by me and by
    her, which is impossible.
 
- * "git merge-file" can be called from within a subdirectory now
-   (55846b9a).
+ * "git merge" into an unborn branch removed an untracked file "foo"
+   from the working tree when merged branch had "foo" (2caf20c..172b642).
 
  * "git push --progress" shows progress indicators now.
 
  * "git repack" places its temporary packs under $GIT_OBJECT_DIRECTORY/pack
    instead of $GIT_OBJECT_DIRECTORY/ to avoid cross directory renames.
 
- * "git rev-list --format="...%x00..." incorrectly chopped its output
-   at NUL (9130ac9fe).
-
  * "git submodule update --recursive --other-flags" passes flags down
    to its subinvocations.
 
 ---
 exec >/var/tmp/1
-O=v1.7.3.2-245-g03276d9
+O=v1.7.3.2-450-g5b9c331
 echo O=$(git describe master)
 git shortlog --no-merges ^maint ^$O master
index 6a6c0b5bd83774c9c92f5530f327019c322d3c2a..0f8579331ed6b9324823f01c3a3cdf32c1e9e674 100644 (file)
@@ -374,6 +374,15 @@ core.warnAmbiguousRefs::
        If true, git will warn you if the ref name you passed it is ambiguous
        and might match multiple refs in the .git/refs/ tree. True by default.
 
+core.abbrevguard::
+       Even though git makes sure that it uses enough hexdigits to show
+       an abbreviated object name unambiguously, as more objects are
+       added to the repository over time, a short name that used to be
+       unique will stop being unique.  Git uses this many extra hexdigits
+       that are more than necessary to make the object name currently
+       unique, in the hope that its output will stay unique a bit longer.
+       Defaults to 0.
+
 core.compression::
        An integer -1..9, indicating a default compression level.
        -1 is the zlib default. 0 means no compression,
@@ -554,9 +563,13 @@ core.sparseCheckout::
        linkgit:git-read-tree[1] for more information.
 
 add.ignore-errors::
+add.ignoreErrors::
        Tells 'git add' to continue adding files when some files cannot be
        added due to indexing errors. Equivalent to the '--ignore-errors'
-       option of linkgit:git-add[1].
+       option of linkgit:git-add[1].  Older versions of git accept only
+       `add.ignore-errors`, which does not follow the usual naming
+       convention for configuration variables.  Newer versions of git
+       honor `add.ignoreErrors` as well.
 
 alias.*::
        Command aliases for the linkgit:git[1] command wrapper - e.g.
@@ -1532,11 +1545,13 @@ pack.packSizeLimit::
        supported.
 
 pager.<cmd>::
-       Allows turning on or off pagination of the output of a
-       particular git subcommand when writing to a tty.  If
-       `\--paginate` or `\--no-pager` is specified on the command line,
-       it takes precedence over this option.  To disable pagination for
-       all commands, set `core.pager` or `GIT_PAGER` to `cat`.
+       If the value is boolean, turns on or off pagination of the
+       output of a particular git subcommand when writing to a tty.
+       Otherwise, turns on pagination for the subcommand using the
+       pager specified by the value of `pager.<cmd>`.  If `\--paginate`
+       or `\--no-pager` is specified on the command line, it takes
+       precedence over this option.  To disable pagination for all
+       commands, set `core.pager` or `GIT_PAGER` to `cat`.
 
 pretty.<name>::
        Alias for a --pretty= format string, as specified in
index a27f43950fdfe8f6a0e8860d79ee31f0bc60750d..c71671b4f99e0be3670fc6d7e4872927a86ed02e 100644 (file)
@@ -8,7 +8,7 @@ git-blame - Show what revision and author last modified each line of a file
 SYNOPSIS
 --------
 [verse]
-'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-p] [-w] [--incremental] [-L n,m]
+'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental] [-L n,m]
            [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
            [<rev> | --contents <file> | --reverse <rev>] [--] <file>
 
@@ -65,6 +65,10 @@ include::blame-options.txt[]
 -s::
        Suppress the author name and timestamp from the output.
 
+-e::
+--show-email::
+       Show the author email instead of author name (Default: off).
+
 -w::
        Ignore whitespace when comparing the parent's version and
        the child's to find where the lines came from.
index 3c96fa8c86c34df2dced6cd9ec8a391ee3b7208e..73008705ebe304c8d2a2d089e9328be369c50fc6 100644 (file)
@@ -92,7 +92,7 @@ git cherry-pick ^HEAD master::
        Apply the changes introduced by all commits that are ancestors
        of master but not of HEAD to produce new commits.
 
-git cherry-pick master\~4 master~2::
+git cherry-pick master{tilde}4 master{tilde}2::
 
        Apply the changes introduced by the fifth and third last
        commits pointed to by master and create 2 new commits with
index 23203829cf35e9b65ef6b4a44786bce7300e3e5a..42e7021215563f146a66d14e1b642d4dbe55bfaf 100644 (file)
@@ -12,7 +12,8 @@ SYNOPSIS
 'git clone' [--template=<template_directory>]
          [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
          [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
-         [--depth <depth>] [--recursive] [--] <repository> [<directory>]
+         [--depth <depth>] [--recursive|--recurse-submodules] [--] <repository>
+         [<directory>]
 
 DESCRIPTION
 -----------
@@ -167,6 +168,7 @@ objects from the source repository into a pack in the cloned repository.
        as patches.
 
 --recursive::
+--recurse-submodules::
        After the clone is created, initialize all submodules within,
        using their default settings. This is equivalent to running
        `git submodule update --init --recursive` immediately after
index 42fb1f57b21d7c355d3df6be07329c478786a9a7..b586c0f442afd33c2875af26b83bb261de7361b0 100644 (file)
@@ -9,10 +9,10 @@ SYNOPSIS
 --------
 [verse]
 'git commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend] [--dry-run]
-          [(-c | -C) <commit>] [-F <file> | -m <msg>] [--reset-author]
-          [--allow-empty] [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
-          [--date=<date>] [--cleanup=<mode>] [--status | --no-status] [--]
-          [[-i | -o ]<file>...]
+          [(-c | -C | --fixup | --squash) <commit>] [-F <file> | -m <msg>]
+          [--reset-author] [--allow-empty] [--allow-empty-message] [--no-verify]
+          [-e] [--author=<author>] [--date=<date>] [--cleanup=<mode>]
+          [--status | --no-status] [-i | -o] [--] [<file>...]
 
 DESCRIPTION
 -----------
@@ -70,6 +70,19 @@ OPTIONS
        Like '-C', but with '-c' the editor is invoked, so that
        the user can further edit the commit message.
 
+--fixup=<commit>::
+       Construct a commit message for use with `rebase --autosquash`.
+       The commit message will be the subject line from the specified
+       commit with a prefix of "fixup! ".  See linkgit:git-rebase[1]
+       for details.
+
+--squash=<commit>::
+       Construct a commit message for use with `rebase --autosquash`.
+       The commit message subject line is taken from the specified
+       commit with a prefix of "squash! ".  Can be used with additional
+       commit message options (`-m`/`-c`/`-C`/`-F`). See
+       linkgit:git-rebase[1] for details.
+
 --reset-author::
        When used with -C/-c/--amend options, declare that the
        authorship of the resulting commit now belongs of the committer.
index dd1fb32786f6f467a1c46f2643fbc54d0390f2ec..f6ac847507a635759323565be44b088b479d8bf0 100644 (file)
@@ -8,12 +8,17 @@ git-diff - Show changes between commits, commit and working tree, etc
 
 SYNOPSIS
 --------
-'git diff' [<common diff options>] <commit>{0,2} [--] [<path>...]
+[verse]
+'git diff' [options] [<commit>] [--] [<path>...]
+'git diff' [options] --cached [<commit>] [--] [<path>...]
+'git diff' [options] <commit> <commit> [--] [<path>...]
+'git diff' [options] [--no-index] [--] <path> <path>
 
 DESCRIPTION
 -----------
-Show changes between two trees, a tree and the working tree, a
-tree and the index file, or the index file and the working tree.
+Show changes between the working tree and the index or a tree, changes
+between the index and a tree, changes between two trees, or changes
+between two files on disk.
 
 'git diff' [--options] [--] [<path>...]::
 
index 8250bad2ce95245ca8e2a41c0740d0029b92ae4b..6fffbc7bf89d01b14505c03993de9d97d37e9404 100644 (file)
@@ -7,13 +7,14 @@ git-difftool - Show changes using common diff tools
 
 SYNOPSIS
 --------
-'git difftool' [<options>] <commit>{0,2} [--] [<path>...]
+'git difftool' [<options>] [<commit> [<commit>]] [--] [<path>...]
 
 DESCRIPTION
 -----------
 'git difftool' is a git command that allows you to compare and edit files
 between revisions using common diff tools.  'git difftool' is a frontend
-to 'git diff' and accepts the same options and arguments.
+to 'git diff' and accepts the same options and arguments. See
+linkgit:git-diff[1].
 
 OPTIONS
 -------
index d43416d299a7c028a88e8ddce89536c2c64e65c9..c1efaaa5c52cd93e90a1a7427125e470f771146a 100644 (file)
@@ -13,6 +13,7 @@ SYNOPSIS
        [-s <strategy>] [-X <strategy-option>]
        [--[no-]rerere-autoupdate] [-m <msg>] <commit>...
 'git merge' <msg> HEAD <commit>...
+'git merge' --abort
 
 DESCRIPTION
 -----------
@@ -47,6 +48,14 @@ The second syntax (<msg> `HEAD` <commit>...) is supported for
 historical reasons.  Do not use it from the command line or in
 new scripts.  It is the same as `git merge -m <msg> <commit>...`.
 
+The third syntax ("`git merge --abort`") can only be run after the
+merge has resulted in conflicts. 'git merge --abort' will abort the
+merge process and try to reconstruct the pre-merge state. However,
+if there were uncommitted changes when the merge started (and
+especially if those changes were further modified after the merge
+was started), 'git merge --abort' will in some cases be unable to
+reconstruct the original (pre-merge) changes. Therefore:
+
 *Warning*: Running 'git merge' with uncommitted changes is
 discouraged: while possible, it leaves you in a state that is hard to
 back out of in the case of a conflict.
@@ -72,6 +81,18 @@ invocations.
        Allow the rerere mechanism to update the index with the
        result of auto-conflict resolution if possible.
 
+--abort::
+       Abort the current conflict resolution process, and
+       try to reconstruct the pre-merge state.
++
+If there were uncommitted worktree changes present when the merge
+started, 'git merge --abort' will in some cases be unable to
+reconstruct these changes. It is therefore recommended to always
+commit or stash your changes before running 'git merge'.
++
+'git merge --abort' is equivalent to 'git reset --merge' when
+`MERGE_HEAD` is present.
+
 <commit>...::
        Commits, usually other branch heads, to merge into our branch.
        You need at least one <commit>.  Specifying more than one
@@ -142,7 +163,7 @@ happens:
    i.e. matching `HEAD`.
 
 If you tried a merge which resulted in complex conflicts and
-want to start over, you can recover with `git reset --merge`.
+want to start over, you can recover with `git merge --abort`.
 
 HOW CONFLICTS ARE PRESENTED
 ---------------------------
@@ -213,8 +234,8 @@ After seeing a conflict, you can do two things:
 
  * Decide not to merge.  The only clean-ups you need are to reset
    the index file to the `HEAD` commit to reverse 2. and to clean
-   up working tree changes made by 2. and 3.; `git-reset --hard` can
-   be used for this.
+   up working tree changes made by 2. and 3.; `git merge --abort`
+   can be used for this.
 
  * Resolve the conflicts.  Git will mark the conflicts in
    the working tree.  Edit the files into shape and
index 2981d8c5efd4e7e0f65d051cfe3b28d3430e3213..296f314eae5af684e68965f4b04bd9acbd4c35a1 100644 (file)
@@ -14,8 +14,12 @@ SYNOPSIS
 'git notes' append [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
 'git notes' edit [<object>]
 'git notes' show [<object>]
+'git notes' merge [-v | -q] [-s <strategy> ] <notes_ref>
+'git notes' merge --commit [-v | -q]
+'git notes' merge --abort [-v | -q]
 'git notes' remove [<object>]
 'git notes' prune [-n | -v]
+'git notes' get-ref
 
 
 DESCRIPTION
@@ -83,6 +87,21 @@ edit::
 show::
        Show the notes for a given object (defaults to HEAD).
 
+merge::
+       Merge the given notes ref into the current notes ref.
+       This will try to merge the changes made by the given
+       notes ref (called "remote") since the merge-base (if
+       any) into the current notes ref (called "local").
++
+If conflicts arise and a strategy for automatically resolving
+conflicting notes (see the -s/--strategy option) is not given,
+the "manual" resolver is used. This resolver checks out the
+conflicting notes in a special worktree (`.git/NOTES_MERGE_WORKTREE`),
+and instructs the user to manually resolve the conflicts there.
+When done, the user can either finalize the merge with
+'git notes merge --commit', or abort the merge with
+'git notes merge --abort'.
+
 remove::
        Remove the notes for a given object (defaults to HEAD).
        This is equivalent to specifying an empty note message to
@@ -91,6 +110,10 @@ remove::
 prune::
        Remove all notes for non-existing/unreachable objects.
 
+get-ref::
+       Print the current notes ref. This provides an easy way to
+       retrieve the current notes ref (e.g. from scripts).
+
 OPTIONS
 -------
 -f::
@@ -133,9 +156,37 @@ OPTIONS
        Do not remove anything; just report the object names whose notes
        would be removed.
 
+-s <strategy>::
+--strategy=<strategy>::
+       When merging notes, resolve notes conflicts using the given
+       strategy. The following strategies are recognized: "manual"
+       (default), "ours", "theirs", "union" and "cat_sort_uniq".
+       See the "NOTES MERGE STRATEGIES" section below for more
+       information on each notes merge strategy.
+
+--commit::
+       Finalize an in-progress 'git notes merge'. Use this option
+       when you have resolved the conflicts that 'git notes merge'
+       stored in .git/NOTES_MERGE_WORKTREE. This amends the partial
+       merge commit created by 'git notes merge' (stored in
+       .git/NOTES_MERGE_PARTIAL) by adding the notes in
+       .git/NOTES_MERGE_WORKTREE. The notes ref stored in the
+       .git/NOTES_MERGE_REF symref is updated to the resulting commit.
+
+--abort::
+       Abort/reset a in-progress 'git notes merge', i.e. a notes merge
+       with conflicts. This simply removes all files related to the
+       notes merge.
+
+-q::
+--quiet::
+       When merging notes, operate quietly.
+
 -v::
 --verbose::
-       Report all object names whose notes are removed.
+       When merging notes, be more verbose.
+       When pruning notes, report all object names whose notes are
+       removed.
 
 
 DISCUSSION
@@ -163,6 +214,38 @@ object, in which case the history of the notes can be read with
 `git log -p -g <refname>`.
 
 
+NOTES MERGE STRATEGIES
+----------------------
+
+The default notes merge strategy is "manual", which checks out
+conflicting notes in a special work tree for resolving notes conflicts
+(`.git/NOTES_MERGE_WORKTREE`), and instructs the user to resolve the
+conflicts in that work tree.
+When done, the user can either finalize the merge with
+'git notes merge --commit', or abort the merge with
+'git notes merge --abort'.
+
+"ours" automatically resolves conflicting notes in favor of the local
+version (i.e. the current notes ref).
+
+"theirs" automatically resolves notes conflicts in favor of the remote
+version (i.e. the given notes ref being merged into the current notes
+ref).
+
+"union" automatically resolves notes conflicts by concatenating the
+local and remote versions.
+
+"cat_sort_uniq" is similar to "union", but in addition to concatenating
+the local and remote versions, this strategy also sorts the resulting
+lines, and removes duplicate lines from the result. This is equivalent
+to applying the "cat | sort | uniq" shell pipeline to the local and
+remote versions. This strategy is useful if the notes follow a line-based
+format where one wants to avoid duplicated lines in the merge result.
+Note that if either the local or remote version contain duplicate lines
+prior to the merge, these will also be removed by this notes merge
+strategy.
+
+
 EXAMPLES
 --------
 
index e47361f2345bba78c917ab58bfe61800aa1e7156..4db73737b044dc151baed09a3f3d17e428eb124b 100644 (file)
@@ -27,8 +27,8 @@ With `--rebase`, it runs 'git rebase' instead of 'git merge'.
 passed to linkgit:git-fetch[1].  <refspec> can name an
 arbitrary remote ref (for example, the name of a tag) or even
 a collection of refs with corresponding remote-tracking branches
-(e.g., refs/heads/*:refs/remotes/origin/*), but usually it is
-the name of a branch in the remote repository.
+(e.g., refs/heads/{asterisk}:refs/remotes/origin/{asterisk}),
+but usually it is the name of a branch in the remote repository.
 
 Default values for <repository> and <branch> are read from the
 "remote" and "merge" configuration for the current branch
diff --git a/Documentation/git-remote-ext.txt b/Documentation/git-remote-ext.txt
new file mode 100644 (file)
index 0000000..f4fbf67
--- /dev/null
@@ -0,0 +1,125 @@
+git-remote-ext(1)
+=================
+
+NAME
+----
+git-remote-ext - Bridge smart transport to external command.
+
+SYNOPSIS
+--------
+git remote add nick "ext::<command>[ <arguments>...]"
+
+DESCRIPTION
+-----------
+This remote helper uses the specified 'program' to connect
+to a remote git server.
+
+Data written to stdin of this specified 'program' is assumed
+to be sent to git:// server, git-upload-pack, git-receive-pack
+or git-upload-archive (depending on situation), and data read
+from stdout of this program is assumed to be received from
+the same service.
+
+Command and arguments are separated by unescaped space.
+
+The following sequences have a special meaning:
+
+'% '::
+       Literal space in command or argument.
+
+'%%'::
+       Literal percent sign.
+
+'%s'::
+       Replaced with name (receive-pack, upload-pack, or
+       upload-archive) of the service git wants to invoke.
+
+'%S'::
+       Replaced with long name (git-receive-pack,
+       git-upload-pack, or git-upload-archive) of the service
+       git wants to invoke.
+
+'%G' (must be first characters in argument)::
+       This argument will not be passed to 'program'. Instead, it
+       will cause helper to start by sending git:// service request to
+       remote side with service field set to approiate value and
+       repository field set to rest of the argument. Default is not to send
+       such request.
++
+This is useful if remote side is git:// server accessed over
+some tunnel.
+
+'%V' (must be first characters in argument)::
+       This argument will not be passed to 'program'. Instead it sets
+       the vhost field in git:// service request (to rest of the argument).
+       Default is not to send vhost in such request (if sent).
+
+ENVIRONMENT VARIABLES:
+----------------------
+
+GIT_TRANSLOOP_DEBUG::
+       If set, prints debugging information about various reads/writes.
+
+ENVIRONMENT VARIABLES PASSED TO COMMAND:
+----------------------------------------
+
+GIT_EXT_SERVICE::
+       Set to long name (git-upload-pack, etc...) of service helper needs
+       to invoke.
+
+GIT_EXT_SERVICE_NOPREFIX::
+       Set to long name (upload-pack, etc...) of service helper needs
+       to invoke.
+
+
+EXAMPLES:
+---------
+This remote helper is transparently used by git when
+you use commands such as "git fetch <URL>", "git clone <URL>",
+, "git push <URL>" or "git remote add nick <URL>", where <URL>
+begins with `ext::`.  Examples:
+
+"ext::ssh -i /home/foo/.ssh/somekey user&#64;host.example %S 'foo/repo'"::
+       Like host.example:foo/repo, but use /home/foo/.ssh/somekey as
+       keypair and user as user on remote side. This avoids needing to
+       edit .ssh/config.
+
+"ext::socat -t3600 - ABSTRACT-CONNECT:/git-server %G/somerepo"::
+       Represents repository with path /somerepo accessable over
+       git protocol at abstract namespace address /git-server.
+
+"ext::git-server-alias foo %G/repo"::
+       Represents a repository with path /repo accessed using the
+       helper program "git-server-alias foo".  The path to the
+       repository and type of request are not passed on the command
+       line but as part of the protocol stream, as usual with git://
+       protocol.
+
+"ext::git-server-alias foo %G/repo %Vfoo"::
+       Represents a repository with path /repo accessed using the
+       helper program "git-server-alias foo".  The hostname for the
+       remote server passed in the protocol stream will be "foo"
+       (this allows multiple virtual git servers to share a
+       link-level address).
+
+"ext::git-server-alias foo %G/repo% with% spaces %Vfoo"::
+       Represents a repository with path '/repo with spaces' accessed
+       using the helper program "git-server-alias foo".  The hostname for
+       the remote server passed in the protocol stream will be "foo"
+       (this allows multiple virtual git servers to share a
+       link-level address).
+
+"ext::git-ssl foo.example /bar"::
+       Represents a repository accessed using the helper program
+       "git-ssl foo.example /bar".  The type of request can be
+       determined by the helper using environment variables (see
+       above).
+
+Documentation
+--------------
+Documentation by Ilari Liusvaara, Jonathan Nieder and the git list
+<git@vger.kernel.org>
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-remote-fd.txt b/Documentation/git-remote-fd.txt
new file mode 100644 (file)
index 0000000..abc4944
--- /dev/null
@@ -0,0 +1,59 @@
+git-remote-fd(1)
+================
+
+NAME
+----
+git-remote-fd - Reflect smart transport stream back to caller
+
+SYNOPSIS
+--------
+"fd::<infd>[,<outfd>][/<anything>]" (as URL)
+
+DESCRIPTION
+-----------
+This helper uses specified file descriptors to connect to remote git server.
+This is not meant for end users but for programs and scripts calling git
+fetch, push or archive.
+
+If only <infd> is given, it is assumed to be bidirectional socket connected
+to remote git server (git-upload-pack, git-receive-pack or
+git-upload-achive). If both <infd> and <outfd> are given, they are assumed
+to be pipes connected to remote git server (<infd> being the inbound pipe
+and <outfd> being the outbound pipe.
+
+It is assumed that any handshaking procedures have already been completed
+(such as sending service request for git://) before this helper is started.
+
+<anything> can be any string. It is ignored. It is meant for provoding
+information to user in the URL in case that URL is displayed in some
+context.
+
+ENVIRONMENT VARIABLES
+---------------------
+GIT_TRANSLOOP_DEBUG::
+       If set, prints debugging information about various reads/writes.
+
+EXAMPLES
+--------
+git fetch fd::17 master::
+       Fetch master, using file descriptor #17 to communicate with
+       git-upload-pack.
+
+git fetch fd::17/foo master::
+       Same as above.
+
+git push fd::7,8 master (as URL)::
+       Push master, using file descriptor #7 to read data from
+       git-receive-pack and file descriptor #8 to write data to
+       same service.
+
+git push fd::7,8/bar master::
+       Same as above.
+
+Documentation
+--------------
+Documentation by Ilari Liusvaara and the git list <git@vger.kernel.org>
+
+GIT
+---
+Part of the linkgit:git[1] suite
index f40984d144e4ca276f84fc81a74c4ae726dcf497..752fc88e768e4b8e1afddad4bdd7833cf20978c7 100644 (file)
@@ -87,7 +87,7 @@ git revert HEAD~3::
        Revert the changes specified by the fourth last commit in HEAD
        and create a new commit with the reverted changes.
 
-git revert -n master\~5..master~2::
+git revert -n master{tilde}5..master{tilde}2::
 
        Revert the changes done by commits from the fifth last commit
        in master (included) to the third last commit in master
index 71e3d9fc23ab6f1c181c47ebb7c9483950a9e9a6..0adbe8b1f8e6e992a1f991fbdba4deffbd6e15b0 100644 (file)
@@ -89,8 +89,8 @@ the paths that have disappeared from the filesystem. However,
 depending on the use case, there are several ways that can be
 done.
 
-Using "git commit -a"
-~~~~~~~~~~~~~~~~~~~~~
+Using ``git commit -a''
+~~~~~~~~~~~~~~~~~~~~~~~
 If you intend that your next commit should record all modifications
 of tracked files in the working tree and record all removals of
 files that have been removed from the working tree with `rm`
@@ -98,8 +98,8 @@ files that have been removed from the working tree with `rm`
 automatically notice and record all removals.  You can also have a
 similar effect without committing by using `git add -u`.
 
-Using "git add -A"
-~~~~~~~~~~~~~~~~~~
+Using ``git add -A''
+~~~~~~~~~~~~~~~~~~~~
 When accepting a new code drop for a vendor branch, you probably
 want to record both the removal of paths and additions of new paths
 as well as modifications of existing paths.
@@ -111,8 +111,8 @@ tree using this command:
 git ls-files -z | xargs -0 rm -f
 ----------------
 
-and then "untar" the new code in the working tree. Alternately
-you could "rsync" the changes into the working tree.
+and then untar the new code in the working tree. Alternately
+you could 'rsync' the changes into the working tree.
 
 After that, the easiest way to record all removals, additions, and
 modifications in the working tree is:
index ebc024ae3d6f48671858197d46ba74329e6b1b3f..7ec9dabe6815f2b8ff03048043e1cfd81cd19fcc 100644 (file)
@@ -322,6 +322,9 @@ have been specified, in which case default to 'compose'.
 Default is the value of 'sendemail.validate'; if this is not set,
 default to '--validate'.
 
+--force::
+       Send emails even if safety checks would prevent it.
+
 
 CONFIGURATION
 -------------
index 0c897df6a71ddb5e7f4e960300c2b336ede5aab2..821608308174817861012f64439c8e8b60a1e134 100644 (file)
@@ -44,31 +44,35 @@ 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.3.2/git.html[documentation for release 1.7.3.2]
+* link:v1.7.3.3/git.html[documentation for release 1.7.3.3]
 
 * release notes for
+  link:RelNotes/1.7.3.3.txt[1.7.3.3],
   link:RelNotes/1.7.3.2.txt[1.7.3.2],
   link:RelNotes/1.7.3.1.txt[1.7.3.1],
   link:RelNotes/1.7.3.txt[1.7.3].
 
-* link:v1.7.2.3/git.html[documentation for release 1.7.2.3]
+* link:v1.7.2.4/git.html[documentation for release 1.7.2.4]
 
 * release notes for
+  link:RelNotes/1.7.2.4.txt[1.7.2.4],
   link:RelNotes/1.7.2.3.txt[1.7.2.3],
   link:RelNotes/1.7.2.2.txt[1.7.2.2],
   link:RelNotes/1.7.2.1.txt[1.7.2.1],
   link:RelNotes/1.7.2.txt[1.7.2].
 
-* link:v1.7.1.2/git.html[documentation for release 1.7.1.2]
+* link:v1.7.1.3/git.html[documentation for release 1.7.1.3]
 
 * release notes for
+  link:RelNotes/1.7.1.3.txt[1.7.1.3],
   link:RelNotes/1.7.1.2.txt[1.7.1.2],
   link:RelNotes/1.7.1.1.txt[1.7.1.1],
   link:RelNotes/1.7.1.txt[1.7.1].
 
-* link:v1.7.0.7/git.html[documentation for release 1.7.0.7]
+* link:v1.7.0.8/git.html[documentation for release 1.7.0.8]
 
 * release notes for
+  link:RelNotes/1.7.0.8.txt[1.7.0.8],
   link:RelNotes/1.7.0.7.txt[1.7.0.7],
   link:RelNotes/1.7.0.6.txt[1.7.0.6],
   link:RelNotes/1.7.0.5.txt[1.7.0.5],
index 7dc2e8b0bcdfb189724ef9b2204f4e624e3be589..8416f3445a5e5d3b583733136d4211b8d0bd4084 100644 (file)
@@ -14,11 +14,8 @@ DESCRIPTION
 
 A `gitignore` file specifies intentionally untracked files that
 git should ignore.
-Note that all the `gitignore` files really concern only files
-that are not already tracked by git;
-in order to ignore uncommitted changes in already tracked files,
-please refer to the 'git update-index --assume-unchanged'
-documentation.
+Files already tracked by git are not affected; see the NOTES
+below for details.
 
 Each line in a `gitignore` file specifies a pattern.
 When deciding whether to ignore a path, git normally checks
@@ -62,7 +59,8 @@ files specified by command-line options.  Higher-level git
 tools, such as 'git status' and 'git add',
 use patterns from the sources specified above.
 
-Patterns have the following format:
+PATTERN FORMAT
+--------------
 
  - A blank line matches no files, so it can serve as a separator
    for readability.
@@ -98,7 +96,20 @@ Patterns have the following format:
    For example, "/{asterisk}.c" matches "cat-file.c" but not
    "mozilla-sha1/sha1.c".
 
-An example:
+NOTES
+-----
+
+The purpose of gitignore files is to ensure that certain files
+not tracked by git remain untracked.
+
+To ignore uncommitted changes in a file that is already tracked,
+use 'git update-index {litdd}assume-unchanged'.
+
+To stop tracking a file that is currently tracked, use
+'git rm --cached'.
+
+EXAMPLES
+--------
 
 --------------------------------------------------------------
     $ git status
@@ -140,6 +151,11 @@ Another example:
 The second .gitignore prevents git from ignoring
 `arch/foo/kernel/vmlinux.lds.S`.
 
+SEE ALSO
+--------
+linkgit:git-rm[1], linkgit:git-update-index[1],
+linkgit:gitrepository-layout[5]
+
 Documentation
 -------------
 Documentation by David Greaves, Junio C Hamano, Josh Triplett,
index 919ed2b7ec87cb8399bb7c1119251c1704e2fbe7..41ab490dbd6e0decc20e02ac10e29daf99d9e07b 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -70,6 +70,11 @@ all::
 #
 # Define NO_STRTOK_R if you don't have strtok_r in the C library.
 #
+# Define NO_FNMATCH if you don't have fnmatch in the C library.
+#
+# Define NO_FNMATCH_CASEFOLD if your fnmatch function doesn't have the
+# FNM_CASEFOLD GNU extension.
+#
 # Define NO_LIBGEN_H if you don't have libgen.h.
 #
 # Define NEEDS_LIBGEN if your libgen needs -lgen when linking
@@ -520,6 +525,7 @@ LIB_H += mailmap.h
 LIB_H += merge-recursive.h
 LIB_H += notes.h
 LIB_H += notes-cache.h
+LIB_H += notes-merge.h
 LIB_H += object.h
 LIB_H += pack.h
 LIB_H += pack-refs.h
@@ -610,6 +616,7 @@ LIB_OBJS += merge-recursive.o
 LIB_OBJS += name-hash.o
 LIB_OBJS += notes.o
 LIB_OBJS += notes-cache.o
+LIB_OBJS += notes-merge.o
 LIB_OBJS += object.o
 LIB_OBJS += pack-check.o
 LIB_OBJS += pack-refs.o
@@ -665,6 +672,7 @@ LIB_OBJS += write_or_die.o
 LIB_OBJS += ws.o
 LIB_OBJS += wt-status.o
 LIB_OBJS += xdiff-interface.o
+LIB_OBJS += zlib.o
 
 BUILTIN_OBJS += builtin/add.o
 BUILTIN_OBJS += builtin/annotate.o
@@ -731,6 +739,8 @@ BUILTIN_OBJS += builtin/read-tree.o
 BUILTIN_OBJS += builtin/receive-pack.o
 BUILTIN_OBJS += builtin/reflog.o
 BUILTIN_OBJS += builtin/remote.o
+BUILTIN_OBJS += builtin/remote-ext.o
+BUILTIN_OBJS += builtin/remote-fd.o
 BUILTIN_OBJS += builtin/replace.o
 BUILTIN_OBJS += builtin/rerere.o
 BUILTIN_OBJS += builtin/reset.o
@@ -849,6 +859,7 @@ ifeq ($(uname_S),SunOS)
        NO_MKDTEMP = YesPlease
        NO_MKSTEMPS = YesPlease
        NO_REGEX = YesPlease
+       NO_FNMATCH_CASEFOLD = YesPlease
        ifeq ($(uname_R),5.6)
                SOCKLEN_T = int
                NO_HSTRERROR = YesPlease
@@ -1055,6 +1066,7 @@ ifeq ($(uname_S),Windows)
        NO_STRCASESTR = YesPlease
        NO_STRLCPY = YesPlease
        NO_STRTOK_R = YesPlease
+       NO_FNMATCH = YesPlease
        NO_MEMMEM = YesPlease
        # NEEDS_LIBICONV = YesPlease
        NO_ICONV = YesPlease
@@ -1084,8 +1096,8 @@ ifeq ($(uname_S),Windows)
        AR = compat/vcbuild/scripts/lib.pl
        CFLAGS =
        BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE
-       COMPAT_OBJS = compat/msvc.o compat/fnmatch/fnmatch.o compat/winansi.o compat/win32/pthread.o compat/win32/syslog.o compat/win32/sys/poll.o
-       COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/fnmatch -Icompat/regex -Icompat/fnmatch -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
+       COMPAT_OBJS = compat/msvc.o compat/winansi.o compat/win32/pthread.o compat/win32/syslog.o compat/win32/sys/poll.o
+       COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
        BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib
        EXTLIBS = advapi32.lib shell32.lib wininet.lib ws2_32.lib
        PTHREAD_LIBS =
@@ -1099,6 +1111,25 @@ else
 endif
        X = .exe
 endif
+ifeq ($(uname_S),Interix)
+       NO_SYS_POLL_H = YesPlease
+       NO_INTTYPES_H = YesPlease
+       NO_INITGROUPS = YesPlease
+       NO_IPV6 = YesPlease
+       NO_MEMMEM = YesPlease
+       NO_MKDTEMP = YesPlease
+       NO_STRTOUMAX = YesPlease
+       NO_NSEC = YesPlease
+       NO_MKSTEMPS = YesPlease
+       ifeq ($(uname_R),3.5)
+               NO_INET_NTOP = YesPlease
+               NO_INET_PTON = YesPlease
+       endif
+       ifeq ($(uname_R),5.2)
+               NO_INET_NTOP = YesPlease
+               NO_INET_PTON = YesPlease
+       endif
+endif
 ifneq (,$(findstring MINGW,$(uname_S)))
        pathsep = ;
        NO_PREAD = YesPlease
@@ -1110,6 +1141,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
        NO_STRCASESTR = YesPlease
        NO_STRLCPY = YesPlease
        NO_STRTOK_R = YesPlease
+       NO_FNMATCH = YesPlease
        NO_MEMMEM = YesPlease
        NEEDS_LIBICONV = YesPlease
        OLD_ICONV = YesPlease
@@ -1133,9 +1165,9 @@ ifneq (,$(findstring MINGW,$(uname_S)))
        NO_INET_PTON = YesPlease
        NO_INET_NTOP = YesPlease
        NO_POSIX_GOODIES = UnfortunatelyYes
-       COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch -Icompat/win32
+       COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/win32
        COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
-       COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o \
+       COMPAT_OBJS += compat/mingw.o compat/winansi.o \
                compat/win32/pthread.o compat/win32/syslog.o \
                compat/win32/sys/poll.o
        EXTLIBS += -lws2_32
@@ -1345,6 +1377,17 @@ ifdef NO_STRTOK_R
        COMPAT_CFLAGS += -DNO_STRTOK_R
        COMPAT_OBJS += compat/strtok_r.o
 endif
+ifdef NO_FNMATCH
+       COMPAT_CFLAGS += -Icompat/fnmatch
+       COMPAT_CFLAGS += -DNO_FNMATCH
+       COMPAT_OBJS += compat/fnmatch/fnmatch.o
+else
+ifdef NO_FNMATCH_CASEFOLD
+       COMPAT_CFLAGS += -Icompat/fnmatch
+       COMPAT_CFLAGS += -DNO_FNMATCH_CASEFOLD
+       COMPAT_OBJS += compat/fnmatch/fnmatch.o
+endif
+endif
 ifdef NO_SETENV
        COMPAT_CFLAGS += -DNO_SETENV
        COMPAT_OBJS += compat/setenv.o
@@ -1363,6 +1406,15 @@ endif
 ifdef NO_SYS_SELECT_H
        BASIC_CFLAGS += -DNO_SYS_SELECT_H
 endif
+ifdef NO_SYS_POLL_H
+       BASIC_CFLAGS += -DNO_SYS_POLL_H
+endif
+ifdef NO_INTTYPES_H
+       BASIC_CFLAGS += -DNO_INTTYPES_H
+endif
+ifdef NO_INITGROUPS
+       BASIC_CFLAGS += -DNO_INITGROUPS
+endif
 ifdef NO_MMAP
        COMPAT_CFLAGS += -DNO_MMAP
        COMPAT_OBJS += compat/mmap.o
@@ -1775,6 +1827,8 @@ XDIFF_OBJS = xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \
        xdiff/xmerge.o xdiff/xpatience.o
 VCSSVN_OBJS = vcs-svn/string_pool.o vcs-svn/line_buffer.o \
        vcs-svn/repo_tree.o vcs-svn/fast_export.o vcs-svn/svndump.o
+VCSSVN_TEST_OBJS = test-obj-pool.o test-string-pool.o \
+       test-line-buffer.o test-treap.o
 OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) $(VCSSVN_OBJS)
 
 dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d)
@@ -1883,13 +1937,12 @@ builtin/branch.o builtin/checkout.o builtin/clone.o builtin/reset.o branch.o tra
 builtin/bundle.o bundle.o transport.o: bundle.h
 builtin/bisect--helper.o builtin/rev-list.o bisect.o: bisect.h
 builtin/clone.o builtin/fetch-pack.o transport.o: fetch-pack.h
-builtin/grep.o: thread-utils.h
+builtin/grep.o builtin/pack-objects.o transport-helper.o: thread-utils.h
 builtin/send-pack.o transport.o: send-pack.h
 builtin/log.o builtin/shortlog.o: shortlog.h
 builtin/prune.o builtin/reflog.o reachable.o: reachable.h
 builtin/commit.o builtin/revert.o wt-status.o: wt-status.h
 builtin/tar-tree.o archive-tar.o: tar.h
-builtin/pack-objects.o: thread-utils.h
 connect.o transport.o http-backend.o: url.h
 http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h
 http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h
@@ -1898,10 +1951,12 @@ xdiff-interface.o $(XDIFF_OBJS): \
        xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \
        xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h
 
-$(VCSSVN_OBJS): \
+$(VCSSVN_OBJS) $(VCSSVN_TEST_OBJS): $(LIB_H) \
        vcs-svn/obj_pool.h vcs-svn/trp.h vcs-svn/string_pool.h \
        vcs-svn/line_buffer.h vcs-svn/repo_tree.h vcs-svn/fast_export.h \
        vcs-svn/svndump.h
+
+test-svn-fe.o: vcs-svn/svndump.h
 endif
 
 exec_cmd.s exec_cmd.o: EXTRA_CPPFLAGS = \
index 8dd4569b3c06654826c298b6b52dcf2ac17481bb..904e067a88f242b42f16b715d2b67b45a101e468 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -16,7 +16,7 @@ extern const char git_more_info_string[];
 extern void prune_packed_objects(int);
 extern int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
                         int merge_title, int shortlog_len);
-extern int commit_notes(struct notes_tree *t, const char *msg);
+extern void commit_notes(struct notes_tree *t, const char *msg);
 
 struct notes_rewrite_cfg {
        struct notes_tree **trees;
@@ -108,6 +108,8 @@ extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_receive_pack(int argc, const char **argv, const char *prefix);
 extern int cmd_reflog(int argc, const char **argv, const char *prefix);
 extern int cmd_remote(int argc, const char **argv, const char *prefix);
+extern int cmd_remote_ext(int argc, const char **argv, const char *prefix);
+extern int cmd_remote_fd(int argc, const char **argv, const char *prefix);
 extern int cmd_config(int argc, const char **argv, const char *prefix);
 extern int cmd_rerere(int argc, const char **argv, const char *prefix);
 extern int cmd_reset(int argc, const char **argv, const char *prefix);
index 71f9b04fef829ad3725ca50902bd6935dc3cda36..12b964e642b91863776f161a7b2aab2ec216efcb 100644 (file)
@@ -331,7 +331,8 @@ static struct option builtin_add_options[] = {
 
 static int add_config(const char *var, const char *value, void *cb)
 {
-       if (!strcasecmp(var, "add.ignore-errors")) {
+       if (!strcasecmp(var, "add.ignoreerrors") ||
+           !strcasecmp(var, "add.ignore-errors")) {
                ignore_add_errors = git_config_bool(var, value);
                return 0;
        }
@@ -446,7 +447,8 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                        if (!seen[i] && pathspec[i][0]
                            && !file_exists(pathspec[i])) {
                                if (ignore_missing) {
-                                       if (excluded(&dir, pathspec[i], DT_UNKNOWN))
+                                       int dtype = DT_UNKNOWN;
+                                       if (excluded(&dir, pathspec[i], &dtype))
                                                dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i]));
                                } else
                                        die("pathspec '%s' did not match any files",
index 96246e960f9d9fb9230edc0f4e7a9a13267608f2..14951daedffa9e8a8a913eee5e8423220e86e291 100644 (file)
@@ -449,7 +449,7 @@ static char *find_name_gnu(const char *line, char *def, int p_value)
        return squash_slash(strbuf_detach(&name, NULL));
 }
 
-static size_t tz_len(const char *line, size_t len)
+static size_t sane_tz_len(const char *line, size_t len)
 {
        const char *tz, *p;
 
@@ -467,6 +467,24 @@ static size_t tz_len(const char *line, size_t len)
        return line + len - tz;
 }
 
+static size_t tz_with_colon_len(const char *line, size_t len)
+{
+       const char *tz, *p;
+
+       if (len < strlen(" +08:00") || line[len - strlen(":00")] != ':')
+               return 0;
+       tz = line + len - strlen(" +08:00");
+
+       if (tz[0] != ' ' || (tz[1] != '+' && tz[1] != '-'))
+               return 0;
+       p = tz + 2;
+       if (!isdigit(*p++) || !isdigit(*p++) || *p++ != ':' ||
+           !isdigit(*p++) || !isdigit(*p++))
+               return 0;
+
+       return line + len - tz;
+}
+
 static size_t date_len(const char *line, size_t len)
 {
        const char *date, *p;
@@ -561,7 +579,9 @@ static size_t diff_timestamp_len(const char *line, size_t len)
        if (!isdigit(end[-1]))
                return 0;
 
-       n = tz_len(line, end - line);
+       n = sane_tz_len(line, end - line);
+       if (!n)
+               n = tz_with_colon_len(line, end - line);
        end -= n;
 
        n = short_time_len(line, end - line);
@@ -733,8 +753,8 @@ static int has_epoch_timestamp(const char *nameline)
                " "
                "[0-2][0-9]:[0-5][0-9]:00(\\.0+)?"
                " "
-               "([-+][0-2][0-9][0-5][0-9])\n";
-       const char *timestamp = NULL, *cp;
+               "([-+][0-2][0-9]:?[0-5][0-9])\n";
+       const char *timestamp = NULL, *cp, *colon;
        static regex_t *stamp;
        regmatch_t m[10];
        int zoneoffset;
@@ -764,8 +784,11 @@ static int has_epoch_timestamp(const char *nameline)
                return 0;
        }
 
-       zoneoffset = strtol(timestamp + m[3].rm_so + 1, NULL, 10);
-       zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100);
+       zoneoffset = strtol(timestamp + m[3].rm_so + 1, (char **) &colon, 10);
+       if (*colon == ':')
+               zoneoffset = zoneoffset * 60 + strtol(colon + 1, NULL, 10);
+       else
+               zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100);
        if (timestamp[m[3].rm_so] == '-')
                zoneoffset = -zoneoffset;
 
@@ -919,28 +942,28 @@ static int gitdiff_newfile(const char *line, struct patch *patch)
 static int gitdiff_copysrc(const char *line, struct patch *patch)
 {
        patch->is_copy = 1;
-       patch->old_name = find_name(line, NULL, 0, 0);
+       patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
        return 0;
 }
 
 static int gitdiff_copydst(const char *line, struct patch *patch)
 {
        patch->is_copy = 1;
-       patch->new_name = find_name(line, NULL, 0, 0);
+       patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
        return 0;
 }
 
 static int gitdiff_renamesrc(const char *line, struct patch *patch)
 {
        patch->is_rename = 1;
-       patch->old_name = find_name(line, NULL, 0, 0);
+       patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
        return 0;
 }
 
 static int gitdiff_renamedst(const char *line, struct patch *patch)
 {
        patch->is_rename = 1;
-       patch->new_name = find_name(line, NULL, 0, 0);
+       patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
        return 0;
 }
 
@@ -1025,7 +1048,7 @@ static char *git_header_name(char *line, int llen)
 {
        const char *name;
        const char *second = NULL;
-       size_t len;
+       size_t len, line_len;
 
        line += strlen("diff --git ");
        llen -= strlen("diff --git ");
@@ -1125,6 +1148,10 @@ static char *git_header_name(char *line, int llen)
         * Accept a name only if it shows up twice, exactly the same
         * form.
         */
+       second = strchr(name, '\n');
+       if (!second)
+               return NULL;
+       line_len = second - name;
        for (len = 0 ; ; len++) {
                switch (name[len]) {
                default:
@@ -1132,15 +1159,11 @@ static char *git_header_name(char *line, int llen)
                case '\n':
                        return NULL;
                case '\t': case ' ':
-                       second = name+len;
-                       for (;;) {
-                               char c = *second++;
-                               if (c == '\n')
-                                       return NULL;
-                               if (c == '/')
-                                       break;
-                       }
-                       if (second[len] == '\n' && !memcmp(name, second, len)) {
+                       second = stop_at_slash(name + len, line_len - len);
+                       if (!second)
+                               return NULL;
+                       second++;
+                       if (second[len] == '\n' && !strncmp(name, second, len)) {
                                return xmemdupz(name, len);
                        }
                }
index f5fccc1f6767d7386e37f7273c560d0d4491ee91..cb25ec9ce882ac2b975420bb25735fbd156dc4ab 100644 (file)
@@ -1617,6 +1617,7 @@ static const char *format_time(unsigned long time, const char *tz_str,
 #define OUTPUT_SHOW_NUMBER     040
 #define OUTPUT_SHOW_SCORE      0100
 #define OUTPUT_NO_AUTHOR       0200
+#define OUTPUT_SHOW_EMAIL      0400
 
 static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
 {
@@ -1682,12 +1683,17 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
                }
 
                printf("%.*s", length, hex);
-               if (opt & OUTPUT_ANNOTATE_COMPAT)
-                       printf("\t(%10s\t%10s\t%d)", ci.author,
+               if (opt & OUTPUT_ANNOTATE_COMPAT) {
+                       const char *name;
+                       if (opt & OUTPUT_SHOW_EMAIL)
+                               name = ci.author_mail;
+                       else
+                               name = ci.author;
+                       printf("\t(%10s\t%10s\t%d)", name,
                               format_time(ci.author_time, ci.author_tz,
                                           show_raw_time),
                               ent->lno + 1 + cnt);
-               else {
+               else {
                        if (opt & OUTPUT_SHOW_SCORE)
                                printf(" %*d %02d",
                                       max_score_digits, ent->score,
@@ -1700,9 +1706,15 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
                                       ent->s_lno + 1 + cnt);
 
                        if (!(opt & OUTPUT_NO_AUTHOR)) {
-                               int pad = longest_author - utf8_strwidth(ci.author);
+                               const char *name;
+                               int pad;
+                               if (opt & OUTPUT_SHOW_EMAIL)
+                                       name = ci.author_mail;
+                               else
+                                       name = ci.author;
+                               pad = longest_author - utf8_strwidth(name);
                                printf(" (%s%*s %10s",
-                                      ci.author, pad, "",
+                                      name, pad, "",
                                       format_time(ci.author_time,
                                                   ci.author_tz,
                                                   show_raw_time));
@@ -1840,7 +1852,10 @@ static void find_alignment(struct scoreboard *sb, int *option)
                if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
                        suspect->commit->object.flags |= METAINFO_SHOWN;
                        get_commit_info(suspect->commit, &ci, 1);
-                       num = utf8_strwidth(ci.author);
+                       if (*option & OUTPUT_SHOW_EMAIL)
+                               num = utf8_strwidth(ci.author_mail);
+                       else
+                               num = utf8_strwidth(ci.author);
                        if (longest_author < num)
                                longest_author = num;
                }
@@ -2289,6 +2304,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                OPT_BIT('t', NULL, &output_option, "Show raw timestamp (Default: off)", OUTPUT_RAW_TIMESTAMP),
                OPT_BIT('l', NULL, &output_option, "Show long commit SHA1 (Default: off)", OUTPUT_LONG_OBJECT_NAME),
                OPT_BIT('s', NULL, &output_option, "Suppress author name and timestamp (Default: off)", OUTPUT_NO_AUTHOR),
+               OPT_BIT('e', "show-email", &output_option, "Show author email instead of name (Default: off)", OUTPUT_SHOW_EMAIL),
                OPT_BIT('w', NULL, &xdl_opts, "Ignore whitespace differences", XDF_IGNORE_WHITESPACE),
                OPT_STRING('S', NULL, &revs_file, "file", "Use revisions from <file> instead of calling git-rev-list"),
                OPT_STRING(0, "contents", &contents_from, "file", "Use <file>'s contents as the final image"),
index 807355a1982f0f91e9acea66c360903fc3714495..0cad20bb5a8bd0054761861aada7f1077260dbd5 100644 (file)
@@ -668,6 +668,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT_END(),
        };
 
+       if (argc == 2 && !strcmp(argv[1], "-h"))
+               usage_with_options(builtin_branch_usage, options);
+
        git_config(git_branch_config, NULL);
 
        if (branch_use_color == -1)
index 1ee30443074bbae67a402c5397b939a716f2a191..f1fec24745a854e77555a1b4bea3bc8d7ab04367 100644 (file)
@@ -241,6 +241,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
                OPT_END()
        };
 
+       if (argc == 2 && !strcmp(argv[1], "-h"))
+               usage_with_options(builtin_checkout_index_usage,
+                                  builtin_checkout_index_options);
        git_config(git_default_config, NULL);
        state.base_dir = "";
        prefix_length = prefix ? strlen(prefix) : 0;
index 19ed64041d0575ee1ad49071dfe1e671114b8ae3..61e0989b5ab8fffb16ec28e64c38b4fa236cb3ed 100644 (file)
@@ -66,6 +66,8 @@ static struct option builtin_clone_options[] = {
                    "setup as shared repository"),
        OPT_BOOLEAN(0, "recursive", &option_recursive,
                    "initialize submodules in the clone"),
+       OPT_BOOLEAN(0, "recurse_submodules", &option_recursive,
+                   "initialize submodules in the clone"),
        OPT_STRING(0, "template", &option_template, "path",
                   "path the template repository"),
        OPT_STRING(0, "reference", &option_reference, "repo",
index 4fd1a1692f867e54eddd406d505e584ee0451cf8..c045c9ef8cf19b5458c279cd46250c839285a4a4 100644 (file)
@@ -69,6 +69,7 @@ static enum {
 static const char *logfile, *force_author;
 static const char *template_file;
 static char *edit_message, *use_message;
+static char *fixup_message, *squash_message;
 static char *author_name, *author_email, *author_date;
 static int all, edit_flag, also, interactive, only, amend, signoff;
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
@@ -124,6 +125,8 @@ static struct option builtin_commit_options[] = {
        OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m),
        OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"),
        OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"),
+       OPT_STRING(0, "fixup", &fixup_message, "COMMIT", "use autosquash formatted message to fixup specified commit"),
+       OPT_STRING(0, "squash", &squash_message, "COMMIT", "use autosquash formatted message to squash specified commit"),
        OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"),
        OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
        OPT_FILENAME('t', "template", &template_file, "use specified template file"),
@@ -565,6 +568,25 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
        if (!no_verify && run_hook(index_file, "pre-commit", NULL))
                return 0;
 
+       if (squash_message) {
+               /*
+                * Insert the proper subject line before other commit
+                * message options add their content.
+                */
+               if (use_message && !strcmp(use_message, squash_message))
+                       strbuf_addstr(&sb, "squash! ");
+               else {
+                       struct pretty_print_context ctx = {0};
+                       struct commit *c;
+                       c = lookup_commit_reference_by_name(squash_message);
+                       if (!c)
+                               die("could not lookup commit %s", squash_message);
+                       ctx.output_encoding = get_commit_output_encoding();
+                       format_commit_message(c, "squash! %s\n\n", &sb,
+                                             &ctx);
+               }
+       }
+
        if (message.len) {
                strbuf_addbuf(&sb, &message);
                hook_arg1 = "message";
@@ -586,6 +608,16 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
                hook_arg1 = "commit";
                hook_arg2 = use_message;
+       } else if (fixup_message) {
+               struct pretty_print_context ctx = {0};
+               struct commit *commit;
+               commit = lookup_commit_reference_by_name(fixup_message);
+               if (!commit)
+                       die("could not lookup commit %s", fixup_message);
+               ctx.output_encoding = get_commit_output_encoding();
+               format_commit_message(commit, "fixup! %s\n\n",
+                                     &sb, &ctx);
+               hook_arg1 = "message";
        } else if (!stat(git_path("MERGE_MSG"), &statbuf)) {
                if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0)
                        die_errno("could not read MERGE_MSG");
@@ -607,6 +639,16 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
        else if (in_merge)
                hook_arg1 = "merge";
 
+       if (squash_message) {
+               /*
+                * If squash_commit was used for the commit subject,
+                * then we're possibly hijacking other commit log options.
+                * Reset the hook args to tell the real story.
+                */
+               hook_arg1 = "message";
+               hook_arg2 = "";
+       }
+
        fp = fopen(git_path(commit_editmsg), "w");
        if (fp == NULL)
                die_errno("could not open '%s'", git_path(commit_editmsg));
@@ -863,7 +905,7 @@ static int parse_and_validate_options(int argc, const char *argv[],
        if (force_author && renew_authorship)
                die("Using both --reset-author and --author does not make sense");
 
-       if (logfile || message.len || use_message)
+       if (logfile || message.len || use_message || fixup_message)
                use_editor = 0;
        if (edit_flag)
                use_editor = 1;
@@ -878,48 +920,35 @@ static int parse_and_validate_options(int argc, const char *argv[],
                die("You have nothing to amend.");
        if (amend && in_merge)
                die("You are in the middle of a merge -- cannot amend.");
-
+       if (fixup_message && squash_message)
+               die("Options --squash and --fixup cannot be used together");
        if (use_message)
                f++;
        if (edit_message)
                f++;
+       if (fixup_message)
+               f++;
        if (logfile)
                f++;
        if (f > 1)
-               die("Only one of -c/-C/-F can be used.");
+               die("Only one of -c/-C/-F/--fixup can be used.");
        if (message.len && f > 0)
-               die("Option -m cannot be combined with -c/-C/-F.");
+               die("Option -m cannot be combined with -c/-C/-F/--fixup.");
        if (edit_message)
                use_message = edit_message;
-       if (amend && !use_message)
+       if (amend && !use_message && !fixup_message)
                use_message = "HEAD";
        if (!use_message && renew_authorship)
                die("--reset-author can be used only with -C, -c or --amend.");
        if (use_message) {
-               unsigned char sha1[20];
-               static char utf8[] = "UTF-8";
                const char *out_enc;
-               char *enc, *end;
                struct commit *commit;
 
-               if (get_sha1(use_message, sha1))
+               commit = lookup_commit_reference_by_name(use_message);
+               if (!commit)
                        die("could not lookup commit %s", use_message);
-               commit = lookup_commit_reference(sha1);
-               if (!commit || parse_commit(commit))
-                       die("could not parse commit %s", use_message);
-
-               enc = strstr(commit->buffer, "\nencoding");
-               if (enc) {
-                       end = strchr(enc + 10, '\n');
-                       enc = xstrndup(enc + 10, end - (enc + 10));
-               } else {
-                       enc = utf8;
-               }
-               out_enc = git_commit_encoding ? git_commit_encoding : utf8;
-
-               if (strcmp(out_enc, enc))
-                       use_message_buffer =
-                               reencode_string(commit->buffer, out_enc, enc);
+               out_enc = get_commit_output_encoding();
+               use_message_buffer = logmsg_reencode(commit, out_enc);
 
                /*
                 * If we failed to reencode the buffer, just copy it
@@ -929,8 +958,6 @@ static int parse_and_validate_options(int argc, const char *argv[],
                 */
                if (use_message_buffer == NULL)
                        use_message_buffer = xstrdup(commit->buffer);
-               if (enc != utf8)
-                       free(enc);
        }
 
        if (!!also + !!only + !!all + !!interactive > 1)
@@ -1070,6 +1097,9 @@ int cmd_status(int argc, const char **argv, const char *prefix)
                OPT_END(),
        };
 
+       if (argc == 2 && !strcmp(argv[1], "-h"))
+               usage_with_options(builtin_status_usage, builtin_status_options);
+
        if (null_termination && status_format == STATUS_FORMAT_LONG)
                status_format = STATUS_FORMAT_PORCELAIN;
 
@@ -1255,6 +1285,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
        int allow_fast_forward = 1;
        struct wt_status s;
 
+       if (argc == 2 && !strcmp(argv[1], "-h"))
+               usage_with_options(builtin_commit_usage, builtin_commit_options);
+
        wt_status_prepare(&s);
        git_config(git_commit_config, &s);
        in_merge = file_exists(git_path("MERGE_HEAD"));
index a43d3263638daf083c52f37f3bd0872327d7459b..945e7583a8294f0612682f3228687498e5c61822 100644 (file)
@@ -22,7 +22,7 @@ struct blobinfo {
 };
 
 static const char builtin_diff_usage[] =
-"git diff <options> <rev>{0,2} -- <path>*";
+"git diff [<options>] [<commit> [<commit>]] [--] [<path>...]";
 
 static void stuff_change(struct diff_options *opt,
                         unsigned old_mode, unsigned new_mode,
index 397a1e6eb3ae8ec0e367a8faa2ca823586d26e6a..1a80702b3d1c86b55af24be3b3396f17e9d4a21c 100644 (file)
@@ -189,6 +189,9 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
                OPT_END()
        };
 
+       if (argc == 2 && !strcmp(argv[1], "-h"))
+               usage_with_options(builtin_gc_usage, builtin_gc_options);
+
        git_config(gc_config, NULL);
 
        if (pack_refs < 0)
index adb542494dde32123011b1e76ed164ab39e6e716..fdf7131efd25618250f382dff178ca38f9b42c77 100644 (file)
 #include "grep.h"
 #include "quote.h"
 #include "dir.h"
-
-#ifndef NO_PTHREADS
-#include <pthread.h>
 #include "thread-utils.h"
-#endif
 
 static char const * const grep_usage[] = {
        "git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]",
index d0297a1c5e563bc99cb9cd3d52f7017348980ec9..4191d9c4e55c0745b96e8f93161ea10fb4ec7e0d 100644 (file)
@@ -329,8 +329,7 @@ static void show_tagger(char *buf, int len, struct rev_info *rev)
        struct strbuf out = STRBUF_INIT;
 
        pp_user_info("Tagger", rev->commit_format, &out, buf, rev->date_mode,
-               git_log_output_encoding ?
-               git_log_output_encoding: git_commit_encoding);
+               get_log_output_encoding());
        printf("%s", out.buf);
        strbuf_release(&out);
 }
index 6a307ab784a25c9f8d201392f79c7146a4a8eece..fb2d5f4b1fb0ce9ef2fb0c4099b5fea6d07b6838 100644 (file)
@@ -530,6 +530,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
                OPT_END()
        };
 
+       if (argc == 2 && !strcmp(argv[1], "-h"))
+               usage_with_options(ls_files_usage, builtin_ls_files_options);
+
        memset(&dir, 0, sizeof(dir));
        prefix = cmd_prefix;
        if (prefix)
index 2320d981ceab220e287e42c21f3c9f3be5b3722f..71e6262a87d883a4a82953db5f742bccf99e5c77 100644 (file)
@@ -1032,7 +1032,7 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)
         */
        git_config(git_mailinfo_config, NULL);
 
-       def_charset = (git_commit_encoding ? git_commit_encoding : "UTF-8");
+       def_charset = get_commit_output_encoding();
        metainfo_charset = def_charset;
 
        while (1 < argc && argv[1][0] == '-') {
index c24a7be020d10540581b41c21a2ecf83df5563d9..42fff387e69d9b5412e8e776aed3272b78ebe758 100644 (file)
@@ -57,6 +57,7 @@ static const char *branch;
 static int option_renormalize;
 static int verbosity;
 static int allow_rerere_auto;
+static int abort_current_merge;
 
 static struct strategy all_strategy[] = {
        { "recursive",  DEFAULT_TWOHEAD | NO_TRIVIAL },
@@ -197,6 +198,8 @@ static struct option builtin_merge_options[] = {
                "message to be used for the merge commit (if any)",
                option_parse_message),
        OPT__VERBOSITY(&verbosity),
+       OPT_BOOLEAN(0, "abort", &abort_current_merge,
+               "abort the current in-progress merge"),
        OPT_END()
 };
 
@@ -919,22 +922,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
        const char *best_strategy = NULL, *wt_strategy = NULL;
        struct commit_list **remotes = &remoteheads;
 
-       if (read_cache_unmerged()) {
-               die_resolve_conflict("merge");
-       }
-       if (file_exists(git_path("MERGE_HEAD"))) {
-               /*
-                * There is no unmerged entry, don't advise 'git
-                * add/rm <file>', just 'git commit'.
-                */
-               if (advice_resolve_conflict)
-                       die("You have not concluded your merge (MERGE_HEAD exists).\n"
-                           "Please, commit your changes before you can merge.");
-               else
-                       die("You have not concluded your merge (MERGE_HEAD exists).");
-       }
+       if (argc == 2 && !strcmp(argv[1], "-h"))
+               usage_with_options(builtin_merge_usage, builtin_merge_options);
 
-       resolve_undo_clear();
        /*
         * Check if we are _not_ on a detached HEAD, i.e. if there is a
         * current branch.
@@ -953,6 +943,34 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 
        argc = parse_options(argc, argv, prefix, builtin_merge_options,
                        builtin_merge_usage, 0);
+
+       if (abort_current_merge) {
+               int nargc = 2;
+               const char *nargv[] = {"reset", "--merge", NULL};
+
+               if (!file_exists(git_path("MERGE_HEAD")))
+                       die("There is no merge to abort (MERGE_HEAD missing).");
+
+               /* Invoke 'git reset --merge' */
+               return cmd_reset(nargc, nargv, prefix);
+       }
+
+       if (read_cache_unmerged())
+               die_resolve_conflict("merge");
+
+       if (file_exists(git_path("MERGE_HEAD"))) {
+               /*
+                * There is no unmerged entry, don't advise 'git
+                * add/rm <file>', just 'git commit'.
+                */
+               if (advice_resolve_conflict)
+                       die("You have not concluded your merge (MERGE_HEAD exists).\n"
+                           "Please, commit your changes before you can merge.");
+               else
+                       die("You have not concluded your merge (MERGE_HEAD exists).");
+       }
+       resolve_undo_clear();
+
        if (verbosity < 0)
                show_diffstat = 0;
 
index c85cbf5a4758c8c08ef43dd65d9f85669c0063f2..4d5556e2cb5bccaf0100d9f5019e1a1c493e35e7 100644 (file)
@@ -17,6 +17,7 @@
 #include "run-command.h"
 #include "parse-options.h"
 #include "string-list.h"
+#include "notes-merge.h"
 
 static const char * const git_notes_usage[] = {
        "git notes [--ref <notes_ref>] [list [<object>]]",
@@ -25,8 +26,12 @@ static const char * const git_notes_usage[] = {
        "git notes [--ref <notes_ref>] append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]",
        "git notes [--ref <notes_ref>] edit [<object>]",
        "git notes [--ref <notes_ref>] show [<object>]",
+       "git notes [--ref <notes_ref>] merge [-v | -q] [-s <strategy> ] <notes_ref>",
+       "git notes merge --commit [-v | -q]",
+       "git notes merge --abort [-v | -q]",
        "git notes [--ref <notes_ref>] remove [<object>]",
        "git notes [--ref <notes_ref>] prune [-n | -v]",
+       "git notes [--ref <notes_ref>] get-ref",
        NULL
 };
 
@@ -61,6 +66,13 @@ static const char * const git_notes_show_usage[] = {
        NULL
 };
 
+static const char * const git_notes_merge_usage[] = {
+       "git notes merge [<options>] <notes_ref>",
+       "git notes merge --commit [<options>]",
+       "git notes merge --abort [<options>]",
+       NULL
+};
+
 static const char * const git_notes_remove_usage[] = {
        "git notes remove [<object>]",
        NULL
@@ -71,6 +83,11 @@ static const char * const git_notes_prune_usage[] = {
        NULL
 };
 
+static const char * const git_notes_get_ref_usage[] = {
+       "git notes get-ref",
+       NULL
+};
+
 static const char note_template[] =
        "\n"
        "#\n"
@@ -83,6 +100,16 @@ struct msg_arg {
        struct strbuf buf;
 };
 
+static void expand_notes_ref(struct strbuf *sb)
+{
+       if (!prefixcmp(sb->buf, "refs/notes/"))
+               return; /* we're happy */
+       else if (!prefixcmp(sb->buf, "notes/"))
+               strbuf_insert(sb, 0, "refs/", 5);
+       else
+               strbuf_insert(sb, 0, "refs/notes/", 11);
+}
+
 static int list_each_note(const unsigned char *object_sha1,
                const unsigned char *note_sha1, char *note_path,
                void *cb_data)
@@ -271,18 +298,17 @@ static int parse_reedit_arg(const struct option *opt, const char *arg, int unset
        return parse_reuse_arg(opt, arg, unset);
 }
 
-int commit_notes(struct notes_tree *t, const char *msg)
+void commit_notes(struct notes_tree *t, const char *msg)
 {
-       struct commit_list *parent;
-       unsigned char tree_sha1[20], prev_commit[20], new_commit[20];
        struct strbuf buf = STRBUF_INIT;
+       unsigned char commit_sha1[20];
 
        if (!t)
                t = &default_notes_tree;
        if (!t->initialized || !t->ref || !*t->ref)
                die("Cannot commit uninitialized/unreferenced notes tree");
        if (!t->dirty)
-               return 0; /* don't have to commit an unchanged tree */
+               return; /* don't have to commit an unchanged tree */
 
        /* Prepare commit message and reflog message */
        strbuf_addstr(&buf, "notes: "); /* commit message starts at index 7 */
@@ -290,27 +316,10 @@ int commit_notes(struct notes_tree *t, const char *msg)
        if (buf.buf[buf.len - 1] != '\n')
                strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */
 
-       /* Convert notes tree to tree object */
-       if (write_notes_tree(t, tree_sha1))
-               die("Failed to write current notes tree to database");
-
-       /* Create new commit for the tree object */
-       if (!read_ref(t->ref, prev_commit)) { /* retrieve parent commit */
-               parent = xmalloc(sizeof(*parent));
-               parent->item = lookup_commit(prev_commit);
-               parent->next = NULL;
-       } else {
-               hashclr(prev_commit);
-               parent = NULL;
-       }
-       if (commit_tree(buf.buf + 7, tree_sha1, parent, new_commit, NULL))
-               die("Failed to commit notes tree to database");
-
-       /* Update notes ref with new commit */
-       update_ref(buf.buf, t->ref, new_commit, prev_commit, 0, DIE_ON_ERR);
+       create_notes_commit(t, NULL, buf.buf + 7, commit_sha1);
+       update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR);
 
        strbuf_release(&buf);
-       return 0;
 }
 
 combine_notes_fn parse_combine_notes_fn(const char *v)
@@ -321,6 +330,8 @@ combine_notes_fn parse_combine_notes_fn(const char *v)
                return combine_notes_ignore;
        else if (!strcasecmp(v, "concatenate"))
                return combine_notes_concatenate;
+       else if (!strcasecmp(v, "cat_sort_uniq"))
+               return combine_notes_cat_sort_uniq;
        else
                return NULL;
 }
@@ -573,8 +584,8 @@ static int add(int argc, const char **argv, const char *prefix)
 
        if (is_null_sha1(new_note))
                remove_note(t, object);
-       else
-               add_note(t, object, new_note, combine_notes_overwrite);
+       else if (add_note(t, object, new_note, combine_notes_overwrite))
+               die("BUG: combine_notes_overwrite failed");
 
        snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
                 is_null_sha1(new_note) ? "removed" : "added", "add");
@@ -653,7 +664,8 @@ static int copy(int argc, const char **argv, const char *prefix)
                goto out;
        }
 
-       add_note(t, object, from_note, combine_notes_overwrite);
+       if (add_note(t, object, from_note, combine_notes_overwrite))
+               die("BUG: combine_notes_overwrite failed");
        commit_notes(t, "Notes added by 'git notes copy'");
 out:
        free_notes(t);
@@ -712,8 +724,8 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 
        if (is_null_sha1(new_note))
                remove_note(t, object);
-       else
-               add_note(t, object, new_note, combine_notes_overwrite);
+       else if (add_note(t, object, new_note, combine_notes_overwrite))
+               die("BUG: combine_notes_overwrite failed");
 
        snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
                 is_null_sha1(new_note) ? "removed" : "added", argv[0]);
@@ -761,6 +773,180 @@ static int show(int argc, const char **argv, const char *prefix)
        return retval;
 }
 
+static int merge_abort(struct notes_merge_options *o)
+{
+       int ret = 0;
+
+       /*
+        * Remove .git/NOTES_MERGE_PARTIAL and .git/NOTES_MERGE_REF, and call
+        * notes_merge_abort() to remove .git/NOTES_MERGE_WORKTREE.
+        */
+
+       if (delete_ref("NOTES_MERGE_PARTIAL", NULL, 0))
+               ret += error("Failed to delete ref NOTES_MERGE_PARTIAL");
+       if (delete_ref("NOTES_MERGE_REF", NULL, REF_NODEREF))
+               ret += error("Failed to delete ref NOTES_MERGE_REF");
+       if (notes_merge_abort(o))
+               ret += error("Failed to remove 'git notes merge' worktree");
+       return ret;
+}
+
+static int merge_commit(struct notes_merge_options *o)
+{
+       struct strbuf msg = STRBUF_INIT;
+       unsigned char sha1[20], parent_sha1[20];
+       struct notes_tree *t;
+       struct commit *partial;
+       struct pretty_print_context pretty_ctx;
+
+       /*
+        * Read partial merge result from .git/NOTES_MERGE_PARTIAL,
+        * and target notes ref from .git/NOTES_MERGE_REF.
+        */
+
+       if (get_sha1("NOTES_MERGE_PARTIAL", sha1))
+               die("Failed to read ref NOTES_MERGE_PARTIAL");
+       else if (!(partial = lookup_commit_reference(sha1)))
+               die("Could not find commit from NOTES_MERGE_PARTIAL.");
+       else if (parse_commit(partial))
+               die("Could not parse commit from NOTES_MERGE_PARTIAL.");
+
+       if (partial->parents)
+               hashcpy(parent_sha1, partial->parents->item->object.sha1);
+       else
+               hashclr(parent_sha1);
+
+       t = xcalloc(1, sizeof(struct notes_tree));
+       init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0);
+
+       o->local_ref = resolve_ref("NOTES_MERGE_REF", sha1, 0, 0);
+       if (!o->local_ref)
+               die("Failed to resolve NOTES_MERGE_REF");
+
+       if (notes_merge_commit(o, t, partial, sha1))
+               die("Failed to finalize notes merge");
+
+       /* Reuse existing commit message in reflog message */
+       memset(&pretty_ctx, 0, sizeof(pretty_ctx));
+       format_commit_message(partial, "%s", &msg, &pretty_ctx);
+       strbuf_trim(&msg);
+       strbuf_insert(&msg, 0, "notes: ", 7);
+       update_ref(msg.buf, o->local_ref, sha1,
+                  is_null_sha1(parent_sha1) ? NULL : parent_sha1,
+                  0, DIE_ON_ERR);
+
+       free_notes(t);
+       strbuf_release(&msg);
+       return merge_abort(o);
+}
+
+static int merge(int argc, const char **argv, const char *prefix)
+{
+       struct strbuf remote_ref = STRBUF_INIT, msg = STRBUF_INIT;
+       unsigned char result_sha1[20];
+       struct notes_tree *t;
+       struct notes_merge_options o;
+       int do_merge = 0, do_commit = 0, do_abort = 0;
+       int verbosity = 0, result;
+       const char *strategy = NULL;
+       struct option options[] = {
+               OPT_GROUP("General options"),
+               OPT__VERBOSITY(&verbosity),
+               OPT_GROUP("Merge options"),
+               OPT_STRING('s', "strategy", &strategy, "strategy",
+                          "resolve notes conflicts using the given strategy "
+                          "(manual/ours/theirs/union/cat_sort_uniq)"),
+               OPT_GROUP("Committing unmerged notes"),
+               { OPTION_BOOLEAN, 0, "commit", &do_commit, NULL,
+                       "finalize notes merge by committing unmerged notes",
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG },
+               OPT_GROUP("Aborting notes merge resolution"),
+               { OPTION_BOOLEAN, 0, "abort", &do_abort, NULL,
+                       "abort notes merge",
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG },
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, prefix, options,
+                            git_notes_merge_usage, 0);
+
+       if (strategy || do_commit + do_abort == 0)
+               do_merge = 1;
+       if (do_merge + do_commit + do_abort != 1) {
+               error("cannot mix --commit, --abort or -s/--strategy");
+               usage_with_options(git_notes_merge_usage, options);
+       }
+
+       if (do_merge && argc != 1) {
+               error("Must specify a notes ref to merge");
+               usage_with_options(git_notes_merge_usage, options);
+       } else if (!do_merge && argc) {
+               error("too many parameters");
+               usage_with_options(git_notes_merge_usage, options);
+       }
+
+       init_notes_merge_options(&o);
+       o.verbosity = verbosity + NOTES_MERGE_VERBOSITY_DEFAULT;
+
+       if (do_abort)
+               return merge_abort(&o);
+       if (do_commit)
+               return merge_commit(&o);
+
+       o.local_ref = default_notes_ref();
+       strbuf_addstr(&remote_ref, argv[0]);
+       expand_notes_ref(&remote_ref);
+       o.remote_ref = remote_ref.buf;
+
+       if (strategy) {
+               if (!strcmp(strategy, "manual"))
+                       o.strategy = NOTES_MERGE_RESOLVE_MANUAL;
+               else if (!strcmp(strategy, "ours"))
+                       o.strategy = NOTES_MERGE_RESOLVE_OURS;
+               else if (!strcmp(strategy, "theirs"))
+                       o.strategy = NOTES_MERGE_RESOLVE_THEIRS;
+               else if (!strcmp(strategy, "union"))
+                       o.strategy = NOTES_MERGE_RESOLVE_UNION;
+               else if (!strcmp(strategy, "cat_sort_uniq"))
+                       o.strategy = NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ;
+               else {
+                       error("Unknown -s/--strategy: %s", strategy);
+                       usage_with_options(git_notes_merge_usage, options);
+               }
+       }
+
+       t = init_notes_check("merge");
+
+       strbuf_addf(&msg, "notes: Merged notes from %s into %s",
+                   remote_ref.buf, default_notes_ref());
+       strbuf_add(&(o.commit_msg), msg.buf + 7, msg.len - 7); /* skip "notes: " */
+
+       result = notes_merge(&o, t, result_sha1);
+
+       if (result >= 0) /* Merge resulted (trivially) in result_sha1 */
+               /* Update default notes ref with new commit */
+               update_ref(msg.buf, default_notes_ref(), result_sha1, NULL,
+                          0, DIE_ON_ERR);
+       else { /* Merge has unresolved conflicts */
+               /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */
+               update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL,
+                          0, DIE_ON_ERR);
+               /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
+               if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
+                       die("Failed to store link to current notes ref (%s)",
+                           default_notes_ref());
+               printf("Automatic notes merge failed. Fix conflicts in %s and "
+                      "commit the result with 'git notes merge --commit', or "
+                      "abort the merge with 'git notes merge --abort'.\n",
+                      git_path(NOTES_MERGE_WORKTREE));
+       }
+
+       free_notes(t);
+       strbuf_release(&remote_ref);
+       strbuf_release(&msg);
+       return result < 0; /* return non-zero on conflicts */
+}
+
 static int remove_cmd(int argc, const char **argv, const char *prefix)
 {
        struct option options[] = {
@@ -827,6 +1013,21 @@ static int prune(int argc, const char **argv, const char *prefix)
        return 0;
 }
 
+static int get_ref(int argc, const char **argv, const char *prefix)
+{
+       struct option options[] = { OPT_END() };
+       argc = parse_options(argc, argv, prefix, options,
+                            git_notes_get_ref_usage, 0);
+
+       if (argc) {
+               error("too many parameters");
+               usage_with_options(git_notes_get_ref_usage, options);
+       }
+
+       puts(default_notes_ref());
+       return 0;
+}
+
 int cmd_notes(int argc, const char **argv, const char *prefix)
 {
        int result;
@@ -843,13 +1044,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 
        if (override_notes_ref) {
                struct strbuf sb = STRBUF_INIT;
-               if (!prefixcmp(override_notes_ref, "refs/notes/"))
-                       /* we're happy */;
-               else if (!prefixcmp(override_notes_ref, "notes/"))
-                       strbuf_addstr(&sb, "refs/");
-               else
-                       strbuf_addstr(&sb, "refs/notes/");
                strbuf_addstr(&sb, override_notes_ref);
+               expand_notes_ref(&sb);
                setenv("GIT_NOTES_REF", sb.buf, 1);
                strbuf_release(&sb);
        }
@@ -864,10 +1060,14 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
                result = append_edit(argc, argv, prefix);
        else if (!strcmp(argv[0], "show"))
                result = show(argc, argv, prefix);
+       else if (!strcmp(argv[0], "merge"))
+               result = merge(argc, argv, prefix);
        else if (!strcmp(argv[0], "remove"))
                result = remove_cmd(argc, argv, prefix);
        else if (!strcmp(argv[0], "prune"))
                result = prune(argc, argv, prefix);
+       else if (!strcmp(argv[0], "get-ref"))
+               result = get_ref(argc, argv, prefix);
        else {
                result = error("Unknown subcommand: %s", argv[0]);
                usage_with_options(git_notes_usage, options);
index f8eba53c82242d0c435524ab9ffab70ba2e2d5ad..b0503b202afb4356caaca794518fb4e21082a48f 100644 (file)
 #include "list-objects.h"
 #include "progress.h"
 #include "refs.h"
-
-#ifndef NO_PTHREADS
-#include <pthread.h>
 #include "thread-utils.h"
-#endif
 
 static const char pack_usage[] =
   "git pack-objects [ -q | --progress | --all-progress ]\n"
@@ -1298,9 +1294,23 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
                read_lock();
                src->data = read_sha1_file(src_entry->idx.sha1, &type, &sz);
                read_unlock();
-               if (!src->data)
+               if (!src->data) {
+                       if (src_entry->preferred_base) {
+                               static int warned = 0;
+                               if (!warned++)
+                                       warning("object %s cannot be read",
+                                               sha1_to_hex(src_entry->idx.sha1));
+                               /*
+                                * Those objects are not included in the
+                                * resulting pack.  Be resilient and ignore
+                                * them if they can't be read, in case the
+                                * pack could be created nevertheless.
+                                */
+                               return 0;
+                       }
                        die("object %s cannot be read",
                            sha1_to_hex(src_entry->idx.sha1));
+               }
                if (sz != src_size)
                        die("object %s inconsistent object length (%lu vs %lu)",
                            sha1_to_hex(src_entry->idx.sha1), sz, src_size);
@@ -1529,7 +1539,7 @@ static void try_to_free_from_threads(size_t size)
        read_unlock();
 }
 
-try_to_free_t old_try_to_free_routine;
+static try_to_free_t old_try_to_free_routine;
 
 /*
  * The main thread waits on the condition that (at least) one of the workers
diff --git a/builtin/remote-ext.c b/builtin/remote-ext.c
new file mode 100644 (file)
index 0000000..1f77317
--- /dev/null
@@ -0,0 +1,246 @@
+#include "git-compat-util.h"
+#include "transport.h"
+#include "run-command.h"
+
+/*
+ * URL syntax:
+ *     'command [arg1 [arg2 [...]]]'   Invoke command with given arguments.
+ *     Special characters:
+ *     '% ': Literal space in argument.
+ *     '%%': Literal percent sign.
+ *     '%S': Name of service (git-upload-pack/git-upload-archive/
+ *             git-receive-pack.
+ *     '%s': Same as \s, but with possible git- prefix stripped.
+ *     '%G': Only allowed as first 'character' of argument. Do not pass this
+ *             Argument to command, instead send this as name of repository
+ *             in in-line git://-style request (also activates sending this
+ *             style of request).
+ *     '%V': Only allowed as first 'character' of argument. Used in
+ *             conjunction with '%G': Do not pass this argument to command,
+ *             instead send this as vhost in git://-style request (note: does
+ *             not activate sending git:// style request).
+ */
+
+static char *git_req;
+static char *git_req_vhost;
+
+static char *strip_escapes(const char *str, const char *service,
+       const char **next)
+{
+       size_t rpos = 0;
+       int escape = 0;
+       char special = 0;
+       size_t pslen = 0;
+       size_t pSlen = 0;
+       size_t psoff = 0;
+       struct strbuf ret = STRBUF_INIT;
+
+       /* Calculate prefix length for \s and lengths for \s and \S */
+       if (!strncmp(service, "git-", 4))
+               psoff = 4;
+       pSlen = strlen(service);
+       pslen = pSlen - psoff;
+
+       /* Pass the service to command. */
+       setenv("GIT_EXT_SERVICE", service, 1);
+       setenv("GIT_EXT_SERVICE_NOPREFIX", service + psoff, 1);
+
+       /* Scan the length of argument. */
+       while (str[rpos] && (escape || str[rpos] != ' ')) {
+               if (escape) {
+                       switch (str[rpos]) {
+                       case ' ':
+                       case '%':
+                       case 's':
+                       case 'S':
+                               break;
+                       case 'G':
+                       case 'V':
+                               special = str[rpos];
+                               if (rpos == 1)
+                                       break;
+                               /* Fall-through to error. */
+                       default:
+                               die("Bad remote-ext placeholder '%%%c'.",
+                                       str[rpos]);
+                       }
+                       escape = 0;
+               } else
+                       escape = (str[rpos] == '%');
+               rpos++;
+       }
+       if (escape && !str[rpos])
+               die("remote-ext command has incomplete placeholder");
+       *next = str + rpos;
+       if (**next == ' ')
+               ++*next;        /* Skip over space */
+
+       /*
+        * Do the actual placeholder substitution. The string will be short
+        * enough not to overflow integers.
+        */
+       rpos = special ? 2 : 0;         /* Skip first 2 bytes in specials. */
+       escape = 0;
+       while (str[rpos] && (escape || str[rpos] != ' ')) {
+               if (escape) {
+                       switch (str[rpos]) {
+                       case ' ':
+                       case '%':
+                               strbuf_addch(&ret, str[rpos]);
+                               break;
+                       case 's':
+                               strbuf_addstr(&ret, service + psoff);
+                               break;
+                       case 'S':
+                               strbuf_addstr(&ret, service);
+                               break;
+                       }
+                       escape = 0;
+               } else
+                       switch (str[rpos]) {
+                       case '%':
+                               escape = 1;
+                               break;
+                       default:
+                               strbuf_addch(&ret, str[rpos]);
+                               break;
+                       }
+               rpos++;
+       }
+       switch (special) {
+       case 'G':
+               git_req = strbuf_detach(&ret, NULL);
+               return NULL;
+       case 'V':
+               git_req_vhost = strbuf_detach(&ret, NULL);
+               return NULL;
+       default:
+               return strbuf_detach(&ret, NULL);
+       }
+}
+
+/* Should be enough... */
+#define MAXARGUMENTS 256
+
+static const char **parse_argv(const char *arg, const char *service)
+{
+       int arguments = 0;
+       int i;
+       const char **ret;
+       char *temparray[MAXARGUMENTS + 1];
+
+       while (*arg) {
+               char *expanded;
+               if (arguments == MAXARGUMENTS)
+                       die("remote-ext command has too many arguments");
+               expanded = strip_escapes(arg, service, &arg);
+               if (expanded)
+                       temparray[arguments++] = expanded;
+       }
+
+       ret = xmalloc((arguments + 1) * sizeof(char *));
+       for (i = 0; i < arguments; i++)
+               ret[i] = temparray[i];
+       ret[arguments] = NULL;
+       return ret;
+}
+
+static void send_git_request(int stdin_fd, const char *serv, const char *repo,
+       const char *vhost)
+{
+       size_t bufferspace;
+       size_t wpos = 0;
+       char *buffer;
+
+       /*
+        * Request needs 12 bytes extra if there is vhost (xxxx \0host=\0) and
+        * 6 bytes extra (xxxx \0) if there is no vhost.
+        */
+       if (vhost)
+               bufferspace = strlen(serv) + strlen(repo) + strlen(vhost) + 12;
+       else
+               bufferspace = strlen(serv) + strlen(repo) + 6;
+
+       if (bufferspace > 0xFFFF)
+               die("Request too large to send");
+       buffer = xmalloc(bufferspace);
+
+       /* Make the packet. */
+       wpos = sprintf(buffer, "%04x%s %s%c", (unsigned)bufferspace,
+               serv, repo, 0);
+
+       /* Add vhost if any. */
+       if (vhost)
+               sprintf(buffer + wpos, "host=%s%c", vhost, 0);
+
+       /* Send the request */
+       if (write_in_full(stdin_fd, buffer, bufferspace) < 0)
+               die_errno("Failed to send request");
+
+       free(buffer);
+}
+
+static int run_child(const char *arg, const char *service)
+{
+       int r;
+       struct child_process child;
+
+       memset(&child, 0, sizeof(child));
+       child.in = -1;
+       child.out = -1;
+       child.err = 0;
+       child.argv = parse_argv(arg, service);
+
+       if (start_command(&child) < 0)
+               die("Can't run specified command");
+
+       if (git_req)
+               send_git_request(child.in, service, git_req, git_req_vhost);
+
+       r = bidirectional_transfer_loop(child.out, child.in);
+       if (!r)
+               r = finish_command(&child);
+       else
+               finish_command(&child);
+       return r;
+}
+
+#define MAXCOMMAND 4096
+
+static int command_loop(const char *child)
+{
+       char buffer[MAXCOMMAND];
+
+       while (1) {
+               size_t length;
+               if (!fgets(buffer, MAXCOMMAND - 1, stdin)) {
+                       if (ferror(stdin))
+                               die("Comammand input error");
+                       exit(0);
+               }
+               /* Strip end of line characters. */
+               length = strlen(buffer);
+               while (isspace((unsigned char)buffer[length - 1]))
+                       buffer[--length] = 0;
+
+               if (!strcmp(buffer, "capabilities")) {
+                       printf("*connect\n\n");
+                       fflush(stdout);
+               } else if (!strncmp(buffer, "connect ", 8)) {
+                       printf("\n");
+                       fflush(stdout);
+                       return run_child(child, buffer + 8);
+               } else {
+                       fprintf(stderr, "Bad command");
+                       return 1;
+               }
+       }
+}
+
+int cmd_remote_ext(int argc, const char **argv, const char *prefix)
+{
+       if (argc != 3)
+               die("Expected two arguments");
+
+       return command_loop(argv[2]);
+}
diff --git a/builtin/remote-fd.c b/builtin/remote-fd.c
new file mode 100644 (file)
index 0000000..1f2467b
--- /dev/null
@@ -0,0 +1,79 @@
+#include "git-compat-util.h"
+#include "transport.h"
+
+/*
+ * URL syntax:
+ *     'fd::<inoutfd>[/<anything>]'            Read/write socket pair
+ *                                             <inoutfd>.
+ *     'fd::<infd>,<outfd>[/<anything>]'       Read pipe <infd> and write
+ *                                             pipe <outfd>.
+ *     [foo] indicates 'foo' is optional. <anything> is any string.
+ *
+ * The data output to <outfd>/<inoutfd> should be passed unmolested to
+ * git-receive-pack/git-upload-pack/git-upload-archive and output of
+ * git-receive-pack/git-upload-pack/git-upload-archive should be passed
+ * unmolested to <infd>/<inoutfd>.
+ *
+ */
+
+#define MAXCOMMAND 4096
+
+static void command_loop(int input_fd, int output_fd)
+{
+       char buffer[MAXCOMMAND];
+
+       while (1) {
+               size_t i;
+               if (!fgets(buffer, MAXCOMMAND - 1, stdin)) {
+                       if (ferror(stdin))
+                               die("Input error");
+                       return;
+               }
+               /* Strip end of line characters. */
+               i = strlen(buffer);
+               while (i > 0 && isspace(buffer[i - 1]))
+                       buffer[--i] = 0;
+
+               if (!strcmp(buffer, "capabilities")) {
+                       printf("*connect\n\n");
+                       fflush(stdout);
+               } else if (!strncmp(buffer, "connect ", 8)) {
+                       printf("\n");
+                       fflush(stdout);
+                       if (bidirectional_transfer_loop(input_fd,
+                               output_fd))
+                               die("Copying data between file descriptors failed");
+                       return;
+               } else {
+                       die("Bad command: %s", buffer);
+               }
+       }
+}
+
+int cmd_remote_fd(int argc, const char **argv, const char *prefix)
+{
+       int input_fd = -1;
+       int output_fd = -1;
+       char *end;
+
+       if (argc != 3)
+               die("Expected two arguments");
+
+       input_fd = (int)strtoul(argv[2], &end, 10);
+
+       if ((end == argv[2]) || (*end != ',' && *end != '/' && *end))
+               die("Bad URL syntax");
+
+       if (*end == '/' || !*end) {
+               output_fd = input_fd;
+       } else {
+               char *end2;
+               output_fd = (int)strtoul(end + 1, &end2, 10);
+
+               if ((end2 == end + 1) || (*end2 != '/' && *end2))
+                       die("Bad URL syntax");
+       }
+
+       command_loop(input_fd, output_fd);
+       return 0;
+}
index 57b51e4a0e148062e1945d640b81559d65e4c2ba..bb6e9e83b756b47dae449064ca7762fe3558fc0e 100644 (file)
@@ -547,6 +547,21 @@ static void prepare_revs(struct rev_info *revs)
                die("empty commit set passed");
 }
 
+static void read_and_refresh_cache(const char *me)
+{
+       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);
+       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);
+       }
+       rollback_lock_file(&index_lock);
+}
+
 static int revert_or_cherry_pick(int argc, const char **argv)
 {
        struct rev_info revs;
@@ -567,8 +582,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
                        die("cherry-pick --ff cannot be used with --edit");
        }
 
-       if (read_cache() < 0)
-               die("git %s: failed to read the index", me);
+       read_and_refresh_cache(me);
 
        prepare_revs(&revs);
 
index d1d7d8701db23dfa0db0fb3457f57826b7012d41..aa1f87d47a3e4bdaa2ca3711c9dfc495fc2247e0 100644 (file)
@@ -29,8 +29,6 @@ struct tag_filter {
        struct commit_list *with_commit;
 };
 
-#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
-
 static int show_reference(const char *refname, const unsigned char *sha1,
                          int flag, void *cb_data)
 {
@@ -70,9 +68,9 @@ static int show_reference(const char *refname, const unsigned char *sha1,
                        return 0;
                }
                /* only take up to "lines" lines, and strip the signature */
+               size = parse_signature(buf, size);
                for (i = 0, sp += 2;
-                               i < filter->lines && sp < buf + size &&
-                               prefixcmp(sp, PGP_SIGNATURE "\n");
+                               i < filter->lines && sp < buf + size;
                                i++) {
                        if (i)
                                printf("\n    ");
@@ -242,8 +240,7 @@ static void write_tag_body(int fd, const unsigned char *sha1)
 {
        unsigned long size;
        enum object_type type;
-       char *buf, *sp, *eob;
-       size_t len;
+       char *buf, *sp;
 
        buf = read_sha1_file(sha1, &type, &size);
        if (!buf)
@@ -256,12 +253,7 @@ static void write_tag_body(int fd, const unsigned char *sha1)
                return;
        }
        sp += 2; /* skip the 2 LFs */
-       eob = strstr(sp, "\n" PGP_SIGNATURE "\n");
-       if (eob)
-               len = eob - sp;
-       else
-               len = buf + size - sp;
-       write_or_die(fd, sp, len);
+       write_or_die(fd, sp, parse_signature(sp, buf + size - sp));
 
        free(buf);
 }
index 62d9f3f0fa358058fcf54e4fcbd4c3101b1279db..200c7efed18ebba7088f2b56d942084be280a578 100644 (file)
@@ -589,6 +589,9 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
        int lock_error = 0;
        struct lock_file *lock_file;
 
+       if (argc == 2 && !strcmp(argv[1], "-h"))
+               usage(update_index_usage);
+
        git_config(git_default_config, NULL);
 
        /* We can't free this memory, it becomes part of a linked list parsed atexit() */
index 8136dba7a1a43bf90cd213a30a7f1a57a1248ac3..313476604967bb2962350c82d45ffad04eab09e1 100644 (file)
@@ -17,13 +17,11 @@ static const char * const verify_tag_usage[] = {
                NULL
 };
 
-#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
-
 static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
 {
        struct child_process gpg;
        const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL};
-       char path[PATH_MAX], *eol;
+       char path[PATH_MAX];
        size_t len;
        int fd, ret;
 
@@ -37,11 +35,7 @@ static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
        close(fd);
 
        /* find the length without signature */
-       len = 0;
-       while (len < size && prefixcmp(buf + len, PGP_SIGNATURE)) {
-               eol = memchr(buf + len, '\n', size - len);
-               len += eol ? eol - (buf + len) + 1 : size - len;
-       }
+       len = parse_signature(buf, size);
        if (verbose)
                write_in_full(1, buf, len);
 
diff --git a/cache.h b/cache.h
index 33decd942d4985c8efc1c75fd3fa2f4adf4a56ca..e83bc2d3bb20287b42be06edd56dfd525ddfbaa9 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -545,6 +545,7 @@ extern int assume_unchanged;
 extern int prefer_symlink_refs;
 extern int log_all_ref_updates;
 extern int warn_ambiguous_refs;
+extern int unique_abbrev_extra_length;
 extern int shared_repository;
 extern const char *apply_default_whitespace;
 extern const char *apply_default_ignorewhitespace;
@@ -859,7 +860,7 @@ struct cache_def {
 
 extern int has_symlink_leading_path(const char *name, int len);
 extern int threaded_has_symlink_leading_path(struct cache_def *, const char *, int);
-extern int has_symlink_or_noent_leading_path(const char *name, int len);
+extern int check_leading_path(const char *name, int len);
 extern int has_dirs_only_path(const char *name, int len, int prefix_len);
 extern void schedule_dir_for_removal(const char *name, int len);
 extern void remove_scheduled_dirs(void);
@@ -1003,6 +1004,9 @@ extern int git_env_bool(const char *, int);
 extern int git_config_system(void);
 extern int git_config_global(void);
 extern int config_error_nonbool(const char *);
+extern const char *get_log_output_encoding(void);
+extern const char *get_commit_output_encoding(void);
+
 extern const char *config_exclusive_filename;
 
 #define MAX_GITNAME (1000)
index 0094ec1c9278332f736d2814c847b272788a7dd3..b21335ee4c775d82901fb6b7dc785d63244f2bdd 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -49,6 +49,19 @@ struct commit *lookup_commit(const unsigned char *sha1)
        return check_commit(obj, sha1, 0);
 }
 
+struct commit *lookup_commit_reference_by_name(const char *name)
+{
+       unsigned char sha1[20];
+       struct commit *commit;
+
+       if (get_sha1(name, sha1))
+               return NULL;
+       commit = lookup_commit_reference(sha1);
+       if (!commit || parse_commit(commit))
+               return NULL;
+       return commit;
+}
+
 static unsigned long parse_commit_date(const char *buf, const char *tail)
 {
        const char *dateptr;
@@ -137,12 +150,8 @@ struct commit_graft *read_graft_line(char *buf, int len)
                buf[--len] = '\0';
        if (buf[0] == '#' || buf[0] == '\0')
                return NULL;
-       if ((len + 1) % 41) {
-       bad_graft_data:
-               error("bad graft data: %s", buf);
-               free(graft);
-               return NULL;
-       }
+       if ((len + 1) % 41)
+               goto bad_graft_data;
        i = (len + 1) / 41 - 1;
        graft = xmalloc(sizeof(*graft) + 20 * i);
        graft->nr_parent = i;
@@ -155,6 +164,11 @@ struct commit_graft *read_graft_line(char *buf, int len)
                        goto bad_graft_data;
        }
        return graft;
+
+bad_graft_data:
+       error("bad graft data: %s", buf);
+       free(graft);
+       return NULL;
 }
 
 static int read_graft_file(const char *graft_file)
index 9113bbe4889d71e824348edcb920110598db18d2..3bfb31b5e0faad6ea04fde9e2f932f54d2e6c235 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -36,6 +36,7 @@ struct commit *lookup_commit(const unsigned char *sha1);
 struct commit *lookup_commit_reference(const unsigned char *sha1);
 struct commit *lookup_commit_reference_gently(const unsigned char *sha1,
                                              int quiet);
+struct commit *lookup_commit_reference_by_name(const char *name);
 
 int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size);
 
@@ -76,6 +77,7 @@ struct pretty_print_context
        int need_8bit_cte;
        int show_notes;
        struct reflog_walk_info *reflog_info;
+       const char *output_encoding;
 };
 
 struct userformat_want {
@@ -84,6 +86,8 @@ struct userformat_want {
 
 extern int has_non_ascii(const char *text);
 struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */
+extern char *logmsg_reencode(const struct commit *commit,
+                            const char *output_encoding);
 extern char *reencode_commit_message(const struct commit *commit,
                                     const char **encoding_p);
 extern void get_commit_format(const char *arg, struct rev_info *);
index 99a746703f3c9fb19e043109e4a044efe151e0e7..35d9813b6badb34068601f35302f8859a786db05 100644 (file)
@@ -37,6 +37,9 @@ typedef int socklen_t;
 #define WEXITSTATUS(x) ((x) & 0xff)
 #define WTERMSIG(x) SIGTERM
 
+#define EWOULDBLOCK EAGAIN
+#define SHUT_WR SD_SEND
+
 #define SIGHUP 1
 #define SIGQUIT 3
 #define SIGKILL 9
index c63d6834e087bbe2469cb285d3b3de475b76b972..32c0b2c41e01a754060e73bbf20a7f0e9c43bb10 100644 (file)
--- a/config.c
+++ b/config.c
@@ -410,7 +410,7 @@ unsigned long git_config_ulong(const char *name, const char *value)
        return ret;
 }
 
-int git_config_maybe_bool(const char *name, const char *value)
+static int git_config_maybe_bool_text(const char *name, const char *value)
 {
        if (!value)
                return 1;
@@ -427,9 +427,21 @@ int git_config_maybe_bool(const char *name, const char *value)
        return -1;
 }
 
+int git_config_maybe_bool(const char *name, const char *value)
+{
+       int v = git_config_maybe_bool_text(name, value);
+       if (0 <= v)
+               return v;
+       if (!strcmp(value, "0"))
+               return 0;
+       if (!strcmp(value, "1"))
+               return 1;
+       return -1;
+}
+
 int git_config_bool_or_int(const char *name, const char *value, int *is_bool)
 {
-       int v = git_config_maybe_bool(name, value);
+       int v = git_config_maybe_bool_text(name, value);
        if (0 <= v) {
                *is_bool = 1;
                return v;
@@ -489,6 +501,13 @@ static int git_default_core_config(const char *var, const char *value)
                return 0;
        }
 
+       if (!strcmp(var, "core.abbrevguard")) {
+               unique_abbrev_extra_length = git_config_int(var, value);
+               if (unique_abbrev_extra_length < 0)
+                       unique_abbrev_extra_length = 0;
+               return 0;
+       }
+
        if (!strcmp(var, "core.bare")) {
                is_bare_repository_cfg = git_config_bool(var, value);
                return 0;
index a0c34eec15773f0f3e4d2b4e12f0c76e722ede95..56343bab505b59289d0a5334a2a6ac840d3f10d1 100644 (file)
@@ -47,6 +47,8 @@ NO_C99_FORMAT=@NO_C99_FORMAT@
 NO_HSTRERROR=@NO_HSTRERROR@
 NO_STRCASESTR=@NO_STRCASESTR@
 NO_STRTOK_R=@NO_STRTOK_R@
+NO_FNMATCH=@NO_FNMATCH@
+NO_FNMATCH_CASEFOLD=@NO_FNMATCH_CASEFOLD@
 NO_MEMMEM=@NO_MEMMEM@
 NO_STRLCPY=@NO_STRLCPY@
 NO_UINTMAX_T=@NO_UINTMAX_T@
index cc55b6d4f741948f3bd05c6299fabdb9f21d7f95..33dd46262bfb7e6d9db80446fa06ff0ba819f5df 100644 (file)
@@ -617,6 +617,18 @@ AC_CHECK_HEADER([sys/select.h],
 [NO_SYS_SELECT_H=UnfortunatelyYes])
 AC_SUBST(NO_SYS_SELECT_H)
 #
+# Define NO_SYS_POLL_H if you don't have sys/poll.h
+AC_CHECK_HEADER([sys/poll.h],
+[NO_SYS_POLL_H=],
+[NO_SYS_POLL_H=UnfortunatelyYes])
+AC_SUBST(NO_SYS_POLL_H)
+#
+# Define NO_INTTYPES_H if you don't have inttypes.h
+AC_CHECK_HEADER([inttypes.h],
+[NO_INTTYPES_H=],
+[NO_INTTYPES_H=UnfortunatelyYes])
+AC_SUBST(NO_INTTYPES_H)
+#
 # Define OLD_ICONV if your library has an old iconv(), where the second
 # (input buffer pointer) parameter is declared with type (const char **).
 AC_DEFUN([OLDICONVTEST_SRC], [[
@@ -818,6 +830,34 @@ GIT_CHECK_FUNC(strtok_r,
 [NO_STRTOK_R=YesPlease])
 AC_SUBST(NO_STRTOK_R)
 #
+# Define NO_FNMATCH if you don't have fnmatch
+GIT_CHECK_FUNC(fnmatch,
+[NO_FNMATCH=],
+[NO_FNMATCH=YesPlease])
+AC_SUBST(NO_FNMATCH)
+#
+# Define NO_FNMATCH_CASEFOLD if your fnmatch function doesn't have the
+# FNM_CASEFOLD GNU extension.
+AC_CACHE_CHECK([whether the fnmatch function supports the FNMATCH_CASEFOLD GNU extension],
+ [ac_cv_c_excellent_fnmatch], [
+AC_EGREP_CPP(yippeeyeswehaveit,
+       AC_LANG_PROGRAM([
+#include <fnmatch.h>
+],
+[#ifdef FNM_CASEFOLD
+yippeeyeswehaveit
+#endif
+]),
+       [ac_cv_c_excellent_fnmatch=yes],
+       [ac_cv_c_excellent_fnmatch=no])
+])
+if test $ac_cv_c_excellent_fnmatch = yes; then
+       NO_FNMATCH_CASEFOLD=
+else
+       NO_FNMATCH_CASEFOLD=YesPlease
+fi
+AC_SUBST(NO_FNMATCH_CASEFOLD)
+#
 # Define NO_MEMMEM if you don't have memmem.
 GIT_CHECK_FUNC(memmem,
 [NO_MEMMEM=],
@@ -868,6 +908,12 @@ GIT_CHECK_FUNC(mkstemps,
 [NO_MKSTEMPS=YesPlease])
 AC_SUBST(NO_MKSTEMPS)
 #
+# Define NO_INITGROUPS if you don't have initgroups in the C library.
+GIT_CHECK_FUNC(initgroups,
+[NO_INITGROUPS=],
+[NO_INITGROUPS=YesPlease])
+AC_SUBST(NO_INITGROUPS)
+#
 #
 # Define NO_MMAP if you want to avoid mmap.
 #
index f71046947febee2cacfd2a9ed2e0746582a5e54f..604fa794cc4d2dd657f054f42e3001bee13b8202 100755 (executable)
@@ -261,7 +261,7 @@ __git_ps1 ()
                                (describe)
                                        git describe HEAD ;;
                                (* | default)
-                                       git describe --exact-match HEAD ;;
+                                       git describe --tags --exact-match HEAD ;;
                                esac 2>/dev/null)" ||
 
                                b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." ||
index 85724bfc08f61d3b928460a8d2ad5ec03cf80b52..f99ea9585014face001849aeeefa5f20e79bc1e2 100755 (executable)
@@ -144,13 +144,13 @@ prep_for_email()
                        short_refname=${refname##refs/remotes/}
                        echo >&2 "*** Push-update of tracking branch, $refname"
                        echo >&2 "***  - no email generated."
-                       exit 0
+                       return 1
                        ;;
                *)
                        # Anything else (is there anything else?)
                        echo >&2 "*** Unknown type of update to $refname ($rev_type)"
                        echo >&2 "***  - no email generated"
-                       return 0
+                       return 1
                        ;;
        esac
 
@@ -166,10 +166,10 @@ prep_for_email()
                esac
                echo >&2 "*** $config_name is not set so no email will be sent"
                echo >&2 "*** for $refname update $oldrev->$newrev"
-               return 0
+               return 1
        fi
 
-       return 1
+       return 0
 }
 
 #
index 13435b46674a3a7b123597ab11751459173f3325..d2a4e023e895d57db798be76450b77afb2ac49be 100644 (file)
--- a/daemon.c
+++ b/daemon.c
 #define NI_MAXSERV 32
 #endif
 
+#ifdef NO_INITGROUPS
+#define initgroups(x, y) (0) /* nothing */
+#endif
+
 static int log_syslog;
 static int verbose;
 static int reuseaddr;
diff --git a/diff.c b/diff.c
index c248bc64c57b3206905dd8e6f72a702073a137cc..6991ed4e17343a3104d3fd518b5d85d0913e9259 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -2158,7 +2158,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
 
                        ecbdata.ws_rule = data.ws_rule;
                        check_blank_at_eof(&mf1, &mf2, &ecbdata);
-                       blank_at_eof = ecbdata.blank_at_eof_in_preimage;
+                       blank_at_eof = ecbdata.blank_at_eof_in_postimage;
 
                        if (blank_at_eof) {
                                static char *err;
@@ -2391,10 +2391,14 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
        }
        else {
                enum object_type type;
-               if (size_only)
+               if (size_only) {
                        type = sha1_object_info(s->sha1, &s->size);
-               else {
+                       if (type < 0)
+                               die("unable to read %s", sha1_to_hex(s->sha1));
+               } else {
                        s->data = read_sha1_file(s->sha1, &type, &s->size);
+                       if (!s->data)
+                               die("unable to read %s", sha1_to_hex(s->sha1));
                        s->should_free = 1;
                }
        }
@@ -3148,12 +3152,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (!prefixcmp(arg, "-B") || !prefixcmp(arg, "--break-rewrites=") ||
                 !strcmp(arg, "--break-rewrites")) {
                if ((options->break_opt = diff_scoreopt_parse(arg)) == -1)
-                       return -1;
+                       return error("invalid argument to -B: %s", arg+2);
        }
        else if (!prefixcmp(arg, "-M") || !prefixcmp(arg, "--detect-renames=") ||
                 !strcmp(arg, "--detect-renames")) {
                if ((options->rename_score = diff_scoreopt_parse(arg)) == -1)
-                       return -1;
+                       return error("invalid argument to -M: %s", arg+2);
                options->detect_rename = DIFF_DETECT_RENAME;
        }
        else if (!prefixcmp(arg, "-C") || !prefixcmp(arg, "--detect-copies=") ||
@@ -3161,7 +3165,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                if (options->detect_rename == DIFF_DETECT_COPY)
                        DIFF_OPT_SET(options, FIND_COPIES_HARDER);
                if ((options->rename_score = diff_scoreopt_parse(arg)) == -1)
-                       return -1;
+                       return error("invalid argument to -C: %s", arg+2);
                options->detect_rename = DIFF_DETECT_COPY;
        }
        else if (!strcmp(arg, "--no-renames"))
diff --git a/dir.c b/dir.c
index b2dfb69eb5606a7538cc5e1876a91f703ec4969c..852e60f2a8d4784bf73d044f89fe822538121286 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -18,6 +18,22 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, in
        int check_only, const struct path_simplify *simplify);
 static int get_dtype(struct dirent *de, const char *path, int len);
 
+/* helper string functions with support for the ignore_case flag */
+int strcmp_icase(const char *a, const char *b)
+{
+       return ignore_case ? strcasecmp(a, b) : strcmp(a, b);
+}
+
+int strncmp_icase(const char *a, const char *b, size_t count)
+{
+       return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);
+}
+
+int fnmatch_icase(const char *pattern, const char *string, int flags)
+{
+       return fnmatch(pattern, string, flags | (ignore_case ? FNM_CASEFOLD : 0));
+}
+
 static int common_prefix(const char **pathspec)
 {
        const char *path, *slash, *next;
@@ -91,16 +107,30 @@ static int match_one(const char *match, const char *name, int namelen)
        if (!*match)
                return MATCHED_RECURSIVELY;
 
-       for (;;) {
-               unsigned char c1 = *match;
-               unsigned char c2 = *name;
-               if (c1 == '\0' || is_glob_special(c1))
-                       break;
-               if (c1 != c2)
-                       return 0;
-               match++;
-               name++;
-               namelen--;
+       if (ignore_case) {
+               for (;;) {
+                       unsigned char c1 = tolower(*match);
+                       unsigned char c2 = tolower(*name);
+                       if (c1 == '\0' || is_glob_special(c1))
+                               break;
+                       if (c1 != c2)
+                               return 0;
+                       match++;
+                       name++;
+                       namelen--;
+               }
+       } else {
+               for (;;) {
+                       unsigned char c1 = *match;
+                       unsigned char c2 = *name;
+                       if (c1 == '\0' || is_glob_special(c1))
+                               break;
+                       if (c1 != c2)
+                               return 0;
+                       match++;
+                       name++;
+                       namelen--;
+               }
        }
 
 
@@ -109,8 +139,8 @@ static int match_one(const char *match, const char *name, int namelen)
         * we need to match by fnmatch
         */
        matchlen = strlen(match);
-       if (strncmp(match, name, matchlen))
-               return !fnmatch(match, name, 0) ? MATCHED_FNMATCH : 0;
+       if (strncmp_icase(match, name, matchlen))
+               return !fnmatch_icase(match, name, 0) ? MATCHED_FNMATCH : 0;
 
        if (namelen == matchlen)
                return MATCHED_EXACTLY;
@@ -375,14 +405,14 @@ int excluded_from_list(const char *pathname,
                        if (x->flags & EXC_FLAG_NODIR) {
                                /* match basename */
                                if (x->flags & EXC_FLAG_NOWILDCARD) {
-                                       if (!strcmp(exclude, basename))
+                                       if (!strcmp_icase(exclude, basename))
                                                return to_exclude;
                                } else if (x->flags & EXC_FLAG_ENDSWITH) {
                                        if (x->patternlen - 1 <= pathlen &&
-                                           !strcmp(exclude + 1, pathname + pathlen - x->patternlen + 1))
+                                           !strcmp_icase(exclude + 1, pathname + pathlen - x->patternlen + 1))
                                                return to_exclude;
                                } else {
-                                       if (fnmatch(exclude, basename, 0) == 0)
+                                       if (fnmatch_icase(exclude, basename, 0) == 0)
                                                return to_exclude;
                                }
                        }
@@ -397,14 +427,14 @@ int excluded_from_list(const char *pathname,
 
                                if (pathlen < baselen ||
                                    (baselen && pathname[baselen-1] != '/') ||
-                                   strncmp(pathname, x->base, baselen))
+                                   strncmp_icase(pathname, x->base, baselen))
                                    continue;
 
                                if (x->flags & EXC_FLAG_NOWILDCARD) {
-                                       if (!strcmp(exclude, pathname + baselen))
+                                       if (!strcmp_icase(exclude, pathname + baselen))
                                                return to_exclude;
                                } else {
-                                       if (fnmatch(exclude, pathname+baselen,
+                                       if (fnmatch_icase(exclude, pathname+baselen,
                                                    FNM_PATHNAME) == 0)
                                            return to_exclude;
                                }
@@ -469,6 +499,39 @@ enum exist_status {
        index_gitdir
 };
 
+/*
+ * Do not use the alphabetically stored index to look up
+ * the directory name; instead, use the case insensitive
+ * name hash.
+ */
+static enum exist_status directory_exists_in_index_icase(const char *dirname, int len)
+{
+       struct cache_entry *ce = index_name_exists(&the_index, dirname, len + 1, ignore_case);
+       unsigned char endchar;
+
+       if (!ce)
+               return index_nonexistent;
+       endchar = ce->name[len];
+
+       /*
+        * The cache_entry structure returned will contain this dirname
+        * and possibly additional path components.
+        */
+       if (endchar == '/')
+               return index_directory;
+
+       /*
+        * If there are no additional path components, then this cache_entry
+        * represents a submodule.  Submodules, despite being directories,
+        * are stored in the cache without a closing slash.
+        */
+       if (!endchar && S_ISGITLINK(ce->ce_mode))
+               return index_gitdir;
+
+       /* This should never be hit, but it exists just in case. */
+       return index_nonexistent;
+}
+
 /*
  * The index sorts alphabetically by entry name, which
  * means that a gitlink sorts as '\0' at the end, while
@@ -478,7 +541,12 @@ enum exist_status {
  */
 static enum exist_status directory_exists_in_index(const char *dirname, int len)
 {
-       int pos = cache_name_pos(dirname, len);
+       int pos;
+
+       if (ignore_case)
+               return directory_exists_in_index_icase(dirname, len);
+
+       pos = cache_name_pos(dirname, len);
        if (pos < 0)
                pos = -pos-1;
        while (pos < active_nr) {
diff --git a/dir.h b/dir.h
index 278d84cdf7df01c33a45e6dc9c20592cecff9d85..b3e2104b9f231fbed88f98df12ad48d0d9992130 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -101,4 +101,8 @@ extern int remove_dir_recursively(struct strbuf *path, int flag);
 /* tries to remove the path with empty directories along it, ignores ENOENT */
 extern int remove_path(const char *path);
 
+extern int strcmp_icase(const char *a, const char *b);
+extern int strncmp_icase(const char *a, const char *b, size_t count);
+extern int fnmatch_icase(const char *pattern, const char *string, int flags);
+
 #endif
index de5581fe51d532231b0121bd2ef2e46669015bda..c79f2a9b561de77e27abd7ee26831e2a58beb259 100644 (file)
@@ -21,6 +21,7 @@ int prefer_symlink_refs;
 int is_bare_repository_cfg = -1; /* unspecified */
 int log_all_ref_updates = -1; /* unspecified */
 int warn_ambiguous_refs = 1;
+int unique_abbrev_extra_length;
 int repository_format_version;
 const char *git_commit_encoding;
 const char *git_log_output_encoding;
@@ -87,6 +88,7 @@ const char * const local_repo_env[LOCAL_REPO_ENV_SIZE + 1] = {
 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 = git_dir ? xstrdup(git_dir) : NULL;
@@ -171,6 +173,43 @@ char *get_object_directory(void)
        return git_object_dir;
 }
 
+int odb_mkstemp(char *template, size_t limit, const char *pattern)
+{
+       int fd;
+       /*
+        * we let the umask do its job, don't try to be more
+        * restrictive except to remove write permission.
+        */
+       int mode = 0444;
+       snprintf(template, limit, "%s/%s",
+                get_object_directory(), pattern);
+       fd = git_mkstemp_mode(template, mode);
+       if (0 <= fd)
+               return fd;
+
+       /* slow path */
+       /* some mkstemp implementations erase template on failure */
+       snprintf(template, limit, "%s/%s",
+                get_object_directory(), pattern);
+       safe_create_leading_directories(template);
+       return xmkstemp_mode(template, mode);
+}
+
+int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1)
+{
+       int fd;
+
+       snprintf(name, namesz, "%s/pack/pack-%s.keep",
+                get_object_directory(), sha1_to_hex(sha1));
+       fd = open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
+       if (0 <= fd)
+               return fd;
+
+       /* slow path */
+       safe_create_leading_directories(name);
+       return open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
+}
+
 char *get_index_file(void)
 {
        if (!git_index_file)
@@ -192,3 +231,14 @@ int set_git_dir(const char *path)
        setup_git_env();
        return 0;
 }
+
+const char *get_log_output_encoding(void)
+{
+       return git_log_output_encoding ? git_log_output_encoding
+               : get_commit_output_encoding();
+}
+
+const char *get_commit_output_encoding(void)
+{
+       return git_commit_encoding ? git_commit_encoding : "UTF-8";
+}
index 77549ebd6fbfbea051901d4ab474eebc8e2dbdbd..534c68db6fe4d0c34c38e632bd2c442966cf8663 100644 (file)
@@ -156,6 +156,7 @@ Format of STDIN stream:
 #include "csum-file.h"
 #include "quote.h"
 #include "exec_cmd.h"
+#include "dir.h"
 
 #define PACK_ID_BITS 16
 #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
@@ -1437,6 +1438,20 @@ static void store_tree(struct tree_entry *root)
        t->entry_count -= del;
 }
 
+static void tree_content_replace(
+       struct tree_entry *root,
+       const unsigned char *sha1,
+       const uint16_t mode,
+       struct tree_content *newtree)
+{
+       if (!S_ISDIR(mode))
+               die("Root cannot be a non-directory");
+       hashcpy(root->versions[1].sha1, sha1);
+       if (root->tree)
+               release_tree_content_recursive(root->tree);
+       root->tree = newtree;
+}
+
 static int tree_content_set(
        struct tree_entry *root,
        const char *p,
@@ -1444,7 +1459,7 @@ static int tree_content_set(
        const uint16_t mode,
        struct tree_content *subtree)
 {
-       struct tree_content *t = root->tree;
+       struct tree_content *t;
        const char *slash1;
        unsigned int i, n;
        struct tree_entry *e;
@@ -1454,23 +1469,17 @@ static int tree_content_set(
                n = slash1 - p;
        else
                n = strlen(p);
-       if (!slash1 && !n) {
-               if (!S_ISDIR(mode))
-                       die("Root cannot be a non-directory");
-               hashcpy(root->versions[1].sha1, sha1);
-               if (root->tree)
-                       release_tree_content_recursive(root->tree);
-               root->tree = subtree;
-               return 1;
-       }
        if (!n)
                die("Empty path component found in input");
        if (!slash1 && !S_ISDIR(mode) && subtree)
                die("Non-directories cannot have subtrees");
 
+       if (!root->tree)
+               load_tree(root);
+       t = root->tree;
        for (i = 0; i < t->entry_count; i++) {
                e = t->entries[i];
-               if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) {
+               if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
                        if (!slash1) {
                                if (!S_ISDIR(mode)
                                                && e->versions[1].mode == mode
@@ -1523,7 +1532,7 @@ static int tree_content_remove(
        const char *p,
        struct tree_entry *backup_leaf)
 {
-       struct tree_content *t = root->tree;
+       struct tree_content *t;
        const char *slash1;
        unsigned int i, n;
        struct tree_entry *e;
@@ -1534,9 +1543,12 @@ static int tree_content_remove(
        else
                n = strlen(p);
 
+       if (!root->tree)
+               load_tree(root);
+       t = root->tree;
        for (i = 0; i < t->entry_count; i++) {
                e = t->entries[i];
-               if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) {
+               if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
                        if (slash1 && !S_ISDIR(e->versions[1].mode))
                                /*
                                 * If p names a file in some subdirectory, and a
@@ -1581,7 +1593,7 @@ static int tree_content_get(
        const char *p,
        struct tree_entry *leaf)
 {
-       struct tree_content *t = root->tree;
+       struct tree_content *t;
        const char *slash1;
        unsigned int i, n;
        struct tree_entry *e;
@@ -1592,9 +1604,12 @@ static int tree_content_get(
        else
                n = strlen(p);
 
+       if (!root->tree)
+               load_tree(root);
+       t = root->tree;
        for (i = 0; i < t->entry_count; i++) {
                e = t->entries[i];
-               if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) {
+               if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
                        if (!slash1) {
                                memcpy(leaf, e, sizeof(*leaf));
                                if (e->tree && is_null_sha1(e->versions[1].sha1))
@@ -2218,6 +2233,10 @@ static void file_change_m(struct branch *b)
                                command_buf.buf);
        }
 
+       if (!*p) {
+               tree_content_replace(&b->branch_tree, sha1, mode, NULL);
+               return;
+       }
        tree_content_set(&b->branch_tree, p, sha1, mode, NULL);
 }
 
@@ -2276,6 +2295,13 @@ static void file_change_cr(struct branch *b, int rename)
                tree_content_get(&b->branch_tree, s, &leaf);
        if (!leaf.versions[1].mode)
                die("Path %s not in branch", s);
+       if (!*d) {      /* C "path/to/subdir" "" */
+               tree_content_replace(&b->branch_tree,
+                       leaf.versions[1].sha1,
+                       leaf.versions[1].mode,
+                       leaf.tree);
+               return;
+       }
        tree_content_set(&b->branch_tree, d,
                leaf.versions[1].sha1,
                leaf.versions[1].mode,
index 77f60fa3960323e412f8d09968e63865cc639298..a329c5a1f8c3b63ee6976a8e0e3903a11783324b 100755 (executable)
@@ -89,6 +89,7 @@ my %patch_modes = (
                TARGET => '',
                PARTICIPLE => 'staging',
                FILTER => 'file-only',
+               IS_REVERSE => 0,
        },
        'stash' => {
                DIFF => 'diff-index -p HEAD',
@@ -98,6 +99,7 @@ my %patch_modes = (
                TARGET => '',
                PARTICIPLE => 'stashing',
                FILTER => undef,
+               IS_REVERSE => 0,
        },
        'reset_head' => {
                DIFF => 'diff-index -p --cached',
@@ -107,6 +109,7 @@ my %patch_modes = (
                TARGET => '',
                PARTICIPLE => 'unstaging',
                FILTER => 'index-only',
+               IS_REVERSE => 1,
        },
        'reset_nothead' => {
                DIFF => 'diff-index -R -p --cached',
@@ -116,6 +119,7 @@ my %patch_modes = (
                TARGET => ' to index',
                PARTICIPLE => 'applying',
                FILTER => 'index-only',
+               IS_REVERSE => 0,
        },
        'checkout_index' => {
                DIFF => 'diff-files -p',
@@ -125,6 +129,7 @@ my %patch_modes = (
                TARGET => ' from worktree',
                PARTICIPLE => 'discarding',
                FILTER => 'file-only',
+               IS_REVERSE => 1,
        },
        'checkout_head' => {
                DIFF => 'diff-index -p',
@@ -134,6 +139,7 @@ my %patch_modes = (
                TARGET => ' from index and worktree',
                PARTICIPLE => 'discarding',
                FILTER => undef,
+               IS_REVERSE => 1,
        },
        'checkout_nothead' => {
                DIFF => 'diff-index -R -p',
@@ -143,6 +149,7 @@ my %patch_modes = (
                TARGET => ' to index and worktree',
                PARTICIPLE => 'applying',
                FILTER => undef,
+               IS_REVERSE => 0,
        },
 );
 
@@ -1001,10 +1008,12 @@ sub edit_hunk_manually {
        print $fh "# Manual hunk edit mode -- see bottom for a quick guide\n";
        print $fh @$oldtext;
        my $participle = $patch_mode_flavour{PARTICIPLE};
+       my $is_reverse = $patch_mode_flavour{IS_REVERSE};
+       my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-');
        print $fh <<EOF;
 # ---
-# To remove '-' lines, make them ' ' lines (context).
-# To remove '+' lines, delete them.
+# To remove '$remove_minus' lines, make them ' ' lines (context).
+# To remove '$remove_plus' lines, delete them.
 # Lines starting with # will be removed.
 #
 # If the patch applies cleanly, the edited hunk will immediately be
index d0a1e480b6445a6f5ba3680672c255ab44ca43f0..d6d269f138b06791ba6e8712b06036f00a73c7e3 100644 (file)
 #include <regex.h>
 #include <utime.h>
 #include <syslog.h>
+#ifndef NO_SYS_POLL_H
 #include <sys/poll.h>
+#else
+#include <poll.h>
+#endif
 #ifndef __MINGW32__
 #include <sys/wait.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
 #include <netdb.h>
 #include <pwd.h>
+#ifndef NO_INTTYPES_H
 #include <inttypes.h>
+#else
+#include <stdint.h>
+#endif
 #if defined(__CYGWIN__)
 #undef _XOPEN_SOURCE
 #include <grp.h>
@@ -413,6 +421,7 @@ extern ssize_t xwrite(int fd, const void *buf, size_t len);
 extern int xdup(int fd);
 extern FILE *xfdopen(int fd, const char *mode);
 extern int xmkstemp(char *template);
+extern int xmkstemp_mode(char *template, int mode);
 extern int odb_mkstemp(char *template, size_t limit, const char *pattern);
 extern int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1);
 
index e6f6ecda177c66a79b18359f7bf8cad9c4744c8c..10fcebb119ce2af81527aa3c24a9b7c3ab3b3e7f 100755 (executable)
@@ -580,6 +580,8 @@ gitweb_conf() {
 our \$projectroot = "$(dirname "$fqgitdir")";
 our \$git_temp = "$fqgitdir/gitweb/tmp";
 our \$projects_list = \$projectroot;
+
+\$feature{'remote_heads'}{'default'} = [1];
 EOF
 }
 
index 375a0ba59e1e2491ddcd0f8970699b1f11981565..1cc2ba6e09614fa55ad205145a3bdacfb78bf283 100644 (file)
@@ -66,7 +66,7 @@ get_remote_merge_branch () {
            origin="$1"
            default=$(get_default_remote)
            test -z "$origin" && origin=$default
-           curr_branch=$(git symbolic-ref -q HEAD)
+           curr_branch=$(git symbolic-ref -q HEAD) &&
            [ "$origin" = "$default" ] &&
            echo $(git for-each-ref --format='%(upstream)' $curr_branch)
            ;;
index 8eb74d45debe7ab2bd2cc9b5ed57b1ba49d94bdb..20a3bbea07c259ec1f19353b2888fde20e8b67da 100755 (executable)
@@ -201,10 +201,7 @@ test true = "$rebase" && {
                        die "updating an unborn branch with changes added to the index"
                fi
        else
-               git update-index --ignore-submodules --refresh &&
-               git diff-files --ignore-submodules --quiet &&
-               git diff-index --ignore-submodules --cached --quiet HEAD -- ||
-               die "refusing to pull with rebase: your working tree is not up-to-date"
+               require_clean_work_tree "pull with rebase" "Please commit or stash them."
        fi
        oldremoteref= &&
        . git-parse-remote &&
index c2383bfed58f0d28d35b0a657a02b273d06d8b27..5934b97fa106d73b1b091f050272c4c9f25eb646 100755 (executable)
@@ -153,14 +153,6 @@ run_pre_rebase_hook () {
        fi
 }
 
-require_clean_work_tree () {
-       # test if working tree is dirty
-       git rev-parse --verify HEAD > /dev/null &&
-       git update-index --ignore-submodules --refresh &&
-       git diff-files --quiet --ignore-submodules &&
-       git diff-index --cached --quiet HEAD --ignore-submodules -- ||
-       die "Working tree is dirty"
-}
 
 ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION"
 
@@ -557,7 +549,7 @@ do_next () {
                        exit "$status"
                fi
                # Run in subshell because require_clean_work_tree can die.
-               if ! (require_clean_work_tree)
+               if ! (require_clean_work_tree "rebase")
                then
                        warn "Commit or stash your changes, and then run"
                        warn
@@ -798,7 +790,7 @@ first and then run 'git rebase --continue' again."
 
                record_in_rewritten "$(cat "$DOTEST"/stopped-sha)"
 
-               require_clean_work_tree
+               require_clean_work_tree "rebase"
                do_rest
                ;;
        --abort)
@@ -896,7 +888,7 @@ first and then run 'git rebase --continue' again."
 
                comment_for_reflog start
 
-               require_clean_work_tree
+               require_clean_work_tree "rebase" "Please commit or stash them."
 
                if test ! -z "$1"
                then
index 10a238ae3cc942bc021db18195b7098e2866c266..9a6d7a4730a3ca81a4060f73b2be516fe235bb43 100755 (executable)
@@ -49,7 +49,8 @@ do_merge=
 dotest="$GIT_DIR"/rebase-merge
 prec=4
 verbose=
-diffstat=$(git config --bool rebase.stat)
+diffstat=
+test "$(git config --bool rebase.stat)" = true && diffstat=t
 git_am_opt=
 rebase_root=
 force_rebase=
@@ -412,19 +413,7 @@ else
        fi
 fi
 
-# The tree must be really really clean.
-if ! git update-index --ignore-submodules --refresh > /dev/null; then
-       echo >&2 "cannot rebase: you have unstaged changes"
-       git diff-files --name-status -r --ignore-submodules -- >&2
-       exit 1
-fi
-diff=$(git diff-index --cached --name-status -r --ignore-submodules HEAD --)
-case "$diff" in
-?*)    echo >&2 "cannot rebase: your index contains uncommitted changes"
-       echo >&2 "$diff"
-       exit 1
-       ;;
-esac
+require_clean_work_tree "rebase" "Please commit or stash them."
 
 if test -z "$rebase_root"
 then
index ae031a1375e9c674d80b9fa3d3d3b76780deaf9f..aa16b8356507e4e669ee5c4cb4b5a667942d559e 100644 (file)
@@ -145,6 +145,35 @@ require_work_tree () {
        die "fatal: $0 cannot be used without a working tree."
 }
 
+require_clean_work_tree () {
+       git rev-parse --verify HEAD >/dev/null || exit 1
+       git update-index -q --ignore-submodules --refresh
+       err=0
+
+       if ! git diff-files --quiet --ignore-submodules
+       then
+               echo >&2 "Cannot $1: You have unstaged changes."
+               err=1
+       fi
+
+       if ! git diff-index --cached --quiet --ignore-submodules HEAD --
+       then
+               if [ $err = 0 ]
+               then
+                   echo >&2 "Cannot $1: Your index contains uncommitted changes."
+               else
+                   echo >&2 "Additionally, your index contains uncommitted changes."
+               fi
+               err=1
+       fi
+
+       if [ $err = 1 ]
+       then
+               test -n "$2" && echo >&2 "$2"
+               exit 1
+       fi
+}
+
 get_author_ident_from_commit () {
        pick_author_script='
        /^author /{
index 757de82161e05b9d12c489efeff05c7fec341fe4..177dd259cd53cde017d73db5ccbecc7ed7dfa573 100755 (executable)
@@ -84,7 +84,7 @@ my ($_stdin, $_help, $_edit,
        $_version, $_fetch_all, $_no_rebase, $_fetch_parent,
        $_merge, $_strategy, $_dry_run, $_local,
        $_prefix, $_no_checkout, $_url, $_verbose,
-       $_git_format, $_commit_url, $_tag);
+       $_git_format, $_commit_url, $_tag, $_merge_info);
 $Git::SVN::_follow_parent = 1;
 $_q ||= 0;
 my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
@@ -154,6 +154,7 @@ my %cmd = (
                          'commit-url=s' => \$_commit_url,
                          'revision|r=i' => \$_revision,
                          'no-rebase' => \$_no_rebase,
+                         'mergeinfo=s' => \$_merge_info,
                        %cmt_opts, %fc_opts } ],
        branch => [ \&cmd_branch,
                    'Create a branch in the SVN repository',
@@ -569,6 +570,7 @@ sub cmd_dcommit {
                                               print "Committed r$_[0]\n";
                                               $cmt_rev = $_[0];
                                        },
+                                       mergeinfo => $_merge_info,
                                        svn_path => '');
                        if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) {
                                print "No changes\n$d~1 == $d\n";
@@ -4451,6 +4453,7 @@ sub new {
        $self->{path_prefix} = length $self->{svn_path} ?
                               "$self->{svn_path}/" : '';
        $self->{config} = $opts->{config};
+       $self->{mergeinfo} = $opts->{mergeinfo};
        return $self;
 }
 
@@ -4760,6 +4763,11 @@ sub change_file_prop {
        $self->SUPER::change_file_prop($fbat, $pname, $pval, $self->{pool});
 }
 
+sub change_dir_prop {
+       my ($self, $pbat, $pname, $pval) = @_;
+       $self->SUPER::change_dir_prop($pbat, $pname, $pval, $self->{pool});
+}
+
 sub _chg_file_get_blob ($$$$) {
        my ($self, $fbat, $m, $which) = @_;
        my $fh = $::_repository->temp_acquire("git_blob_$which");
@@ -4853,6 +4861,11 @@ sub apply_diff {
                        fatal("Invalid change type: $f");
                }
        }
+
+       if (defined($self->{mergeinfo})) {
+               $self->change_dir_prop($self->{bat}{''}, "svn:mergeinfo",
+                                      $self->{mergeinfo});
+       }
        $self->rmdirs if $_rmdir;
        if (@$mods == 0) {
                $self->abort_edit;
diff --git a/git.c b/git.c
index 0409ac9fd3f1ea36680189e07116e58b2630ccad..d532576cdff244fcd3945ef4fcc580e1bf0b04ae 100644 (file)
--- a/git.c
+++ b/git.c
@@ -19,14 +19,22 @@ static struct startup_info git_startup_info;
 static int use_pager = -1;
 struct pager_config {
        const char *cmd;
-       int val;
+       int want;
+       char *value;
 };
 
 static int pager_command_config(const char *var, const char *value, void *data)
 {
        struct pager_config *c = data;
-       if (!prefixcmp(var, "pager.") && !strcmp(var + 6, c->cmd))
-               c->val = git_config_bool(var, value);
+       if (!prefixcmp(var, "pager.") && !strcmp(var + 6, c->cmd)) {
+               int b = git_config_maybe_bool(var, value);
+               if (b >= 0)
+                       c->want = b;
+               else {
+                       c->want = 1;
+                       c->value = xstrdup(value);
+               }
+       }
        return 0;
 }
 
@@ -35,9 +43,12 @@ int check_pager_config(const char *cmd)
 {
        struct pager_config c;
        c.cmd = cmd;
-       c.val = -1;
+       c.want = -1;
+       c.value = NULL;
        git_config(pager_command_config, &c);
-       return c.val;
+       if (c.value)
+               pager_program = c.value;
+       return c.want;
 }
 
 static void commit_pager_choice(void) {
@@ -374,6 +385,8 @@ static void handle_internal_command(int argc, const char **argv)
                { "receive-pack", cmd_receive_pack },
                { "reflog", cmd_reflog, RUN_SETUP },
                { "remote", cmd_remote, RUN_SETUP },
+               { "remote-ext", cmd_remote_ext },
+               { "remote-fd", cmd_remote_fd },
                { "replace", cmd_replace, RUN_SETUP },
                { "repo-config", cmd_config, RUN_SETUP_GENTLY },
                { "rerere", cmd_rerere, RUN_SETUP },
index 679f2da3ee0c59413d34bf020e242887bfd4f28c..75306ca967c5e978da5504276051738338be7537 100755 (executable)
@@ -17,12 +17,10 @@ use Encode;
 use Fcntl ':mode';
 use File::Find qw();
 use File::Basename qw(basename);
+use Time::HiRes qw(gettimeofday tv_interval);
 binmode STDOUT, ':utf8';
 
-our $t0;
-if (eval { require Time::HiRes; 1; }) {
-       $t0 = [Time::HiRes::gettimeofday()];
-}
+our $t0 = [ gettimeofday() ];
 our $number_of_git_cmds = 0;
 
 BEGIN {
@@ -493,6 +491,18 @@ our %feature = (
                'sub' => sub { feature_bool('highlight', @_) },
                'override' => 0,
                'default' => [0]},
+
+       # Enable displaying of remote heads in the heads list
+
+       # To enable system wide have in $GITWEB_CONFIG
+       # $feature{'remote_heads'}{'default'} = [1];
+       # To have project specific config enable override in $GITWEB_CONFIG
+       # $feature{'remote_heads'}{'override'} = 1;
+       # and in project config gitweb.remote_heads = 0|1;
+       'remote_heads' => {
+               'sub' => sub { feature_bool('remote_heads', @_) },
+               'override' => 0,
+               'default' => [0]},
 );
 
 sub gitweb_get_feature {
@@ -707,6 +717,7 @@ our %actions = (
        "log" => \&git_log,
        "patch" => \&git_patch,
        "patches" => \&git_patches,
+       "remotes" => \&git_remotes,
        "rss" => \&git_rss,
        "atom" => \&git_atom,
        "search" => \&git_search,
@@ -1065,7 +1076,7 @@ sub dispatch {
 }
 
 sub reset_timer {
-       our $t0 = [Time::HiRes::gettimeofday()]
+       our $t0 = [ gettimeofday() ]
                if defined $t0;
        our $number_of_git_cmds = 0;
 }
@@ -2759,6 +2770,44 @@ sub git_get_last_activity {
        return (undef, undef);
 }
 
+# Implementation note: when a single remote is wanted, we cannot use 'git
+# remote show -n' because that command always work (assuming it's a remote URL
+# if it's not defined), and we cannot use 'git remote show' because that would
+# try to make a network roundtrip. So the only way to find if that particular
+# remote is defined is to walk the list provided by 'git remote -v' and stop if
+# and when we find what we want.
+sub git_get_remotes_list {
+       my $wanted = shift;
+       my %remotes = ();
+
+       open my $fd, '-|' , git_cmd(), 'remote', '-v';
+       return unless $fd;
+       while (my $remote = <$fd>) {
+               chomp $remote;
+               $remote =~ s!\t(.*?)\s+\((\w+)\)$!!;
+               next if $wanted and not $remote eq $wanted;
+               my ($url, $key) = ($1, $2);
+
+               $remotes{$remote} ||= { 'heads' => () };
+               $remotes{$remote}{$key} = $url;
+       }
+       close $fd or return;
+       return wantarray ? %remotes : \%remotes;
+}
+
+# Takes a hash of remotes as first parameter and fills it by adding the
+# available remote heads for each of the indicated remotes.
+sub fill_remote_heads {
+       my $remotes = shift;
+       my @heads = map { "remotes/$_" } keys %$remotes;
+       my @remoteheads = git_get_heads_list(undef, @heads);
+       foreach my $remote (keys %$remotes) {
+               $remotes->{$remote}{'heads'} = [ grep {
+                       $_->{'name'} =~ s!^$remote/!!
+                       } @remoteheads ];
+       }
+}
+
 sub git_get_references {
        my $type = shift || "";
        my %refs;
@@ -3157,13 +3206,15 @@ sub parse_from_to_diffinfo {
 ## parse to array of hashes functions
 
 sub git_get_heads_list {
-       my $limit = shift;
+       my ($limit, @classes) = @_;
+       @classes = ('heads') unless @classes;
+       my @patterns = map { "refs/$_" } @classes;
        my @headslist;
 
        open my $fd, '-|', git_cmd(), 'for-each-ref',
                ($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate',
                '--format=%(objectname) %(refname) %(subject)%00%(committer)',
-               'refs/heads'
+               @patterns
                or return;
        while (my $line = <$fd>) {
                my %ref_item;
@@ -3174,7 +3225,7 @@ sub git_get_heads_list {
                my ($committer, $epoch, $tz) =
                        ($committerinfo =~ /^(.*) ([0-9]+) (.*)$/);
                $ref_item{'fullname'}  = $name;
-               $name =~ s!^refs/heads/!!;
+               $name =~ s!^refs/(?:head|remote)s/!!;
 
                $ref_item{'name'}  = $name;
                $ref_item{'id'}    = $hash;
@@ -3511,7 +3562,15 @@ EOF
        if (defined $project) {
                print $cgi->a({-href => href(action=>"summary")}, esc_html($project));
                if (defined $action) {
-                       print " / $action";
+                       my $action_print = $action ;
+                       if (defined $opts{-action_extra}) {
+                               $action_print = $cgi->a({-href => href(action=>$action)},
+                                       $action);
+                       }
+                       print " / $action_print";
+               }
+               if (defined $opts{-action_extra}) {
+                       print " / $opts{-action_extra}";
                }
                print "\n";
        }
@@ -3590,7 +3649,7 @@ sub git_footer_html {
                print "<div id=\"generating_info\">\n";
                print 'This page took '.
                      '<span id="generating_time" class="time_span">'.
-                     Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
+                     tv_interval($t0, [ gettimeofday() ]).
                      ' seconds </span>'.
                      ' and '.
                      '<span id="generating_cmd">'.
@@ -3718,6 +3777,19 @@ sub git_print_page_nav {
              "</div>\n";
 }
 
+# returns a submenu for the nagivation of the refs views (tags, heads,
+# remotes) with the current view disabled and the remotes view only
+# available if the feature is enabled
+sub format_ref_views {
+       my ($current) = @_;
+       my @ref_views = qw{tags heads};
+       push @ref_views, 'remotes' if gitweb_check_feature('remote_heads');
+       return join " | ", map {
+               $_ eq $current ? $_ :
+               $cgi->a({-href => href(action=>$_)}, $_)
+       } @ref_views
+}
+
 sub format_paging_nav {
        my ($action, $page, $has_next_link) = @_;
        my $paging_nav;
@@ -3761,6 +3833,49 @@ sub git_print_header_div {
              "\n</div>\n";
 }
 
+sub format_repo_url {
+       my ($name, $url) = @_;
+       return "<tr class=\"metadata_url\"><td>$name</td><td>$url</td></tr>\n";
+}
+
+# Group output by placing it in a DIV element and adding a header.
+# Options for start_div() can be provided by passing a hash reference as the
+# first parameter to the function.
+# Options to git_print_header_div() can be provided by passing an array
+# reference. This must follow the options to start_div if they are present.
+# The content can be a scalar, which is output as-is, a scalar reference, which
+# is output after html escaping, an IO handle passed either as *handle or
+# *handle{IO}, or a function reference. In the latter case all following
+# parameters will be taken as argument to the content function call.
+sub git_print_section {
+       my ($div_args, $header_args, $content);
+       my $arg = shift;
+       if (ref($arg) eq 'HASH') {
+               $div_args = $arg;
+               $arg = shift;
+       }
+       if (ref($arg) eq 'ARRAY') {
+               $header_args = $arg;
+               $arg = shift;
+       }
+       $content = $arg;
+
+       print $cgi->start_div($div_args);
+       git_print_header_div(@$header_args);
+
+       if (ref($content) eq 'CODE') {
+               $content->(@_);
+       } elsif (ref($content) eq 'SCALAR') {
+               print esc_html($$content);
+       } elsif (ref($content) eq 'GLOB' or ref($content) eq 'IO::Handle') {
+               print <$content>;
+       } elsif (!ref($content) && defined($content)) {
+               print $content;
+       }
+
+       print $cgi->end_div;
+}
+
 sub print_local_time {
        print format_local_time(@_);
 }
@@ -4960,7 +5075,7 @@ sub git_heads_body {
                      "<td class=\"link\">" .
                      $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'fullname'})}, "shortlog") . " | " .
                      $cgi->a({-href => href(action=>"log", hash=>$ref{'fullname'})}, "log") . " | " .
-                     $cgi->a({-href => href(action=>"tree", hash=>$ref{'fullname'}, hash_base=>$ref{'name'})}, "tree") .
+                     $cgi->a({-href => href(action=>"tree", hash=>$ref{'fullname'}, hash_base=>$ref{'fullname'})}, "tree") .
                      "</td>\n" .
                      "</tr>";
        }
@@ -4972,6 +5087,101 @@ sub git_heads_body {
        print "</table>\n";
 }
 
+# Display a single remote block
+sub git_remote_block {
+       my ($remote, $rdata, $limit, $head) = @_;
+
+       my $heads = $rdata->{'heads'};
+       my $fetch = $rdata->{'fetch'};
+       my $push = $rdata->{'push'};
+
+       my $urls_table = "<table class=\"projects_list\">\n" ;
+
+       if (defined $fetch) {
+               if ($fetch eq $push) {
+                       $urls_table .= format_repo_url("URL", $fetch);
+               } else {
+                       $urls_table .= format_repo_url("Fetch URL", $fetch);
+                       $urls_table .= format_repo_url("Push URL", $push) if defined $push;
+               }
+       } elsif (defined $push) {
+               $urls_table .= format_repo_url("Push URL", $push);
+       } else {
+               $urls_table .= format_repo_url("", "No remote URL");
+       }
+
+       $urls_table .= "</table>\n";
+
+       my $dots;
+       if (defined $limit && $limit < @$heads) {
+               $dots = $cgi->a({-href => href(action=>"remotes", hash=>$remote)}, "...");
+       }
+
+       print $urls_table;
+       git_heads_body($heads, $head, 0, $limit, $dots);
+}
+
+# Display a list of remote names with the respective fetch and push URLs
+sub git_remotes_list {
+       my ($remotedata, $limit) = @_;
+       print "<table class=\"heads\">\n";
+       my $alternate = 1;
+       my @remotes = sort keys %$remotedata;
+
+       my $limited = $limit && $limit < @remotes;
+
+       $#remotes = $limit - 1 if $limited;
+
+       while (my $remote = shift @remotes) {
+               my $rdata = $remotedata->{$remote};
+               my $fetch = $rdata->{'fetch'};
+               my $push = $rdata->{'push'};
+               if ($alternate) {
+                       print "<tr class=\"dark\">\n";
+               } else {
+                       print "<tr class=\"light\">\n";
+               }
+               $alternate ^= 1;
+               print "<td>" .
+                     $cgi->a({-href=> href(action=>'remotes', hash=>$remote),
+                              -class=> "list name"},esc_html($remote)) .
+                     "</td>";
+               print "<td class=\"link\">" .
+                     (defined $fetch ? $cgi->a({-href=> $fetch}, "fetch") : "fetch") .
+                     " | " .
+                     (defined $push ? $cgi->a({-href=> $push}, "push") : "push") .
+                     "</td>";
+
+               print "</tr>\n";
+       }
+
+       if ($limited) {
+               print "<tr>\n" .
+                     "<td colspan=\"3\">" .
+                     $cgi->a({-href => href(action=>"remotes")}, "...") .
+                     "</td>\n" . "</tr>\n";
+       }
+
+       print "</table>";
+}
+
+# Display remote heads grouped by remote, unless there are too many
+# remotes, in which case we only display the remote names
+sub git_remotes_body {
+       my ($remotedata, $limit, $head) = @_;
+       if ($limit and $limit < keys %$remotedata) {
+               git_remotes_list($remotedata, $limit);
+       } else {
+               fill_remote_heads($remotedata);
+               while (my ($remote, $rdata) = each %$remotedata) {
+                       git_print_section({-class=>"remote", -id=>$remote},
+                               ["remotes", $remote, $remote], sub {
+                                       git_remote_block($remote, $rdata, $limit, $head);
+                               });
+               }
+       }
+}
+
 sub git_search_grep_body {
        my ($commitlist, $from, $to, $extra) = @_;
        $from = 0 unless defined $from;
@@ -5109,6 +5319,7 @@ sub git_summary {
        my %co = parse_commit("HEAD");
        my %cd = %co ? parse_date($co{'committer_epoch'}, $co{'committer_tz'}) : ();
        my $head = $co{'id'};
+       my $remote_heads = gitweb_check_feature('remote_heads');
 
        my $owner = git_get_project_owner($project);
 
@@ -5117,6 +5328,7 @@ sub git_summary {
        # there are more ...
        my @taglist  = git_get_tags_list(16);
        my @headlist = git_get_heads_list(16);
+       my %remotedata = $remote_heads ? git_get_remotes_list() : ();
        my @forklist;
        my $check_forks = gitweb_check_feature('forks');
 
@@ -5142,7 +5354,7 @@ sub git_summary {
        @url_list = map { "$_/$project" } @git_base_url_list unless @url_list;
        foreach my $git_url (@url_list) {
                next unless $git_url;
-               print "<tr class=\"metadata_url\"><td>$url_tag</td><td>$git_url</td></tr>\n";
+               print format_repo_url($url_tag, $git_url);
                $url_tag = "";
        }
 
@@ -5194,6 +5406,11 @@ sub git_summary {
                               $cgi->a({-href => href(action=>"heads")}, "..."));
        }
 
+       if (%remotedata) {
+               git_print_header_div('remotes');
+               git_remotes_body(\%remotedata, 15, $head);
+       }
+
        if (@forklist) {
                git_print_header_div('forks');
                git_project_list_body(\@forklist, 'age', 0, 15,
@@ -5298,7 +5515,7 @@ sub git_blame_common {
                print 'END';
                if (defined $t0 && gitweb_check_feature('timed')) {
                        print ' '.
-                             Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
+                             tv_interval($t0, [ gettimeofday() ]).
                              ' '.$number_of_git_cmds;
                }
                print "\n";
@@ -5485,7 +5702,7 @@ sub git_blame_data {
 sub git_tags {
        my $head = git_get_head_hash($project);
        git_header_html();
-       git_print_page_nav('','', $head,undef,$head);
+       git_print_page_nav('','', $head,undef,$head,format_ref_views('tags'));
        git_print_header_div('summary', $project);
 
        my @tagslist = git_get_tags_list();
@@ -5498,7 +5715,7 @@ sub git_tags {
 sub git_heads {
        my $head = git_get_head_hash($project);
        git_header_html();
-       git_print_page_nav('','', $head,undef,$head);
+       git_print_page_nav('','', $head,undef,$head,format_ref_views('heads'));
        git_print_header_div('summary', $project);
 
        my @headslist = git_get_heads_list();
@@ -5508,6 +5725,39 @@ sub git_heads {
        git_footer_html();
 }
 
+# used both for single remote view and for list of all the remotes
+sub git_remotes {
+       gitweb_check_feature('remote_heads')
+               or die_error(403, "Remote heads view is disabled");
+
+       my $head = git_get_head_hash($project);
+       my $remote = $input_params{'hash'};
+
+       my $remotedata = git_get_remotes_list($remote);
+       die_error(500, "Unable to get remote information") unless defined $remotedata;
+
+       unless (%$remotedata) {
+               die_error(404, defined $remote ?
+                       "Remote $remote not found" :
+                       "No remotes found");
+       }
+
+       git_header_html(undef, undef, -action_extra => $remote);
+       git_print_page_nav('', '',  $head, undef, $head,
+               format_ref_views($remote ? '' : 'remotes'));
+
+       fill_remote_heads($remotedata);
+       if (defined $remote) {
+               git_print_header_div('remotes', "$remote remote for $project");
+               git_remote_block($remote, $remotedata->{$remote}, undef, $head);
+       } else {
+               git_print_header_div('summary', "$project remotes");
+               git_remotes_body($remotedata, undef, $head);
+       }
+
+       git_footer_html();
+}
+
 sub git_blob_plain {
        my $type = shift;
        my $expires;
index 4132aabcdb4ef402ceb1a890b61778b3254fdf18..79d7eebba797f3bd80a639f6ca0835e15016762d 100644 (file)
@@ -573,6 +573,12 @@ div.binary {
        font-style: italic;
 }
 
+div.remote {
+       margin: .5em;
+       border: 1px solid #d9d8d1;
+       display: inline-block;
+}
+
 /* Style definition generated by highlight 2.4.5, http://www.andre-simon.de/ */
 
 /* Highlighting theme definition: */
diff --git a/http.c b/http.c
index 0a5011f615bdfade0b69682b177dc97471ac20a3..f582b13b915e58a189086cdec2bab6dbb94eac6a 100644 (file)
--- a/http.c
+++ b/http.c
@@ -2,6 +2,7 @@
 #include "pack.h"
 #include "sideband.h"
 #include "run-command.h"
+#include "url.h"
 
 int data_received;
 int active_requests;
@@ -279,6 +280,11 @@ static CURL *get_curl_handle(void)
        }
 
        curl_easy_setopt(result, CURLOPT_FOLLOWLOCATION, 1);
+#if LIBCURL_VERSION_NUM >= 0x071301
+       curl_easy_setopt(result, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL);
+#elif LIBCURL_VERSION_NUM >= 0x071101
+       curl_easy_setopt(result, CURLOPT_POST301, 1);
+#endif
 
        if (getenv("GIT_CURL_VERBOSE"))
                curl_easy_setopt(result, CURLOPT_VERBOSE, 1);
@@ -297,7 +303,7 @@ static CURL *get_curl_handle(void)
 
 static void http_auth_init(const char *url)
 {
-       char *at, *colon, *cp, *slash;
+       char *at, *colon, *cp, *slash, *decoded;
        int len;
 
        cp = strstr(url, "://");
@@ -322,16 +328,25 @@ static void http_auth_init(const char *url)
                user_name = xmalloc(len + 1);
                memcpy(user_name, cp, len);
                user_name[len] = '\0';
+               decoded = url_decode(user_name);
+               free(user_name);
+               user_name = decoded;
                user_pass = NULL;
        } else {
                len = colon - cp;
                user_name = xmalloc(len + 1);
                memcpy(user_name, cp, len);
                user_name[len] = '\0';
+               decoded = url_decode(user_name);
+               free(user_name);
+               user_name = decoded;
                len = at - (colon + 1);
                user_pass = xmalloc(len + 1);
                memcpy(user_pass, colon + 1, len);
                user_pass[len] = '\0';
+               decoded = url_decode(user_pass);
+               free(user_pass);
+               user_pass = decoded;
        }
 }
 
index 875859f68efaab5113785896326140c8d0543f45..16c2dbeab95a0f4099e3de8badf688bb4dee8189 100644 (file)
@@ -63,6 +63,22 @@ static int sha_eq(const unsigned char *a, const unsigned char *b)
        return a && b && hashcmp(a, b) == 0;
 }
 
+enum rename_type {
+       RENAME_NORMAL = 0,
+       RENAME_DELETE,
+       RENAME_ONE_FILE_TO_TWO
+};
+
+struct 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;
+};
+
 /*
  * Since we want to write the index eventually, we cannot reuse the index
  * for these (temporary) data.
@@ -74,9 +90,37 @@ struct stage_data
                unsigned mode;
                unsigned char sha[20];
        } stages[4];
+       struct rename_df_conflict_info *rename_df_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)
+{
+       struct rename_df_conflict_info *ci = xcalloc(1, sizeof(struct rename_df_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->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;
+       }
+}
+
 static int show(struct merge_options *o, int v)
 {
        return (!o->call_depth && o->verbosity >= v) || o->verbosity >= 5;
@@ -302,6 +346,63 @@ 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)
+{
+       /* 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.
+        *
+        * 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.
+        */
+       const char *last_file = NULL;
+       int last_len = 0;
+       struct stage_data *last_e;
+       int i;
+
+       for (i = 0; i < entries->nr; i++) {
+               const char *path = entries->items[i].string;
+               int len = strlen(path);
+               struct stage_data *e = 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 (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);
+               }
+
+               /*
+                * Determine whether path could exist as a file in the
+                * working directory as a possible D/F conflict.  This
+                * will only occur when it exists in stage 2 as a
+                * file.
+                */
+               if (S_ISREG(e->stages[2].mode) || S_ISLNK(e->stages[2].mode)) {
+                       last_file = path;
+                       last_len = len;
+                       last_e = e;
+               } else {
+                       last_file = NULL;
+               }
+       }
+}
+
 struct rename
 {
        struct diff_filepair *pair;
@@ -374,11 +475,10 @@ static struct string_list *get_renames(struct merge_options *o,
        return renames;
 }
 
-static int update_stages(const char *path, struct diff_filespec *o,
+static int update_stages_options(const char *path, struct diff_filespec *o,
                         struct diff_filespec *a, struct diff_filespec *b,
-                        int clear)
+                        int clear, int options)
 {
-       int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;
        if (clear)
                if (remove_file_from_cache(path))
                        return -1;
@@ -394,6 +494,34 @@ static int update_stages(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)
+{
+       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;
+       entry->stages[3].mode = b->mode;
+       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,
                       const char *path, int no_wd)
 {
@@ -732,29 +860,56 @@ static struct merge_file_info merge_file(struct merge_options *o,
        return result;
 }
 
-static void conflict_rename_rename(struct merge_options *o,
-                                  struct rename *ren1,
-                                  const char *branch1,
-                                  struct rename *ren2,
-                                  const char *branch2)
+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;
+
+       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;
+       }
+       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)
 {
+       /* One file was renamed in both branches, but to different names. */
        char *del[2];
        int delp = 0;
-       const char *ren1_dst = ren1->pair->two->path;
-       const char *ren2_dst = ren2->pair->two->path;
+       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;
-       if (string_list_has_string(&o->current_directory_set, ren1_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);
-               remove_file(o, 0, ren1_dst, 0);
        }
-       if (string_list_has_string(&o->current_directory_set, ren2_dst)) {
+       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);
-               remove_file(o, 0, ren2_dst, 0);
        }
        if (o->call_depth) {
                remove_file_from_cache(dst_name1);
@@ -762,34 +917,27 @@ static void conflict_rename_rename(struct merge_options *o,
                /*
                 * Uncomment to leave the conflicting names in the resulting tree
                 *
-                * update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, dst_name1);
-                * update_file(o, 0, ren2->pair->two->sha1, ren2->pair->two->mode, dst_name2);
+                * update_file(o, 0, pair1->two->sha1, pair1->two->mode, dst_name1);
+                * update_file(o, 0, pair2->two->sha1, pair2->two->mode, dst_name2);
                 */
        } else {
-               update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1);
-               update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1);
+               update_stages(ren1_dst, NULL, pair1->two, NULL, 1);
+               update_stages(ren2_dst, NULL, NULL, pair2->two, 1);
+
+               update_file(o, 0, pair1->two->sha1, pair1->two->mode, dst_name1);
+               update_file(o, 0, pair2->two->sha1, pair2->two->mode, dst_name2);
        }
        while (delp--)
                free(del[delp]);
 }
 
-static void conflict_rename_dir(struct merge_options *o,
-                               struct rename *ren1,
-                               const char *branch1)
-{
-       char *new_path = unique_path(o, ren1->pair->two->path, branch1);
-       output(o, 1, "Renaming %s to %s instead", ren1->pair->one->path, new_path);
-       remove_file(o, 0, ren1->pair->two->path, 0);
-       update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path);
-       free(new_path);
-}
-
-static void conflict_rename_rename_2(struct merge_options *o,
-                                    struct rename *ren1,
-                                    const char *branch1,
-                                    struct rename *ren2,
-                                    const char *branch2)
+static void conflict_rename_rename_2to1(struct merge_options *o,
+                                       struct rename *ren1,
+                                       const char *branch1,
+                                       struct rename *ren2,
+                                       const char *branch2)
 {
+       /* 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",
@@ -879,84 +1027,60 @@ static int process_renames(struct merge_options *o,
                        ren2->dst_entry->processed = 1;
                        ren2->processed = 1;
                        if (strcmp(ren1_dst, ren2_dst) != 0) {
-                               clean_merge = 0;
-                               output(o, 1, "CONFLICT (rename/rename): "
-                                      "Rename \"%s\"->\"%s\" in branch \"%s\" "
-                                      "rename \"%s\"->\"%s\" in \"%s\"%s",
-                                      src, ren1_dst, branch1,
-                                      src, ren2_dst, branch2,
-                                      o->call_depth ? " (left unresolved)": "");
-                               if (o->call_depth) {
-                                       remove_file_from_cache(src);
-                                       update_file(o, 0, ren1->pair->one->sha1,
-                                                   ren1->pair->one->mode, src);
-                               }
-                               conflict_rename_rename(o, ren1, branch1, ren2, branch2);
+                               setup_rename_df_conflict_info(RENAME_ONE_FILE_TO_TWO,
+                                                             ren1->pair,
+                                                             ren2->pair,
+                                                             branch1,
+                                                             branch2,
+                                                             ren1->dst_entry,
+                                                             ren2->dst_entry);
                        } else {
-                               struct merge_file_info mfi;
                                remove_file(o, 1, ren1_src, 1);
-                               mfi = merge_file(o,
-                                                ren1->pair->one,
-                                                ren1->pair->two,
-                                                ren2->pair->two,
-                                                branch1,
-                                                branch2);
-                               if (mfi.merge || !mfi.clean)
-                                       output(o, 1, "Renaming %s->%s", src, ren1_dst);
-
-                               if (mfi.merge)
-                                       output(o, 2, "Auto-merging %s", ren1_dst);
-
-                               if (!mfi.clean) {
-                                       output(o, 1, "CONFLICT (content): merge conflict in %s",
-                                              ren1_dst);
-                                       clean_merge = 0;
-
-                                       if (!o->call_depth)
-                                               update_stages(ren1_dst,
-                                                             ren1->pair->one,
-                                                             ren1->pair->two,
-                                                             ren2->pair->two,
-                                                             1 /* clear */);
-                               }
-                               update_file(o, mfi.clean, mfi.sha, mfi.mode, ren1_dst);
+                               update_stages_and_entry(ren1_dst,
+                                                       ren1->dst_entry,
+                                                       ren1->pair->one,
+                                                       ren1->pair->two,
+                                                       ren2->pair->two,
+                                                       1 /* clear */);
                        }
                } 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, stage = a_renames == renames1 ? 3: 2;
+                       int try_merge;
 
-                       remove_file(o, 1, ren1_src, o->call_depth || stage == 3);
+                       /*
+                        * unpack_trees loads entries from common-commit
+                        * into stage 1, from head-commit into stage 2, and
+                        * from merge-commit into stage 3.  We keep track
+                        * of which side corresponds to the rename.
+                        */
+                       int renamed_stage = a_renames == renames1 ? 2 : 3;
+                       int other_stage =   a_renames == renames1 ? 3 : 2;
 
-                       hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha);
-                       src_other.mode = ren1->src_entry->stages[stage].mode;
-                       hashcpy(dst_other.sha1, ren1->dst_entry->stages[stage].sha);
-                       dst_other.mode = ren1->dst_entry->stages[stage].mode;
+                       remove_file(o, 1, ren1_src, o->call_depth || renamed_stage == 2);
 
+                       hashcpy(src_other.sha1, ren1->src_entry->stages[other_stage].sha);
+                       src_other.mode = ren1->src_entry->stages[other_stage].mode;
+                       hashcpy(dst_other.sha1, ren1->dst_entry->stages[other_stage].sha);
+                       dst_other.mode = ren1->dst_entry->stages[other_stage].mode;
                        try_merge = 0;
 
-                       if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
-                               clean_merge = 0;
-                               output(o, 1, "CONFLICT (rename/directory): Rename %s->%s in %s "
-                                      " directory %s added in %s",
-                                      ren1_src, ren1_dst, branch1,
-                                      ren1_dst, branch2);
-                               conflict_rename_dir(o, ren1, branch1);
-                       } else if (sha_eq(src_other.sha1, null_sha1)) {
-                               clean_merge = 0;
-                               output(o, 1, "CONFLICT (rename/delete): Rename %s->%s in %s "
-                                      "and deleted in %s",
-                                      ren1_src, ren1_dst, branch1,
-                                      branch2);
-                               update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst);
-                               if (!o->call_depth)
-                                       update_stages(ren1_dst, NULL,
-                                                       branch1 == o->branch1 ?
-                                                       ren1->pair->two : NULL,
-                                                       branch1 == o->branch1 ?
-                                                       NULL : ren1->pair->two, 1);
+                       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);
+                               }
                        } else if ((dst_other.mode == ren1->pair->two->mode) &&
                                   sha_eq(dst_other.sha1, ren1->pair->two->sha1)) {
                                /* Added file on the other side
@@ -991,6 +1115,7 @@ static int process_renames(struct merge_options *o,
                                                    mfi.sha,
                                                    mfi.mode,
                                                    ren1_dst);
+                                       try_merge = 0;
                                } else {
                                        new_path = unique_path(o, ren1_dst, branch2);
                                        output(o, 1, "Adding as %s instead", new_path);
@@ -1005,13 +1130,12 @@ static int process_renames(struct merge_options *o,
                                       "Rename %s->%s in %s",
                                       ren1_src, ren1_dst, branch1,
                                       ren2->pair->one->path, ren2->pair->two->path, branch2);
-                               conflict_rename_rename_2(o, ren1, branch1, ren2, branch2);
+                               conflict_rename_rename_2to1(o, ren1, branch1, ren2, branch2);
                        } else
                                try_merge = 1;
 
                        if (try_merge) {
                                struct diff_filespec *one, *a, *b;
-                               struct merge_file_info mfi;
                                src_other.path = (char *)ren1_src;
 
                                one = ren1->pair->one;
@@ -1022,41 +1146,15 @@ static int process_renames(struct merge_options *o,
                                        b = ren1->pair->two;
                                        a = &src_other;
                                }
-                               mfi = merge_file(o, one, a, b,
-                                               o->branch1, o->branch2);
-
-                               if (mfi.clean &&
-                                   sha_eq(mfi.sha, ren1->pair->two->sha1) &&
-                                   mfi.mode == ren1->pair->two->mode) {
-                                       /*
-                                        * This message is part of
-                                        * t6022 test. If you change
-                                        * it update the test too.
-                                        */
-                                       output(o, 3, "Skipped %s (merged same as existing)", ren1_dst);
-
-                                       /* There may be higher stage entries left
-                                        * in the index (e.g. due to a D/F
-                                        * conflict) that need to be resolved.
-                                        */
-                                       if (!ren1->dst_entry->stages[2].mode !=
-                                           !ren1->dst_entry->stages[3].mode)
-                                               ren1->dst_entry->processed = 0;
-                               } else {
-                                       if (mfi.merge || !mfi.clean)
-                                               output(o, 1, "Renaming %s => %s", ren1_src, ren1_dst);
-                                       if (mfi.merge)
-                                               output(o, 2, "Auto-merging %s", ren1_dst);
-                                       if (!mfi.clean) {
-                                               output(o, 1, "CONFLICT (rename/modify): Merge conflict in %s",
-                                                      ren1_dst);
-                                               clean_merge = 0;
-
-                                               if (!o->call_depth)
-                                                       update_stages(ren1_dst,
-                                                                     one, a, b, 1);
-                                       }
-                                       update_file(o, mfi.clean, mfi.sha, mfi.mode, ren1_dst);
+                               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);
                                }
                        }
                }
@@ -1119,6 +1217,90 @@ error_return:
        return ret;
 }
 
+static void handle_delete_modify(struct merge_options *o,
+                                const char *path,
+                                const char *new_path,
+                                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);
+       }
+}
+
+static int merge_content(struct merge_options *o,
+                        const char *path,
+                        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)
+{
+       const char *reason = "content";
+       struct merge_file_info mfi;
+       struct diff_filespec one, a, b;
+       struct stat st;
+       unsigned df_conflict_remains = 0;
+
+       if (!o_sha) {
+               reason = "add/add";
+               o_sha = (unsigned char *)null_sha1;
+       }
+       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;
+
+       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 (mfi.clean && !df_conflict_remains &&
+           sha_eq(mfi.sha, a_sha) && mfi.mode == a.mode)
+               output(o, 3, "Skipped %s (merged same as existing)", path);
+       else
+               output(o, 2, "Auto-merging %s", path);
+
+       if (!mfi.clean) {
+               if (S_ISGITLINK(mfi.mode))
+                       reason = "submodule";
+               output(o, 1, "CONFLICT (%s): Merge conflict in %s",
+                               reason, path);
+       }
+
+       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;
+               output(o, 1, "Adding as %s instead", new_path);
+               update_file_flags(o, mfi.sha, mfi.mode, new_path, 0, 1);
+       } else {
+               update_file(o, mfi.clean, mfi.sha, mfi.mode, path);
+       }
+       return mfi.clean;
+
+}
+
 /* Per entry merge function */
 static int process_entry(struct merge_options *o,
                         const char *path, struct stage_data *entry)
@@ -1136,6 +1318,9 @@ 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 */
@@ -1148,22 +1333,15 @@ static int process_entry(struct merge_options *o,
                                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;
-                       if (!a_sha) {
-                               output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
-                                      "and modified in %s. Version %s of %s left in tree.",
-                                      path, o->branch1,
-                                      o->branch2, o->branch2, path);
-                               update_file(o, 0, b_sha, b_mode, path);
-                       } else {
-                               output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
-                                      "and modified in %s. Version %s of %s left in tree.",
-                                      path, o->branch2,
-                                      o->branch1, o->branch1, path);
-                               update_file(o, 0, a_sha, a_mode, path);
-                       }
+                       handle_delete_modify(o, path, path,
+                                            a_sha, a_mode, b_sha, b_mode);
                }
 
        } else if ((!o_sha && a_sha && !b_sha) ||
@@ -1182,15 +1360,7 @@ static int process_entry(struct merge_options *o,
                if (string_list_has_string(&o->current_directory_set, path)) {
                        /* Handle D->F conflicts after all subfiles */
                        entry->processed = 0;
-                       /* But get any file out of the way now, so conflicted
-                        * entries below the directory of the same name can
-                        * be put in the working directory.
-                        */
-                       if (a_sha)
-                               output(o, 2, "Removing %s", path);
-                       /* do not touch working file if it did not exist */
-                       remove_file(o, 0, path, !a_sha);
-                       return 1; /* Assume clean till processed */
+                       return 1; /* Assume clean until processed */
                } else {
                        output(o, 2, "Adding %s", path);
                        update_file(o, 1, sha, mode, path);
@@ -1198,34 +1368,9 @@ static int process_entry(struct merge_options *o,
        } else if (a_sha && b_sha) {
                /* Case C: Added in both (check for same permissions) and */
                /* case D: Modified in both, but differently. */
-               const char *reason = "content";
-               struct merge_file_info mfi;
-               struct diff_filespec one, a, b;
-
-               if (!o_sha) {
-                       reason = "add/add";
-                       o_sha = (unsigned char *)null_sha1;
-               }
-               output(o, 2, "Auto-merging %s", path);
-               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;
-
-               mfi = merge_file(o, &one, &a, &b,
-                                o->branch1, o->branch2);
-
-               clean_merge = mfi.clean;
-               if (!mfi.clean) {
-                       if (S_ISGITLINK(mfi.mode))
-                               reason = "submodule";
-                       output(o, 1, "CONFLICT (%s): Merge conflict in %s",
-                                       reason, path);
-               }
-               update_file(o, mfi.clean, mfi.sha, mfi.mode, path);
+               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
@@ -1239,13 +1384,19 @@ static int process_entry(struct merge_options *o,
 }
 
 /*
- * Per entry merge function for D/F conflicts, to be called only after
- * all files below dir have been processed.  We do this because in the
- * cases we can cleanly resolve D/F conflicts, process_entry() can clean
- * out all the files below the directory for us.
+ * 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)
+                           const char *path, struct stage_data *entry)
 {
        int clean_merge = 1;
        unsigned o_mode = entry->stages[1].mode;
@@ -1254,43 +1405,91 @@ static int process_df_entry(struct merge_options *o,
        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);
-       const char *add_branch;
-       const char *other_branch;
-       unsigned mode;
-       const unsigned char *sha;
-       const char *conf;
        struct stat st;
 
-       /* We currently only handle D->F cases */
-       assert((!o_sha && a_sha && !b_sha) ||
-              (!o_sha && !a_sha && b_sha));
-
        entry->processed = 1;
-
-       if (a_sha) {
-               add_branch = o->branch1;
-               other_branch = o->branch2;
-               mode = a_mode;
-               sha = a_sha;
-               conf = "file/directory";
-       } else {
-               add_branch = o->branch2;
-               other_branch = o->branch1;
-               mode = b_mode;
-               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 (entry->rename_df_conflict_info) {
+               struct rename_df_conflict_info *conflict_info = entry->rename_df_conflict_info;
+               char *src;
+               switch (conflict_info->rename_type) {
+               case RENAME_NORMAL:
+                       clean_merge = merge_content(o, path,
+                                                   o_sha, o_mode, a_sha, a_mode, b_sha, b_mode,
+                                                   conflict_info->branch1);
+                       break;
+               case RENAME_DELETE:
+                       clean_merge = 0;
+                       conflict_rename_delete(o, conflict_info->pair1,
+                                              conflict_info->branch1,
+                                              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;
+                       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;
-               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);
-               remove_file(o, 0, path, 0);
-               update_file(o, 0, sha, mode, new_path);
+               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) */
+               const char *add_branch;
+               const char *other_branch;
+               unsigned mode;
+               const unsigned char *sha;
+               const char *conf;
+
+               if (a_sha) {
+                       add_branch = o->branch1;
+                       other_branch = o->branch2;
+                       mode = a_mode;
+                       sha = a_sha;
+                       conf = "file/directory";
+               } else {
+                       add_branch = o->branch2;
+                       other_branch = o->branch1;
+                       mode = b_mode;
+                       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);
+                       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);
+                       update_file(o, 0, sha, mode, new_path);
+               } else {
+                       output(o, 2, "Adding %s", path);
+                       update_file(o, 1, sha, mode, path);
+               }
        } else {
-               output(o, 2, "Adding %s", path);
-               update_file(o, 1, sha, mode, path);
+               entry->processed = 0;
+               return 1; /* not handled; assume clean until processed */
        }
 
        return clean_merge;
@@ -1335,6 +1534,7 @@ int merge_trees(struct merge_options *o,
                get_files_dirs(o, merge);
 
                entries = get_unmerged();
+               make_room_for_directories_of_df_conflicts(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);
@@ -1352,6 +1552,12 @@ int merge_trees(struct merge_options *o,
                                && !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)
+                               die("Unprocessed path??? %s",
+                                   entries->items[i].string);
+               }
 
                string_list_clear(re_merge, 0);
                string_list_clear(re_head, 0);
index 0031d78e8c98a32d61cd0dc0f939a033e24ed890..c6b6a3fe4cd94e48893b172c17b6e7df3bfa36f8 100644 (file)
@@ -32,6 +32,42 @@ static unsigned int hash_name(const char *name, int namelen)
        return hash;
 }
 
+static void hash_index_entry_directories(struct index_state *istate, struct cache_entry *ce)
+{
+       /*
+        * Throw each directory component in the hash for quick lookup
+        * during a git status. Directory components are stored with their
+        * closing slash.  Despite submodules being a directory, they never
+        * reach this point, because they are stored without a closing slash
+        * in the cache.
+        *
+        * Note that the cache_entry stored with the directory does not
+        * represent the directory itself.  It is a pointer to an existing
+        * filename, and its only purpose is to represent existence of the
+        * directory in the cache.  It is very possible multiple directory
+        * hash entries may point to the same cache_entry.
+        */
+       unsigned int hash;
+       void **pos;
+
+       const char *ptr = ce->name;
+       while (*ptr) {
+               while (*ptr && *ptr != '/')
+                       ++ptr;
+               if (*ptr == '/') {
+                       ++ptr;
+                       hash = hash_name(ce->name, ptr - ce->name);
+                       if (!lookup_hash(hash, &istate->name_hash)) {
+                               pos = insert_hash(hash, ce, &istate->name_hash);
+                               if (pos) {
+                                       ce->next = *pos;
+                                       *pos = ce;
+                               }
+                       }
+               }
+       }
+}
+
 static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
 {
        void **pos;
@@ -47,6 +83,9 @@ static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
                ce->next = *pos;
                *pos = ce;
        }
+
+       if (ignore_case)
+               hash_index_entry_directories(istate, ce);
 }
 
 static void lazy_init_name_hash(struct index_state *istate)
@@ -97,7 +136,21 @@ static int same_name(const struct cache_entry *ce, const char *name, int namelen
        if (len == namelen && !cache_name_compare(name, namelen, ce->name, len))
                return 1;
 
-       return icase && slow_same_name(name, namelen, ce->name, len);
+       if (!icase)
+               return 0;
+
+       /*
+        * If the entry we're comparing is a filename (no trailing slash), then compare
+        * the lengths exactly.
+        */
+       if (name[namelen - 1] != '/')
+               return slow_same_name(name, namelen, ce->name, len);
+
+       /*
+        * For a directory, we point to an arbitrary cache_entry filename.  Just
+        * make sure the directory portion matches.
+        */
+       return slow_same_name(name, namelen, ce->name, namelen < len ? namelen : len);
 }
 
 struct cache_entry *index_name_exists(struct index_state *istate, const char *name, int namelen, int icase)
@@ -115,5 +168,22 @@ struct cache_entry *index_name_exists(struct index_state *istate, const char *na
                }
                ce = ce->next;
        }
+
+       /*
+        * Might be a submodule.  Despite submodules being directories,
+        * they are stored in the name hash without a closing slash.
+        * When ignore_case is 1, directories are stored in the name hash
+        * with their closing slash.
+        *
+        * The side effect of this storage technique is we have need to
+        * remove the slash from name and perform the lookup again without
+        * the slash.  If a match is made, S_ISGITLINK(ce->mode) will be
+        * true.
+        */
+       if (icase && name[namelen - 1] == '/') {
+               ce = index_name_exists(istate, name, namelen - 1, icase);
+               if (ce && S_ISGITLINK(ce->ce_mode))
+                       return ce;
+       }
        return NULL;
 }
index dee6d62e72703dfa68b4a3fb8ed069ccdc2a5004..4c8984ede1e218d3e0aaadb6d8c72bfcafddeee9 100644 (file)
@@ -89,6 +89,5 @@ int notes_cache_put(struct notes_cache *c, unsigned char key_sha1[20],
 
        if (write_sha1_file(data, size, "blob", value_sha1) < 0)
                return -1;
-       add_note(&c->tree, key_sha1, value_sha1, NULL);
-       return 0;
+       return add_note(&c->tree, key_sha1, value_sha1, NULL);
 }
diff --git a/notes-merge.c b/notes-merge.c
new file mode 100644 (file)
index 0000000..71c4d45
--- /dev/null
@@ -0,0 +1,737 @@
+#include "cache.h"
+#include "commit.h"
+#include "refs.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "xdiff-interface.h"
+#include "ll-merge.h"
+#include "dir.h"
+#include "notes.h"
+#include "notes-merge.h"
+#include "strbuf.h"
+
+struct notes_merge_pair {
+       unsigned char obj[20], base[20], local[20], remote[20];
+};
+
+void init_notes_merge_options(struct notes_merge_options *o)
+{
+       memset(o, 0, sizeof(struct notes_merge_options));
+       strbuf_init(&(o->commit_msg), 0);
+       o->verbosity = NOTES_MERGE_VERBOSITY_DEFAULT;
+}
+
+#define OUTPUT(o, v, ...) \
+       do { \
+               if ((o)->verbosity >= (v)) { \
+                       printf(__VA_ARGS__); \
+                       puts(""); \
+               } \
+       } while (0)
+
+static int path_to_sha1(const char *path, unsigned char *sha1)
+{
+       char hex_sha1[40];
+       int i = 0;
+       while (*path && i < 40) {
+               if (*path != '/')
+                       hex_sha1[i++] = *path;
+               path++;
+       }
+       if (*path || i != 40)
+               return -1;
+       return get_sha1_hex(hex_sha1, sha1);
+}
+
+static int verify_notes_filepair(struct diff_filepair *p, unsigned char *sha1)
+{
+       switch (p->status) {
+       case DIFF_STATUS_MODIFIED:
+               assert(p->one->mode == p->two->mode);
+               assert(!is_null_sha1(p->one->sha1));
+               assert(!is_null_sha1(p->two->sha1));
+               break;
+       case DIFF_STATUS_ADDED:
+               assert(is_null_sha1(p->one->sha1));
+               break;
+       case DIFF_STATUS_DELETED:
+               assert(is_null_sha1(p->two->sha1));
+               break;
+       default:
+               return -1;
+       }
+       assert(!strcmp(p->one->path, p->two->path));
+       return path_to_sha1(p->one->path, sha1);
+}
+
+static struct notes_merge_pair *find_notes_merge_pair_pos(
+               struct notes_merge_pair *list, int len, unsigned char *obj,
+               int insert_new, int *occupied)
+{
+       /*
+        * Both diff_tree_remote() and diff_tree_local() tend to process
+        * merge_pairs in ascending order. Therefore, cache last returned
+        * index, and search sequentially from there until the appropriate
+        * position is found.
+        *
+        * Since inserts only happen from diff_tree_remote() (which mainly
+        * _appends_), we don't care that inserting into the middle of the
+        * list is expensive (using memmove()).
+        */
+       static int last_index;
+       int i = last_index < len ? last_index : len - 1;
+       int prev_cmp = 0, cmp = -1;
+       while (i >= 0 && i < len) {
+               cmp = hashcmp(obj, list[i].obj);
+               if (!cmp) /* obj belongs @ i */
+                       break;
+               else if (cmp < 0 && prev_cmp <= 0) /* obj belongs < i */
+                       i--;
+               else if (cmp < 0) /* obj belongs between i-1 and i */
+                       break;
+               else if (cmp > 0 && prev_cmp >= 0) /* obj belongs > i */
+                       i++;
+               else /* if (cmp > 0) */ { /* obj belongs between i and i+1 */
+                       i++;
+                       break;
+               }
+               prev_cmp = cmp;
+       }
+       if (i < 0)
+               i = 0;
+       /* obj belongs at, or immediately preceding, index i (0 <= i <= len) */
+
+       if (!cmp)
+               *occupied = 1;
+       else {
+               *occupied = 0;
+               if (insert_new && i < len) {
+                       memmove(list + i + 1, list + i,
+                               (len - i) * sizeof(struct notes_merge_pair));
+                       memset(list + i, 0, sizeof(struct notes_merge_pair));
+               }
+       }
+       last_index = i;
+       return list + i;
+}
+
+static unsigned char uninitialized[20] =
+       "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" \
+       "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff";
+
+static struct notes_merge_pair *diff_tree_remote(struct notes_merge_options *o,
+                                                const unsigned char *base,
+                                                const unsigned char *remote,
+                                                int *num_changes)
+{
+       struct diff_options opt;
+       struct notes_merge_pair *changes;
+       int i, len = 0;
+
+       trace_printf("\tdiff_tree_remote(base = %.7s, remote = %.7s)\n",
+              sha1_to_hex(base), sha1_to_hex(remote));
+
+       diff_setup(&opt);
+       DIFF_OPT_SET(&opt, RECURSIVE);
+       opt.output_format = DIFF_FORMAT_NO_OUTPUT;
+       if (diff_setup_done(&opt) < 0)
+               die("diff_setup_done failed");
+       diff_tree_sha1(base, remote, "", &opt);
+       diffcore_std(&opt);
+
+       changes = xcalloc(diff_queued_diff.nr, sizeof(struct notes_merge_pair));
+
+       for (i = 0; i < diff_queued_diff.nr; i++) {
+               struct diff_filepair *p = diff_queued_diff.queue[i];
+               struct notes_merge_pair *mp;
+               int occupied;
+               unsigned char obj[20];
+
+               if (verify_notes_filepair(p, obj)) {
+                       trace_printf("\t\tCannot merge entry '%s' (%c): "
+                              "%.7s -> %.7s. Skipping!\n", p->one->path,
+                              p->status, sha1_to_hex(p->one->sha1),
+                              sha1_to_hex(p->two->sha1));
+                       continue;
+               }
+               mp = find_notes_merge_pair_pos(changes, len, obj, 1, &occupied);
+               if (occupied) {
+                       /* We've found an addition/deletion pair */
+                       assert(!hashcmp(mp->obj, obj));
+                       if (is_null_sha1(p->one->sha1)) { /* addition */
+                               assert(is_null_sha1(mp->remote));
+                               hashcpy(mp->remote, p->two->sha1);
+                       } else if (is_null_sha1(p->two->sha1)) { /* deletion */
+                               assert(is_null_sha1(mp->base));
+                               hashcpy(mp->base, p->one->sha1);
+                       } else
+                               assert(!"Invalid existing change recorded");
+               } else {
+                       hashcpy(mp->obj, obj);
+                       hashcpy(mp->base, p->one->sha1);
+                       hashcpy(mp->local, uninitialized);
+                       hashcpy(mp->remote, p->two->sha1);
+                       len++;
+               }
+               trace_printf("\t\tStored remote change for %s: %.7s -> %.7s\n",
+                      sha1_to_hex(mp->obj), sha1_to_hex(mp->base),
+                      sha1_to_hex(mp->remote));
+       }
+       diff_flush(&opt);
+       diff_tree_release_paths(&opt);
+
+       *num_changes = len;
+       return changes;
+}
+
+static void diff_tree_local(struct notes_merge_options *o,
+                           struct notes_merge_pair *changes, int len,
+                           const unsigned char *base,
+                           const unsigned char *local)
+{
+       struct diff_options opt;
+       int i;
+
+       trace_printf("\tdiff_tree_local(len = %i, base = %.7s, local = %.7s)\n",
+              len, sha1_to_hex(base), sha1_to_hex(local));
+
+       diff_setup(&opt);
+       DIFF_OPT_SET(&opt, RECURSIVE);
+       opt.output_format = DIFF_FORMAT_NO_OUTPUT;
+       if (diff_setup_done(&opt) < 0)
+               die("diff_setup_done failed");
+       diff_tree_sha1(base, local, "", &opt);
+       diffcore_std(&opt);
+
+       for (i = 0; i < diff_queued_diff.nr; i++) {
+               struct diff_filepair *p = diff_queued_diff.queue[i];
+               struct notes_merge_pair *mp;
+               int match;
+               unsigned char obj[20];
+
+               if (verify_notes_filepair(p, obj)) {
+                       trace_printf("\t\tCannot merge entry '%s' (%c): "
+                              "%.7s -> %.7s. Skipping!\n", p->one->path,
+                              p->status, sha1_to_hex(p->one->sha1),
+                              sha1_to_hex(p->two->sha1));
+                       continue;
+               }
+               mp = find_notes_merge_pair_pos(changes, len, obj, 0, &match);
+               if (!match) {
+                       trace_printf("\t\tIgnoring local-only change for %s: "
+                              "%.7s -> %.7s\n", sha1_to_hex(obj),
+                              sha1_to_hex(p->one->sha1),
+                              sha1_to_hex(p->two->sha1));
+                       continue;
+               }
+
+               assert(!hashcmp(mp->obj, obj));
+               if (is_null_sha1(p->two->sha1)) { /* deletion */
+                       /*
+                        * Either this is a true deletion (1), or it is part
+                        * of an A/D pair (2), or D/A pair (3):
+                        *
+                        * (1) mp->local is uninitialized; set it to null_sha1
+                        * (2) mp->local is not uninitialized; don't touch it
+                        * (3) mp->local is uninitialized; set it to null_sha1
+                        *     (will be overwritten by following addition)
+                        */
+                       if (!hashcmp(mp->local, uninitialized))
+                               hashclr(mp->local);
+               } else if (is_null_sha1(p->one->sha1)) { /* addition */
+                       /*
+                        * Either this is a true addition (1), or it is part
+                        * of an A/D pair (2), or D/A pair (3):
+                        *
+                        * (1) mp->local is uninitialized; set to p->two->sha1
+                        * (2) mp->local is uninitialized; set to p->two->sha1
+                        * (3) mp->local is null_sha1;     set to p->two->sha1
+                        */
+                       assert(is_null_sha1(mp->local) ||
+                              !hashcmp(mp->local, uninitialized));
+                       hashcpy(mp->local, p->two->sha1);
+               } else { /* modification */
+                       /*
+                        * This is a true modification. p->one->sha1 shall
+                        * match mp->base, and mp->local shall be uninitialized.
+                        * Set mp->local to p->two->sha1.
+                        */
+                       assert(!hashcmp(p->one->sha1, mp->base));
+                       assert(!hashcmp(mp->local, uninitialized));
+                       hashcpy(mp->local, p->two->sha1);
+               }
+               trace_printf("\t\tStored local change for %s: %.7s -> %.7s\n",
+                      sha1_to_hex(mp->obj), sha1_to_hex(mp->base),
+                      sha1_to_hex(mp->local));
+       }
+       diff_flush(&opt);
+       diff_tree_release_paths(&opt);
+}
+
+static void check_notes_merge_worktree(struct notes_merge_options *o)
+{
+       if (!o->has_worktree) {
+               /*
+                * Must establish NOTES_MERGE_WORKTREE.
+                * Abort if NOTES_MERGE_WORKTREE already exists
+                */
+               if (file_exists(git_path(NOTES_MERGE_WORKTREE))) {
+                       if (advice_resolve_conflict)
+                               die("You have not concluded your previous "
+                                   "notes merge (%s exists).\nPlease, use "
+                                   "'git notes merge --commit' or 'git notes "
+                                   "merge --abort' to commit/abort the "
+                                   "previous merge before you start a new "
+                                   "notes merge.", git_path("NOTES_MERGE_*"));
+                       else
+                               die("You have not concluded your notes merge "
+                                   "(%s exists).", git_path("NOTES_MERGE_*"));
+               }
+
+               if (safe_create_leading_directories(git_path(
+                               NOTES_MERGE_WORKTREE "/.test")))
+                       die_errno("unable to create directory %s",
+                                 git_path(NOTES_MERGE_WORKTREE));
+               o->has_worktree = 1;
+       } else if (!file_exists(git_path(NOTES_MERGE_WORKTREE)))
+               /* NOTES_MERGE_WORKTREE should already be established */
+               die("missing '%s'. This should not happen",
+                   git_path(NOTES_MERGE_WORKTREE));
+}
+
+static void write_buf_to_worktree(const unsigned char *obj,
+                                 const char *buf, unsigned long size)
+{
+       int fd;
+       char *path = git_path(NOTES_MERGE_WORKTREE "/%s", sha1_to_hex(obj));
+       if (safe_create_leading_directories(path))
+               die_errno("unable to create directory for '%s'", path);
+       if (file_exists(path))
+               die("found existing file at '%s'", path);
+
+       fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, 0666);
+       if (fd < 0)
+               die_errno("failed to open '%s'", path);
+
+       while (size > 0) {
+               long ret = write_in_full(fd, buf, size);
+               if (ret < 0) {
+                       /* Ignore epipe */
+                       if (errno == EPIPE)
+                               break;
+                       die_errno("notes-merge");
+               } else if (!ret) {
+                       die("notes-merge: disk full?");
+               }
+               size -= ret;
+               buf += ret;
+       }
+
+       close(fd);
+}
+
+static void write_note_to_worktree(const unsigned char *obj,
+                                  const unsigned char *note)
+{
+       enum object_type type;
+       unsigned long size;
+       void *buf = read_sha1_file(note, &type, &size);
+
+       if (!buf)
+               die("cannot read note %s for object %s",
+                   sha1_to_hex(note), sha1_to_hex(obj));
+       if (type != OBJ_BLOB)
+               die("blob expected in note %s for object %s",
+                   sha1_to_hex(note), sha1_to_hex(obj));
+       write_buf_to_worktree(obj, buf, size);
+       free(buf);
+}
+
+static int ll_merge_in_worktree(struct notes_merge_options *o,
+                               struct notes_merge_pair *p)
+{
+       mmbuffer_t result_buf;
+       mmfile_t base, local, remote;
+       int status;
+
+       read_mmblob(&base, p->base);
+       read_mmblob(&local, p->local);
+       read_mmblob(&remote, p->remote);
+
+       status = ll_merge(&result_buf, sha1_to_hex(p->obj), &base, NULL,
+                         &local, o->local_ref, &remote, o->remote_ref, 0);
+
+       free(base.ptr);
+       free(local.ptr);
+       free(remote.ptr);
+
+       if ((status < 0) || !result_buf.ptr)
+               die("Failed to execute internal merge");
+
+       write_buf_to_worktree(p->obj, result_buf.ptr, result_buf.size);
+       free(result_buf.ptr);
+
+       return status;
+}
+
+static int merge_one_change_manual(struct notes_merge_options *o,
+                                  struct notes_merge_pair *p,
+                                  struct notes_tree *t)
+{
+       const char *lref = o->local_ref ? o->local_ref : "local version";
+       const char *rref = o->remote_ref ? o->remote_ref : "remote version";
+
+       trace_printf("\t\t\tmerge_one_change_manual(obj = %.7s, base = %.7s, "
+              "local = %.7s, remote = %.7s)\n",
+              sha1_to_hex(p->obj), sha1_to_hex(p->base),
+              sha1_to_hex(p->local), sha1_to_hex(p->remote));
+
+       /* add "Conflicts:" section to commit message first time through */
+       if (!o->has_worktree)
+               strbuf_addstr(&(o->commit_msg), "\n\nConflicts:\n");
+
+       strbuf_addf(&(o->commit_msg), "\t%s\n", sha1_to_hex(p->obj));
+
+       OUTPUT(o, 2, "Auto-merging notes for %s", sha1_to_hex(p->obj));
+       check_notes_merge_worktree(o);
+       if (is_null_sha1(p->local)) {
+               /* D/F conflict, checkout p->remote */
+               assert(!is_null_sha1(p->remote));
+               OUTPUT(o, 1, "CONFLICT (delete/modify): Notes for object %s "
+                      "deleted in %s and modified in %s. Version from %s "
+                      "left in tree.", sha1_to_hex(p->obj), lref, rref, rref);
+               write_note_to_worktree(p->obj, p->remote);
+       } else if (is_null_sha1(p->remote)) {
+               /* D/F conflict, checkout p->local */
+               assert(!is_null_sha1(p->local));
+               OUTPUT(o, 1, "CONFLICT (delete/modify): Notes for object %s "
+                      "deleted in %s and modified in %s. Version from %s "
+                      "left in tree.", sha1_to_hex(p->obj), rref, lref, lref);
+               write_note_to_worktree(p->obj, p->local);
+       } else {
+               /* "regular" conflict, checkout result of ll_merge() */
+               const char *reason = "content";
+               if (is_null_sha1(p->base))
+                       reason = "add/add";
+               assert(!is_null_sha1(p->local));
+               assert(!is_null_sha1(p->remote));
+               OUTPUT(o, 1, "CONFLICT (%s): Merge conflict in notes for "
+                      "object %s", reason, sha1_to_hex(p->obj));
+               ll_merge_in_worktree(o, p);
+       }
+
+       trace_printf("\t\t\tremoving from partial merge result\n");
+       remove_note(t, p->obj);
+
+       return 1;
+}
+
+static int merge_one_change(struct notes_merge_options *o,
+                           struct notes_merge_pair *p, struct notes_tree *t)
+{
+       /*
+        * Return 0 if change is successfully resolved (stored in notes_tree).
+        * Return 1 is change results in a conflict (NOT stored in notes_tree,
+        * but instead written to NOTES_MERGE_WORKTREE with conflict markers).
+        */
+       switch (o->strategy) {
+       case NOTES_MERGE_RESOLVE_MANUAL:
+               return merge_one_change_manual(o, p, t);
+       case NOTES_MERGE_RESOLVE_OURS:
+               OUTPUT(o, 2, "Using local notes for %s", sha1_to_hex(p->obj));
+               /* nothing to do */
+               return 0;
+       case NOTES_MERGE_RESOLVE_THEIRS:
+               OUTPUT(o, 2, "Using remote notes for %s", sha1_to_hex(p->obj));
+               if (add_note(t, p->obj, p->remote, combine_notes_overwrite))
+                       die("BUG: combine_notes_overwrite failed");
+               return 0;
+       case NOTES_MERGE_RESOLVE_UNION:
+               OUTPUT(o, 2, "Concatenating local and remote notes for %s",
+                      sha1_to_hex(p->obj));
+               if (add_note(t, p->obj, p->remote, combine_notes_concatenate))
+                       die("failed to concatenate notes "
+                           "(combine_notes_concatenate)");
+               return 0;
+       case NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ:
+               OUTPUT(o, 2, "Concatenating unique lines in local and remote "
+                      "notes for %s", sha1_to_hex(p->obj));
+               if (add_note(t, p->obj, p->remote, combine_notes_cat_sort_uniq))
+                       die("failed to concatenate notes "
+                           "(combine_notes_cat_sort_uniq)");
+               return 0;
+       }
+       die("Unknown strategy (%i).", o->strategy);
+}
+
+static int merge_changes(struct notes_merge_options *o,
+                        struct notes_merge_pair *changes, int *num_changes,
+                        struct notes_tree *t)
+{
+       int i, conflicts = 0;
+
+       trace_printf("\tmerge_changes(num_changes = %i)\n", *num_changes);
+       for (i = 0; i < *num_changes; i++) {
+               struct notes_merge_pair *p = changes + i;
+               trace_printf("\t\t%.7s: %.7s -> %.7s/%.7s\n",
+                      sha1_to_hex(p->obj), sha1_to_hex(p->base),
+                      sha1_to_hex(p->local), sha1_to_hex(p->remote));
+
+               if (!hashcmp(p->base, p->remote)) {
+                       /* no remote change; nothing to do */
+                       trace_printf("\t\t\tskipping (no remote change)\n");
+               } else if (!hashcmp(p->local, p->remote)) {
+                       /* same change in local and remote; nothing to do */
+                       trace_printf("\t\t\tskipping (local == remote)\n");
+               } else if (!hashcmp(p->local, uninitialized) ||
+                          !hashcmp(p->local, p->base)) {
+                       /* no local change; adopt remote change */
+                       trace_printf("\t\t\tno local change, adopted remote\n");
+                       if (add_note(t, p->obj, p->remote,
+                                    combine_notes_overwrite))
+                               die("BUG: combine_notes_overwrite failed");
+               } else {
+                       /* need file-level merge between local and remote */
+                       trace_printf("\t\t\tneed content-level merge\n");
+                       conflicts += merge_one_change(o, p, t);
+               }
+       }
+
+       return conflicts;
+}
+
+static int merge_from_diffs(struct notes_merge_options *o,
+                           const unsigned char *base,
+                           const unsigned char *local,
+                           const unsigned char *remote, struct notes_tree *t)
+{
+       struct notes_merge_pair *changes;
+       int num_changes, conflicts;
+
+       trace_printf("\tmerge_from_diffs(base = %.7s, local = %.7s, "
+              "remote = %.7s)\n", sha1_to_hex(base), sha1_to_hex(local),
+              sha1_to_hex(remote));
+
+       changes = diff_tree_remote(o, base, remote, &num_changes);
+       diff_tree_local(o, changes, num_changes, base, local);
+
+       conflicts = merge_changes(o, changes, &num_changes, t);
+       free(changes);
+
+       OUTPUT(o, 4, "Merge result: %i unmerged notes and a %s notes tree",
+              conflicts, t->dirty ? "dirty" : "clean");
+
+       return conflicts ? -1 : 1;
+}
+
+void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
+                        const char *msg, unsigned char *result_sha1)
+{
+       unsigned char tree_sha1[20];
+
+       assert(t->initialized);
+
+       if (write_notes_tree(t, tree_sha1))
+               die("Failed to write notes tree to database");
+
+       if (!parents) {
+               /* Deduce parent commit from t->ref */
+               unsigned char parent_sha1[20];
+               if (!read_ref(t->ref, parent_sha1)) {
+                       struct commit *parent = lookup_commit(parent_sha1);
+                       if (!parent || parse_commit(parent))
+                               die("Failed to find/parse commit %s", t->ref);
+                       commit_list_insert(parent, &parents);
+               }
+               /* else: t->ref points to nothing, assume root/orphan commit */
+       }
+
+       if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL))
+               die("Failed to commit notes tree to database");
+}
+
+int notes_merge(struct notes_merge_options *o,
+               struct notes_tree *local_tree,
+               unsigned char *result_sha1)
+{
+       unsigned char local_sha1[20], remote_sha1[20];
+       struct commit *local, *remote;
+       struct commit_list *bases = NULL;
+       const unsigned char *base_sha1, *base_tree_sha1;
+       int result = 0;
+
+       assert(o->local_ref && o->remote_ref);
+       assert(!strcmp(o->local_ref, local_tree->ref));
+       hashclr(result_sha1);
+
+       trace_printf("notes_merge(o->local_ref = %s, o->remote_ref = %s)\n",
+              o->local_ref, o->remote_ref);
+
+       /* Dereference o->local_ref into local_sha1 */
+       if (!resolve_ref(o->local_ref, local_sha1, 0, NULL))
+               die("Failed to resolve local notes ref '%s'", o->local_ref);
+       else if (!check_ref_format(o->local_ref) && is_null_sha1(local_sha1))
+               local = NULL; /* local_sha1 == null_sha1 indicates unborn ref */
+       else if (!(local = lookup_commit_reference(local_sha1)))
+               die("Could not parse local commit %s (%s)",
+                   sha1_to_hex(local_sha1), o->local_ref);
+       trace_printf("\tlocal commit: %.7s\n", sha1_to_hex(local_sha1));
+
+       /* Dereference o->remote_ref into remote_sha1 */
+       if (get_sha1(o->remote_ref, remote_sha1)) {
+               /*
+                * Failed to get remote_sha1. If o->remote_ref looks like an
+                * unborn ref, perform the merge using an empty notes tree.
+                */
+               if (!check_ref_format(o->remote_ref)) {
+                       hashclr(remote_sha1);
+                       remote = NULL;
+               } else {
+                       die("Failed to resolve remote notes ref '%s'",
+                           o->remote_ref);
+               }
+       } else if (!(remote = lookup_commit_reference(remote_sha1))) {
+               die("Could not parse remote commit %s (%s)",
+                   sha1_to_hex(remote_sha1), o->remote_ref);
+       }
+       trace_printf("\tremote commit: %.7s\n", sha1_to_hex(remote_sha1));
+
+       if (!local && !remote)
+               die("Cannot merge empty notes ref (%s) into empty notes ref "
+                   "(%s)", o->remote_ref, o->local_ref);
+       if (!local) {
+               /* result == remote commit */
+               hashcpy(result_sha1, remote_sha1);
+               goto found_result;
+       }
+       if (!remote) {
+               /* result == local commit */
+               hashcpy(result_sha1, local_sha1);
+               goto found_result;
+       }
+       assert(local && remote);
+
+       /* Find merge bases */
+       bases = get_merge_bases(local, remote, 1);
+       if (!bases) {
+               base_sha1 = null_sha1;
+               base_tree_sha1 = (unsigned char *)EMPTY_TREE_SHA1_BIN;
+               OUTPUT(o, 4, "No merge base found; doing history-less merge");
+       } else if (!bases->next) {
+               base_sha1 = bases->item->object.sha1;
+               base_tree_sha1 = bases->item->tree->object.sha1;
+               OUTPUT(o, 4, "One merge base found (%.7s)",
+                      sha1_to_hex(base_sha1));
+       } else {
+               /* TODO: How to handle multiple merge-bases? */
+               base_sha1 = bases->item->object.sha1;
+               base_tree_sha1 = bases->item->tree->object.sha1;
+               OUTPUT(o, 3, "Multiple merge bases found. Using the first "
+                      "(%.7s)", sha1_to_hex(base_sha1));
+       }
+
+       OUTPUT(o, 4, "Merging remote commit %.7s into local commit %.7s with "
+              "merge-base %.7s", sha1_to_hex(remote->object.sha1),
+              sha1_to_hex(local->object.sha1), sha1_to_hex(base_sha1));
+
+       if (!hashcmp(remote->object.sha1, base_sha1)) {
+               /* Already merged; result == local commit */
+               OUTPUT(o, 2, "Already up-to-date!");
+               hashcpy(result_sha1, local->object.sha1);
+               goto found_result;
+       }
+       if (!hashcmp(local->object.sha1, base_sha1)) {
+               /* Fast-forward; result == remote commit */
+               OUTPUT(o, 2, "Fast-forward");
+               hashcpy(result_sha1, remote->object.sha1);
+               goto found_result;
+       }
+
+       result = merge_from_diffs(o, base_tree_sha1, local->tree->object.sha1,
+                                 remote->tree->object.sha1, local_tree);
+
+       if (result != 0) { /* non-trivial merge (with or without conflicts) */
+               /* Commit (partial) result */
+               struct commit_list *parents = NULL;
+               commit_list_insert(remote, &parents); /* LIFO order */
+               commit_list_insert(local, &parents);
+               create_notes_commit(local_tree, parents, o->commit_msg.buf,
+                                   result_sha1);
+       }
+
+found_result:
+       free_commit_list(bases);
+       strbuf_release(&(o->commit_msg));
+       trace_printf("notes_merge(): result = %i, result_sha1 = %.7s\n",
+              result, sha1_to_hex(result_sha1));
+       return result;
+}
+
+int notes_merge_commit(struct notes_merge_options *o,
+                      struct notes_tree *partial_tree,
+                      struct commit *partial_commit,
+                      unsigned char *result_sha1)
+{
+       /*
+        * Iterate through files in .git/NOTES_MERGE_WORKTREE and add all
+        * found notes to 'partial_tree'. Write the updates notes tree to
+        * the DB, and commit the resulting tree object while reusing the
+        * commit message and parents from 'partial_commit'.
+        * Finally store the new commit object SHA1 into 'result_sha1'.
+        */
+       struct dir_struct dir;
+       const char *path = git_path(NOTES_MERGE_WORKTREE "/");
+       int path_len = strlen(path), i;
+       const char *msg = strstr(partial_commit->buffer, "\n\n");
+
+       OUTPUT(o, 3, "Committing notes in notes merge worktree at %.*s",
+              path_len - 1, path);
+
+       if (!msg || msg[2] == '\0')
+               die("partial notes commit has empty message");
+       msg += 2;
+
+       memset(&dir, 0, sizeof(dir));
+       read_directory(&dir, path, path_len, NULL);
+       for (i = 0; i < dir.nr; i++) {
+               struct dir_entry *ent = dir.entries[i];
+               struct stat st;
+               const char *relpath = ent->name + path_len;
+               unsigned char obj_sha1[20], blob_sha1[20];
+
+               if (ent->len - path_len != 40 || get_sha1_hex(relpath, obj_sha1)) {
+                       OUTPUT(o, 3, "Skipping non-SHA1 entry '%s'", ent->name);
+                       continue;
+               }
+
+               /* write file as blob, and add to partial_tree */
+               if (stat(ent->name, &st))
+                       die_errno("Failed to stat '%s'", ent->name);
+               if (index_path(blob_sha1, ent->name, &st, 1))
+                       die("Failed to write blob object from '%s'", ent->name);
+               if (add_note(partial_tree, obj_sha1, blob_sha1, NULL))
+                       die("Failed to add resolved note '%s' to notes tree",
+                           ent->name);
+               OUTPUT(o, 4, "Added resolved note for object %s: %s",
+                      sha1_to_hex(obj_sha1), sha1_to_hex(blob_sha1));
+       }
+
+       create_notes_commit(partial_tree, partial_commit->parents, msg,
+                           result_sha1);
+       OUTPUT(o, 4, "Finalized notes merge commit: %s",
+              sha1_to_hex(result_sha1));
+       return 0;
+}
+
+int notes_merge_abort(struct notes_merge_options *o)
+{
+       /* Remove .git/NOTES_MERGE_WORKTREE directory and all files within */
+       struct strbuf buf = STRBUF_INIT;
+       int ret;
+
+       strbuf_addstr(&buf, git_path(NOTES_MERGE_WORKTREE));
+       OUTPUT(o, 3, "Removing notes merge worktree at %s", buf.buf);
+       ret = remove_dir_recursively(&buf, 0);
+       strbuf_release(&buf);
+       return ret;
+}
diff --git a/notes-merge.h b/notes-merge.h
new file mode 100644 (file)
index 0000000..168a672
--- /dev/null
@@ -0,0 +1,98 @@
+#ifndef NOTES_MERGE_H
+#define NOTES_MERGE_H
+
+#define NOTES_MERGE_WORKTREE "NOTES_MERGE_WORKTREE"
+
+enum notes_merge_verbosity {
+       NOTES_MERGE_VERBOSITY_DEFAULT = 2,
+       NOTES_MERGE_VERBOSITY_MAX = 5
+};
+
+struct notes_merge_options {
+       const char *local_ref;
+       const char *remote_ref;
+       struct strbuf commit_msg;
+       int verbosity;
+       enum {
+               NOTES_MERGE_RESOLVE_MANUAL = 0,
+               NOTES_MERGE_RESOLVE_OURS,
+               NOTES_MERGE_RESOLVE_THEIRS,
+               NOTES_MERGE_RESOLVE_UNION,
+               NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ
+       } strategy;
+       unsigned has_worktree:1;
+};
+
+void init_notes_merge_options(struct notes_merge_options *o);
+
+/*
+ * Create new notes commit from the given notes tree
+ *
+ * Properties of the created commit:
+ * - tree: the result of converting t to a tree object with write_notes_tree().
+ * - parents: the given parents OR (if NULL) the commit referenced by t->ref.
+ * - author/committer: the default determined by commmit_tree().
+ * - commit message: msg
+ *
+ * The resulting commit SHA1 is stored in result_sha1.
+ */
+void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
+                        const char *msg, unsigned char *result_sha1);
+
+/*
+ * Merge notes from o->remote_ref into o->local_ref
+ *
+ * The given notes_tree 'local_tree' must be the notes_tree referenced by the
+ * o->local_ref. This is the notes_tree in which the object-level merge is
+ * performed.
+ *
+ * The commits given by the two refs are merged, producing one of the following
+ * outcomes:
+ *
+ * 1. The merge trivially results in an existing commit (e.g. fast-forward or
+ *    already-up-to-date). 'local_tree' is untouched, the SHA1 of the result
+ *    is written into 'result_sha1' and 0 is returned.
+ * 2. The merge successfully completes, producing a merge commit. local_tree
+ *    contains the updated notes tree, the SHA1 of the resulting commit is
+ *    written into 'result_sha1', and 1 is returned.
+ * 3. The merge results in conflicts. This is similar to #2 in that the
+ *    partial merge result (i.e. merge result minus the unmerged entries)
+ *    are stored in 'local_tree', and the SHA1 or the resulting commit
+ *    (to be amended when the conflicts have been resolved) is written into
+ *    'result_sha1'. The unmerged entries are written into the
+ *    .git/NOTES_MERGE_WORKTREE directory with conflict markers.
+ *    -1 is returned.
+ *
+ * Both o->local_ref and o->remote_ref must be given (non-NULL), but either ref
+ * (although not both) may refer to a non-existing notes ref, in which case
+ * that notes ref is interpreted as an empty notes tree, and the merge
+ * trivially results in what the other ref points to.
+ */
+int notes_merge(struct notes_merge_options *o,
+               struct notes_tree *local_tree,
+               unsigned char *result_sha1);
+
+/*
+ * Finalize conflict resolution from an earlier notes_merge()
+ *
+ * The given notes tree 'partial_tree' must be the notes_tree corresponding to
+ * the given 'partial_commit', the partial result commit created by a previous
+ * call to notes_merge().
+ *
+ * This function will add the (now resolved) notes in .git/NOTES_MERGE_WORKTREE
+ * to 'partial_tree', and create a final notes merge commit, the SHA1 of which
+ * will be stored in 'result_sha1'.
+ */
+int notes_merge_commit(struct notes_merge_options *o,
+                      struct notes_tree *partial_tree,
+                      struct commit *partial_commit,
+                      unsigned char *result_sha1);
+
+/*
+ * Abort conflict resolution from an earlier notes_merge()
+ *
+ * Removes the notes merge worktree in .git/NOTES_MERGE_WORKTREE.
+ */
+int notes_merge_abort(struct notes_merge_options *o);
+
+#endif
diff --git a/notes.c b/notes.c
index 70d00135eb5b67cd6f21b416cde2ae4a1967fd0a..a013c1bc638dbf5a8111183a3f9d154721ec5e04 100644 (file)
--- a/notes.c
+++ b/notes.c
@@ -149,86 +149,6 @@ static struct leaf_node *note_tree_find(struct notes_tree *t,
        return NULL;
 }
 
-/*
- * To insert a leaf_node:
- * Search to the tree location appropriate for the given leaf_node's key:
- * - If location is unused (NULL), store the tweaked pointer directly there
- * - If location holds a note entry that matches the note-to-be-inserted, then
- *   combine the two notes (by calling the given combine_notes function).
- * - If location holds a note entry that matches the subtree-to-be-inserted,
- *   then unpack the subtree-to-be-inserted into the location.
- * - If location holds a matching subtree entry, unpack the subtree at that
- *   location, and restart the insert operation from that level.
- * - Else, create a new int_node, holding both the node-at-location and the
- *   node-to-be-inserted, and store the new int_node into the location.
- */
-static void note_tree_insert(struct notes_tree *t, struct int_node *tree,
-               unsigned char n, struct leaf_node *entry, unsigned char type,
-               combine_notes_fn combine_notes)
-{
-       struct int_node *new_node;
-       struct leaf_node *l;
-       void **p = note_tree_search(t, &tree, &n, entry->key_sha1);
-
-       assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */
-       l = (struct leaf_node *) CLR_PTR_TYPE(*p);
-       switch (GET_PTR_TYPE(*p)) {
-       case PTR_TYPE_NULL:
-               assert(!*p);
-               *p = SET_PTR_TYPE(entry, type);
-               return;
-       case PTR_TYPE_NOTE:
-               switch (type) {
-               case PTR_TYPE_NOTE:
-                       if (!hashcmp(l->key_sha1, entry->key_sha1)) {
-                               /* skip concatenation if l == entry */
-                               if (!hashcmp(l->val_sha1, entry->val_sha1))
-                                       return;
-
-                               if (combine_notes(l->val_sha1, entry->val_sha1))
-                                       die("failed to combine notes %s and %s"
-                                           " for object %s",
-                                           sha1_to_hex(l->val_sha1),
-                                           sha1_to_hex(entry->val_sha1),
-                                           sha1_to_hex(l->key_sha1));
-                               free(entry);
-                               return;
-                       }
-                       break;
-               case PTR_TYPE_SUBTREE:
-                       if (!SUBTREE_SHA1_PREFIXCMP(l->key_sha1,
-                                                   entry->key_sha1)) {
-                               /* unpack 'entry' */
-                               load_subtree(t, entry, tree, n);
-                               free(entry);
-                               return;
-                       }
-                       break;
-               }
-               break;
-       case PTR_TYPE_SUBTREE:
-               if (!SUBTREE_SHA1_PREFIXCMP(entry->key_sha1, l->key_sha1)) {
-                       /* unpack 'l' and restart insert */
-                       *p = NULL;
-                       load_subtree(t, l, tree, n);
-                       free(l);
-                       note_tree_insert(t, tree, n, entry, type,
-                                        combine_notes);
-                       return;
-               }
-               break;
-       }
-
-       /* non-matching leaf_node */
-       assert(GET_PTR_TYPE(*p) == PTR_TYPE_NOTE ||
-              GET_PTR_TYPE(*p) == PTR_TYPE_SUBTREE);
-       new_node = (struct int_node *) xcalloc(sizeof(struct int_node), 1);
-       note_tree_insert(t, new_node, n + 1, l, GET_PTR_TYPE(*p),
-                        combine_notes);
-       *p = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL);
-       note_tree_insert(t, new_node, n + 1, entry, type, combine_notes);
-}
-
 /*
  * How to consolidate an int_node:
  * If there are > 1 non-NULL entries, give up and return non-zero.
@@ -305,6 +225,93 @@ static void note_tree_remove(struct notes_tree *t,
                i--;
 }
 
+/*
+ * To insert a leaf_node:
+ * Search to the tree location appropriate for the given leaf_node's key:
+ * - If location is unused (NULL), store the tweaked pointer directly there
+ * - If location holds a note entry that matches the note-to-be-inserted, then
+ *   combine the two notes (by calling the given combine_notes function).
+ * - If location holds a note entry that matches the subtree-to-be-inserted,
+ *   then unpack the subtree-to-be-inserted into the location.
+ * - If location holds a matching subtree entry, unpack the subtree at that
+ *   location, and restart the insert operation from that level.
+ * - Else, create a new int_node, holding both the node-at-location and the
+ *   node-to-be-inserted, and store the new int_node into the location.
+ */
+static int note_tree_insert(struct notes_tree *t, struct int_node *tree,
+               unsigned char n, struct leaf_node *entry, unsigned char type,
+               combine_notes_fn combine_notes)
+{
+       struct int_node *new_node;
+       struct leaf_node *l;
+       void **p = note_tree_search(t, &tree, &n, entry->key_sha1);
+       int ret = 0;
+
+       assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */
+       l = (struct leaf_node *) CLR_PTR_TYPE(*p);
+       switch (GET_PTR_TYPE(*p)) {
+       case PTR_TYPE_NULL:
+               assert(!*p);
+               if (is_null_sha1(entry->val_sha1))
+                       free(entry);
+               else
+                       *p = SET_PTR_TYPE(entry, type);
+               return 0;
+       case PTR_TYPE_NOTE:
+               switch (type) {
+               case PTR_TYPE_NOTE:
+                       if (!hashcmp(l->key_sha1, entry->key_sha1)) {
+                               /* skip concatenation if l == entry */
+                               if (!hashcmp(l->val_sha1, entry->val_sha1))
+                                       return 0;
+
+                               ret = combine_notes(l->val_sha1,
+                                                   entry->val_sha1);
+                               if (!ret && is_null_sha1(l->val_sha1))
+                                       note_tree_remove(t, tree, n, entry);
+                               free(entry);
+                               return ret;
+                       }
+                       break;
+               case PTR_TYPE_SUBTREE:
+                       if (!SUBTREE_SHA1_PREFIXCMP(l->key_sha1,
+                                                   entry->key_sha1)) {
+                               /* unpack 'entry' */
+                               load_subtree(t, entry, tree, n);
+                               free(entry);
+                               return 0;
+                       }
+                       break;
+               }
+               break;
+       case PTR_TYPE_SUBTREE:
+               if (!SUBTREE_SHA1_PREFIXCMP(entry->key_sha1, l->key_sha1)) {
+                       /* unpack 'l' and restart insert */
+                       *p = NULL;
+                       load_subtree(t, l, tree, n);
+                       free(l);
+                       return note_tree_insert(t, tree, n, entry, type,
+                                               combine_notes);
+               }
+               break;
+       }
+
+       /* non-matching leaf_node */
+       assert(GET_PTR_TYPE(*p) == PTR_TYPE_NOTE ||
+              GET_PTR_TYPE(*p) == PTR_TYPE_SUBTREE);
+       if (is_null_sha1(entry->val_sha1)) { /* skip insertion of empty note */
+               free(entry);
+               return 0;
+       }
+       new_node = (struct int_node *) xcalloc(sizeof(struct int_node), 1);
+       ret = note_tree_insert(t, new_node, n + 1, l, GET_PTR_TYPE(*p),
+                              combine_notes);
+       if (ret)
+               return ret;
+       *p = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL);
+       return note_tree_insert(t, new_node, n + 1, entry, type, combine_notes);
+}
+
 /* Free the entire notes data contained in the given tree */
 static void note_tree_free(struct int_node *tree)
 {
@@ -445,8 +452,12 @@ static void load_subtree(struct notes_tree *t, struct leaf_node *subtree,
                                l->key_sha1[19] = (unsigned char) len;
                                type = PTR_TYPE_SUBTREE;
                        }
-                       note_tree_insert(t, node, n, l, type,
-                                        combine_notes_concatenate);
+                       if (note_tree_insert(t, node, n, l, type,
+                                            combine_notes_concatenate))
+                               die("Failed to load %s %s into notes tree "
+                                   "from %s",
+                                   type == PTR_TYPE_NOTE ? "note" : "subtree",
+                                   sha1_to_hex(l->key_sha1), t->ref);
                }
                continue;
 
@@ -804,16 +815,17 @@ int combine_notes_concatenate(unsigned char *cur_sha1,
                return 0;
        }
 
-       /* we will separate the notes by a newline anyway */
+       /* we will separate the notes by two newlines anyway */
        if (cur_msg[cur_len - 1] == '\n')
                cur_len--;
 
        /* concatenate cur_msg and new_msg into buf */
-       buf_len = cur_len + 1 + new_len;
+       buf_len = cur_len + 2 + new_len;
        buf = (char *) xmalloc(buf_len);
        memcpy(buf, cur_msg, cur_len);
        buf[cur_len] = '\n';
-       memcpy(buf + cur_len + 1, new_msg, new_len);
+       buf[cur_len + 1] = '\n';
+       memcpy(buf + cur_len + 2, new_msg, new_len);
        free(cur_msg);
        free(new_msg);
 
@@ -836,6 +848,82 @@ int combine_notes_ignore(unsigned char *cur_sha1,
        return 0;
 }
 
+static int string_list_add_note_lines(struct string_list *sort_uniq_list,
+                                     const unsigned char *sha1)
+{
+       char *data;
+       unsigned long len;
+       enum object_type t;
+       struct strbuf buf = STRBUF_INIT;
+       struct strbuf **lines = NULL;
+       int i, list_index;
+
+       if (is_null_sha1(sha1))
+               return 0;
+
+       /* read_sha1_file NUL-terminates */
+       data = read_sha1_file(sha1, &t, &len);
+       if (t != OBJ_BLOB || !data || !len) {
+               free(data);
+               return t != OBJ_BLOB || !data;
+       }
+
+       strbuf_attach(&buf, data, len, len + 1);
+       lines = strbuf_split(&buf, '\n');
+
+       for (i = 0; lines[i]; i++) {
+               if (lines[i]->buf[lines[i]->len - 1] == '\n')
+                       strbuf_setlen(lines[i], lines[i]->len - 1);
+               if (!lines[i]->len)
+                       continue; /* skip empty lines */
+               list_index = string_list_find_insert_index(sort_uniq_list,
+                                                          lines[i]->buf, 0);
+               if (list_index < 0)
+                       continue; /* skip duplicate lines */
+               string_list_insert_at_index(sort_uniq_list, list_index,
+                                           lines[i]->buf);
+       }
+
+       strbuf_list_free(lines);
+       strbuf_release(&buf);
+       return 0;
+}
+
+static int string_list_join_lines_helper(struct string_list_item *item,
+                                        void *cb_data)
+{
+       struct strbuf *buf = cb_data;
+       strbuf_addstr(buf, item->string);
+       strbuf_addch(buf, '\n');
+       return 0;
+}
+
+int combine_notes_cat_sort_uniq(unsigned char *cur_sha1,
+               const unsigned char *new_sha1)
+{
+       struct string_list sort_uniq_list = { NULL, 0, 0, 1 };
+       struct strbuf buf = STRBUF_INIT;
+       int ret = 1;
+
+       /* read both note blob objects into unique_lines */
+       if (string_list_add_note_lines(&sort_uniq_list, cur_sha1))
+               goto out;
+       if (string_list_add_note_lines(&sort_uniq_list, new_sha1))
+               goto out;
+
+       /* create a new blob object from sort_uniq_list */
+       if (for_each_string_list(&sort_uniq_list,
+                                string_list_join_lines_helper, &buf))
+               goto out;
+
+       ret = write_sha1_file(buf.buf, buf.len, blob_type, cur_sha1);
+
+out:
+       strbuf_release(&buf);
+       string_list_clear(&sort_uniq_list, 0);
+       return ret;
+}
+
 static int string_list_add_one_ref(const char *path, const unsigned char *sha1,
                                   int flag, void *cb)
 {
@@ -893,7 +981,7 @@ static int notes_display_config(const char *k, const char *v, void *cb)
        return 0;
 }
 
-static const char *default_notes_ref(void)
+const char *default_notes_ref(void)
 {
        const char *notes_ref = NULL;
        if (!notes_ref)
@@ -935,7 +1023,7 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
                return;
        if (get_tree_entry(object_sha1, "", sha1, &mode))
                die("Failed to read notes tree referenced by %s (%s)",
-                   notes_ref, object_sha1);
+                   notes_ref, sha1_to_hex(object_sha1));
 
        hashclr(root_tree.key_sha1);
        hashcpy(root_tree.val_sha1, sha1);
@@ -989,7 +1077,7 @@ void init_display_notes(struct display_notes_opt *opt)
        string_list_clear(&display_notes_refs, 0);
 }
 
-void add_note(struct notes_tree *t, const unsigned char *object_sha1,
+int add_note(struct notes_tree *t, const unsigned char *object_sha1,
                const unsigned char *note_sha1, combine_notes_fn combine_notes)
 {
        struct leaf_node *l;
@@ -1003,7 +1091,7 @@ void add_note(struct notes_tree *t, const unsigned char *object_sha1,
        l = (struct leaf_node *) xmalloc(sizeof(struct leaf_node));
        hashcpy(l->key_sha1, object_sha1);
        hashcpy(l->val_sha1, note_sha1);
-       note_tree_insert(t, t->root, 0, l, PTR_TYPE_NOTE, combine_notes);
+       return note_tree_insert(t, t->root, 0, l, PTR_TYPE_NOTE, combine_notes);
 }
 
 int remove_note(struct notes_tree *t, const unsigned char *object_sha1)
@@ -1182,7 +1270,7 @@ void format_display_notes(const unsigned char *object_sha1,
 
 int copy_note(struct notes_tree *t,
              const unsigned char *from_obj, const unsigned char *to_obj,
-             int force, combine_notes_fn combine_fn)
+             int force, combine_notes_fn combine_notes)
 {
        const unsigned char *note = get_note(t, from_obj);
        const unsigned char *existing_note = get_note(t, to_obj);
@@ -1191,9 +1279,9 @@ int copy_note(struct notes_tree *t,
                return 1;
 
        if (note)
-               add_note(t, to_obj, note, combine_fn);
+               return add_note(t, to_obj, note, combine_notes);
        else if (existing_note)
-               add_note(t, to_obj, null_sha1, combine_fn);
+               return add_note(t, to_obj, null_sha1, combine_notes);
 
        return 0;
 }
diff --git a/notes.h b/notes.h
index 5106761534cfb6c5b97baede700e99b0aa5dfc3b..83bd6e0ec02a1a8650004f14cd7476eedbdff518 100644 (file)
--- a/notes.h
+++ b/notes.h
  * resulting SHA1 into the first SHA1 argument (cur_sha1). A non-zero return
  * value indicates failure.
  *
- * The two given SHA1s must both be non-NULL and different from each other.
+ * The two given SHA1s shall both be non-NULL and different from each other.
+ * Either of them (but not both) may be == null_sha1, which indicates an
+ * empty/non-existent note. If the resulting SHA1 (cur_sha1) is == null_sha1,
+ * the note will be removed from the notes tree.
  *
  * The default combine_notes function (you get this when passing NULL) is
  * combine_notes_concatenate(), which appends the contents of the new note to
@@ -24,6 +27,7 @@ typedef int (*combine_notes_fn)(unsigned char *cur_sha1, const unsigned char *ne
 int combine_notes_concatenate(unsigned char *cur_sha1, const unsigned char *new_sha1);
 int combine_notes_overwrite(unsigned char *cur_sha1, const unsigned char *new_sha1);
 int combine_notes_ignore(unsigned char *cur_sha1, const unsigned char *new_sha1);
+int combine_notes_cat_sort_uniq(unsigned char *cur_sha1, const unsigned char *new_sha1);
 
 /*
  * Notes tree object
@@ -43,6 +47,20 @@ extern struct notes_tree {
        int dirty;
 } default_notes_tree;
 
+/*
+ * Return the default notes ref.
+ *
+ * The default notes ref is the notes ref that is used when notes_ref == NULL
+ * is passed to init_notes().
+ *
+ * This the first of the following to be defined:
+ * 1. The '--ref' option to 'git notes', if given
+ * 2. The $GIT_NOTES_REF environment variable, if set
+ * 3. The value of the core.notesRef config variable, if set
+ * 4. GIT_NOTES_DEFAULT_REF (i.e. "refs/notes/commits")
+ */
+const char *default_notes_ref(void);
+
 /*
  * Flags controlling behaviour of notes tree initialization
  *
@@ -76,11 +94,24 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
 /*
  * Add the given note object to the given notes_tree structure
  *
+ * If there already exists a note for the given object_sha1, the given
+ * combine_notes function is invoked to break the tie. If not given (i.e.
+ * combine_notes == NULL), the default combine_notes function for the given
+ * notes_tree is used.
+ *
+ * Passing note_sha1 == null_sha1 indicates the addition of an
+ * empty/non-existent note. This is a (potentially expensive) no-op unless
+ * there already exists a note for the given object_sha1, AND combining that
+ * note with the empty note (using the given combine_notes function) results
+ * in a new/changed note.
+ *
+ * Returns zero on success; non-zero means combine_notes failed.
+ *
  * IMPORTANT: The changes made by add_note() to the given notes_tree structure
  * are not persistent until a subsequent call to write_notes_tree() returns
  * zero.
  */
-void add_note(struct notes_tree *t, const unsigned char *object_sha1,
+int add_note(struct notes_tree *t, const unsigned char *object_sha1,
                const unsigned char *note_sha1, combine_notes_fn combine_notes);
 
 /*
@@ -105,11 +136,18 @@ const unsigned char *get_note(struct notes_tree *t,
 /*
  * Copy a note from one object to another in the given notes_tree.
  *
- * Fails if the to_obj already has a note unless 'force' is true.
+ * Returns 1 if the to_obj already has a note and 'force' is false. Otherwise,
+ * returns non-zero if 'force' is true, but the given combine_notes function
+ * failed to combine from_obj's note with to_obj's existing note.
+ * Returns zero on success.
+ *
+ * IMPORTANT: The changes made by copy_note() to the given notes_tree structure
+ * are not persistent until a subsequent call to write_notes_tree() returns
+ * zero.
  */
 int copy_note(struct notes_tree *t,
              const unsigned char *from_obj, const unsigned char *to_obj,
-             int force, combine_notes_fn combine_fn);
+             int force, combine_notes_fn combine_notes);
 
 /*
  * Flags controlling behaviour of for_each_note()
@@ -151,6 +189,7 @@ int copy_note(struct notes_tree *t,
  * notes tree) from within the callback:
  * - add_note()
  * - remove_note()
+ * - copy_note()
  * - free_notes()
  */
 typedef int each_note_fn(const unsigned char *object_sha1,
diff --git a/path.c b/path.c
index a2c9d1e24afb9d1250c846791607c49d82e9565e..8951333cb88a6e28ef29538183ebf71e3b0913fa 100644 (file)
--- a/path.c
+++ b/path.c
@@ -161,119 +161,6 @@ char *git_path_submodule(const char *path, const char *fmt, ...)
        return cleanup_path(pathname);
 }
 
-/* git_mkstemp() - create tmp file honoring TMPDIR variable */
-int git_mkstemp(char *path, size_t len, const char *template)
-{
-       const char *tmp;
-       size_t n;
-
-       tmp = getenv("TMPDIR");
-       if (!tmp)
-               tmp = "/tmp";
-       n = snprintf(path, len, "%s/%s", tmp, template);
-       if (len <= n) {
-               errno = ENAMETOOLONG;
-               return -1;
-       }
-       return mkstemp(path);
-}
-
-/* git_mkstemps() - create tmp file with suffix honoring TMPDIR variable. */
-int git_mkstemps(char *path, size_t len, const char *template, int suffix_len)
-{
-       const char *tmp;
-       size_t n;
-
-       tmp = getenv("TMPDIR");
-       if (!tmp)
-               tmp = "/tmp";
-       n = snprintf(path, len, "%s/%s", tmp, template);
-       if (len <= n) {
-               errno = ENAMETOOLONG;
-               return -1;
-       }
-       return mkstemps(path, suffix_len);
-}
-
-/* Adapted from libiberty's mkstemp.c. */
-
-#undef TMP_MAX
-#define TMP_MAX 16384
-
-int git_mkstemps_mode(char *pattern, int suffix_len, int mode)
-{
-       static const char letters[] =
-               "abcdefghijklmnopqrstuvwxyz"
-               "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-               "0123456789";
-       static const int num_letters = 62;
-       uint64_t value;
-       struct timeval tv;
-       char *template;
-       size_t len;
-       int fd, count;
-
-       len = strlen(pattern);
-
-       if (len < 6 + suffix_len) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (strncmp(&pattern[len - 6 - suffix_len], "XXXXXX", 6)) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       /*
-        * Replace pattern's XXXXXX characters with randomness.
-        * Try TMP_MAX different filenames.
-        */
-       gettimeofday(&tv, NULL);
-       value = ((size_t)(tv.tv_usec << 16)) ^ tv.tv_sec ^ getpid();
-       template = &pattern[len - 6 - suffix_len];
-       for (count = 0; count < TMP_MAX; ++count) {
-               uint64_t v = value;
-               /* Fill in the random bits. */
-               template[0] = letters[v % num_letters]; v /= num_letters;
-               template[1] = letters[v % num_letters]; v /= num_letters;
-               template[2] = letters[v % num_letters]; v /= num_letters;
-               template[3] = letters[v % num_letters]; v /= num_letters;
-               template[4] = letters[v % num_letters]; v /= num_letters;
-               template[5] = letters[v % num_letters]; v /= num_letters;
-
-               fd = open(pattern, O_CREAT | O_EXCL | O_RDWR, mode);
-               if (fd > 0)
-                       return fd;
-               /*
-                * Fatal error (EPERM, ENOSPC etc).
-                * It doesn't make sense to loop.
-                */
-               if (errno != EEXIST)
-                       break;
-               /*
-                * This is a random value.  It is only necessary that
-                * the next TMP_MAX values generated by adding 7777 to
-                * VALUE are different with (module 2^32).
-                */
-               value += 7777;
-       }
-       /* We return the null string if we can't find a unique file name.  */
-       pattern[0] = '\0';
-       return -1;
-}
-
-int git_mkstemp_mode(char *pattern, int mode)
-{
-       /* mkstemp is just mkstemps with no suffix */
-       return git_mkstemps_mode(pattern, 0, mode);
-}
-
-int gitmkstemps(char *pattern, int suffix_len)
-{
-       return git_mkstemps_mode(pattern, suffix_len, 0600);
-}
-
 int validate_headref(const char *path)
 {
        struct stat st;
index f85444b27d4815547f7cef542291b1f6616f77d5..85499347514ec3e1d62d6caa9f53b886c556e345 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -403,8 +403,8 @@ static char *replace_encoding_header(char *buf, const char *encoding)
        return strbuf_detach(&tmp, NULL);
 }
 
-static char *logmsg_reencode(const struct commit *commit,
-                            const char *output_encoding)
+char *logmsg_reencode(const struct commit *commit,
+                     const char *output_encoding)
 {
        static const char *utf8 = "UTF-8";
        const char *use_encoding;
@@ -555,6 +555,7 @@ struct format_commit_context {
        const struct pretty_print_context *pretty_ctx;
        unsigned commit_header_parsed:1;
        unsigned commit_message_parsed:1;
+       char *message;
        size_t width, indent1, indent2;
 
        /* These offsets are relative to the start of the commit message. */
@@ -591,7 +592,7 @@ static int add_again(struct strbuf *sb, struct chunk *chunk)
 
 static void parse_commit_header(struct format_commit_context *context)
 {
-       const char *msg = context->commit->buffer;
+       const char *msg = context->message;
        int i;
 
        for (i = 0; msg[i]; i++) {
@@ -677,8 +678,8 @@ const char *format_subject(struct strbuf *sb, const char *msg,
 
 static void parse_commit_message(struct format_commit_context *c)
 {
-       const char *msg = c->commit->buffer + c->message_off;
-       const char *start = c->commit->buffer;
+       const char *msg = c->message + c->message_off;
+       const char *start = c->message;
 
        msg = skip_empty_lines(msg);
        c->subject_off = msg - start;
@@ -741,7 +742,7 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
 {
        struct format_commit_context *c = context;
        const struct commit *commit = c->commit;
-       const char *msg = commit->buffer;
+       const char *msg = c->message;
        struct commit_list *p;
        int h1, h2;
 
@@ -886,8 +887,7 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
        case 'N':
                if (c->pretty_ctx->show_notes) {
                        format_display_notes(commit->object.sha1, sb,
-                                   git_log_output_encoding ? git_log_output_encoding
-                                                           : git_commit_encoding, 0);
+                                   get_log_output_encoding(), 0);
                        return 1;
                }
                return 0;
@@ -1012,13 +1012,27 @@ void format_commit_message(const struct commit *commit,
                           const struct pretty_print_context *pretty_ctx)
 {
        struct format_commit_context context;
+       static const char utf8[] = "UTF-8";
+       const char *enc;
+       const char *output_enc = pretty_ctx->output_encoding;
 
        memset(&context, 0, sizeof(context));
        context.commit = commit;
        context.pretty_ctx = pretty_ctx;
        context.wrap_start = sb->len;
+       context.message = commit->buffer;
+       if (output_enc) {
+               enc = get_header(commit, "encoding");
+               enc = enc ? enc : utf8;
+               if (strcmp(enc, output_enc))
+                       context.message = logmsg_reencode(commit, output_enc);
+       }
+
        strbuf_expand(sb, format, format_commit_item, &context);
        rewrap_message_tail(sb, &context, 0, 0, 0);
+
+       if (context.message != commit->buffer)
+               free(context.message);
 }
 
 static void pp_header(enum cmit_fmt fmt,
@@ -1159,11 +1173,7 @@ char *reencode_commit_message(const struct commit *commit, const char **encoding
 {
        const char *encoding;
 
-       encoding = (git_log_output_encoding
-                   ? git_log_output_encoding
-                   : git_commit_encoding);
-       if (!encoding)
-               encoding = "UTF-8";
+       encoding = get_log_output_encoding();
        if (encoding_p)
                *encoding_p = encoding;
        return logmsg_reencode(commit, encoding);
index 1f42473e8070a05ada8c56b0d60537227a5223ec..4f2e890b01b0c27ef2e49080e1fd34bf67e969c7 100644 (file)
@@ -608,6 +608,29 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
                ce->ce_mode = ce_mode_from_stat(ent, st_mode);
        }
 
+       /* When core.ignorecase=true, determine if a directory of the same name but differing
+        * case already exists within the Git repository.  If it does, ensure the directory
+        * case of the file being added to the repository matches (is folded into) the existing
+        * entry's directory case.
+        */
+       if (ignore_case) {
+               const char *startPtr = ce->name;
+               const char *ptr = startPtr;
+               while (*ptr) {
+                       while (*ptr && *ptr != '/')
+                               ++ptr;
+                       if (*ptr == '/') {
+                               struct cache_entry *foundce;
+                               ++ptr;
+                               foundce = index_name_exists(&the_index, ce->name, ptr - ce->name, ignore_case);
+                               if (foundce) {
+                                       memcpy((void *)startPtr, foundce->name + (startPtr - ce->name), ptr - startPtr);
+                                       startPtr = ptr;
+                               }
+                       }
+               }
+       }
+
        alias = index_name_exists(istate, ce->name, ce_namelen(ce), ignore_case);
        if (alias && !ce_stage(alias) && !ie_match_stat(istate, alias, st, ce_option)) {
                /* Nothing changed, really */
index 0cd9435619f1e0637584289b45d52c1cdd8a9460..1cafdfa617a833ec757b481826dc62282be8f374 100644 (file)
@@ -35,6 +35,8 @@ static size_t sz_fmt(size_t s) { return s; }
 
 const unsigned char null_sha1[20];
 
+static int git_open_noatime(const char *name, struct packed_git *p);
+
 int safe_create_leading_directories(char *path)
 {
        char *pos = path + offset_1st_component(path);
@@ -298,7 +300,7 @@ static void read_info_alternates(const char * relative_base, int depth)
        int fd;
 
        sprintf(path, "%s/%s", relative_base, alt_file_name);
-       fd = open(path, O_RDONLY);
+       fd = git_open_noatime(path, NULL);
        if (fd < 0)
                return;
        if (fstat(fd, &st) || (st.st_size == 0)) {
@@ -411,7 +413,7 @@ static int check_packed_git_idx(const char *path,  struct packed_git *p)
        struct pack_idx_header *hdr;
        size_t idx_size;
        uint32_t version, nr, i, *index;
-       int fd = open(path, O_RDONLY);
+       int fd = git_open_noatime(path, p);
        struct stat st;
 
        if (fd < 0)
@@ -576,6 +578,21 @@ void release_pack_memory(size_t need, int fd)
                ; /* nothing */
 }
 
+void *xmmap(void *start, size_t length,
+       int prot, int flags, int fd, off_t offset)
+{
+       void *ret = mmap(start, length, prot, flags, fd, offset);
+       if (ret == MAP_FAILED) {
+               if (!length)
+                       return NULL;
+               release_pack_memory(length, fd);
+               ret = mmap(start, length, prot, flags, fd, offset);
+               if (ret == MAP_FAILED)
+                       die_errno("Out of memory? mmap failed");
+       }
+       return ret;
+}
+
 void close_pack_windows(struct packed_git *p)
 {
        while (p->windows) {
@@ -655,9 +672,7 @@ static int open_packed_git_1(struct packed_git *p)
        if (!p->index_data && open_pack_index(p))
                return error("packfile %s index unavailable", p->pack_name);
 
-       p->pack_fd = open(p->pack_name, O_RDONLY);
-       while (p->pack_fd < 0 && errno == EMFILE && unuse_one_window(p, -1))
-               p->pack_fd = open(p->pack_name, O_RDONLY);
+       p->pack_fd = git_open_noatime(p->pack_name, p);
        if (p->pack_fd < 0 || fstat(p->pack_fd, &st))
                return -1;
 
@@ -803,11 +818,22 @@ static struct packed_git *alloc_packed_git(int extra)
        return p;
 }
 
+static void try_to_free_pack_memory(size_t size)
+{
+       release_pack_memory(size, -1);
+}
+
 struct packed_git *add_packed_git(const char *path, int path_len, int local)
 {
+       static int have_set_try_to_free_routine;
        struct stat st;
        struct packed_git *p = alloc_packed_git(path_len + 2);
 
+       if (!have_set_try_to_free_routine) {
+               have_set_try_to_free_routine = 1;
+               set_try_to_free_routine(try_to_free_pack_memory);
+       }
+
        /*
         * Make sure a corresponding .pack file exists and that
         * the index looks sane.
@@ -874,7 +900,7 @@ static void prepare_packed_git_one(char *objdir, int local)
        sprintf(path, "%s/pack", objdir);
        len = strlen(path);
        dir = opendir(path);
-       while (!dir && errno == EMFILE && unuse_one_window(packed_git, -1))
+       while (!dir && errno == EMFILE && unuse_one_window(NULL, -1))
                dir = opendir(path);
        if (!dir) {
                if (errno != ENOENT)
@@ -1003,7 +1029,7 @@ static void mark_bad_packed_object(struct packed_git *p,
        p->num_bad_objects++;
 }
 
-static int has_packed_and_bad(const unsigned char *sha1)
+static const struct packed_git *has_packed_and_bad(const unsigned char *sha1)
 {
        struct packed_git *p;
        unsigned i;
@@ -1011,8 +1037,8 @@ static int has_packed_and_bad(const unsigned char *sha1)
        for (p = packed_git; p; p = p->next)
                for (i = 0; i < p->num_bad_objects; i++)
                        if (!hashcmp(sha1, p->bad_object_sha1 + 20 * i))
-                               return 1;
-       return 0;
+                               return p;
+       return NULL;
 }
 
 int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long size, const char *type)
@@ -1022,18 +1048,31 @@ int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long siz
        return hashcmp(sha1, real_sha1) ? -1 : 0;
 }
 
-static int git_open_noatime(const char *name)
+static int git_open_noatime(const char *name, struct packed_git *p)
 {
        static int sha1_file_open_flag = O_NOATIME;
-       int fd = open(name, O_RDONLY | sha1_file_open_flag);
 
-       /* Might the failure be due to O_NOATIME? */
-       if (fd < 0 && errno != ENOENT && sha1_file_open_flag) {
-               fd = open(name, O_RDONLY);
+       for (;;) {
+               int fd = open(name, O_RDONLY | sha1_file_open_flag);
                if (fd >= 0)
+                       return fd;
+
+               /* Might the failure be insufficient file descriptors? */
+               if (errno == EMFILE) {
+                       if (unuse_one_window(p, -1))
+                               continue;
+                       else
+                               return -1;
+               }
+
+               /* Might the failure be due to O_NOATIME? */
+               if (errno != ENOENT && sha1_file_open_flag) {
                        sha1_file_open_flag = 0;
+                       continue;
+               }
+
+               return -1;
        }
-       return fd;
 }
 
 static int open_sha1_file(const unsigned char *sha1)
@@ -1042,7 +1081,7 @@ static int open_sha1_file(const unsigned char *sha1)
        char *name = sha1_file_name(sha1);
        struct alternate_object_database *alt;
 
-       fd = git_open_noatime(name);
+       fd = git_open_noatime(name, NULL);
        if (fd >= 0)
                return fd;
 
@@ -1051,7 +1090,7 @@ static int open_sha1_file(const unsigned char *sha1)
        for (alt = alt_odb_list; alt; alt = alt->next) {
                name = alt->name;
                fill_sha1_path(name, sha1);
-               fd = git_open_noatime(alt->base);
+               fd = git_open_noatime(alt->base, NULL);
                if (fd >= 0)
                        return fd;
        }
@@ -2079,36 +2118,48 @@ static void *read_object(const unsigned char *sha1, enum object_type *type,
        return read_packed_sha1(sha1, type, size);
 }
 
+/*
+ * This function dies on corrupt objects; the callers who want to
+ * deal with them should arrange to call read_object() and give error
+ * messages themselves.
+ */
 void *read_sha1_file_repl(const unsigned char *sha1,
                          enum object_type *type,
                          unsigned long *size,
                          const unsigned char **replacement)
 {
        const unsigned char *repl = lookup_replace_object(sha1);
-       void *data = read_object(repl, type, size);
+       void *data;
        char *path;
+       const struct packed_git *p;
+
+       errno = 0;
+       data = read_object(repl, type, size);
+       if (data) {
+               if (replacement)
+                       *replacement = repl;
+               return data;
+       }
+
+       if (errno != ENOENT)
+               die_errno("failed to read object %s", sha1_to_hex(sha1));
 
        /* die if we replaced an object with one that does not exist */
-       if (!data && repl != sha1)
+       if (repl != sha1)
                die("replacement %s not found for %s",
                    sha1_to_hex(repl), sha1_to_hex(sha1));
 
-       /* legacy behavior is to die on corrupted objects */
-       if (!data) {
-               if (has_loose_object(repl)) {
-                       path = sha1_file_name(sha1);
-                       die("loose object %s (stored in %s) is corrupted", sha1_to_hex(repl), path);
-               }
-               if (has_packed_and_bad(repl)) {
-                       path = sha1_pack_name(sha1);
-                       die("packed object %s (stored in %s) is corrupted", sha1_to_hex(repl), path);
-               }
+       if (has_loose_object(repl)) {
+               path = sha1_file_name(sha1);
+               die("loose object %s (stored in %s) is corrupt",
+                   sha1_to_hex(repl), path);
        }
 
-       if (replacement)
-               *replacement = repl;
+       if ((p = has_packed_and_bad(repl)) != NULL)
+               die("packed object %s (stored in %s) is corrupt",
+                   sha1_to_hex(repl), p->pack_name);
 
-       return data;
+       return NULL;
 }
 
 void *read_object_with_reference(const unsigned char *sha1,
@@ -2300,7 +2351,7 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
 
        filename = sha1_file_name(sha1);
        fd = create_tmpfile(tmpfile, sizeof(tmpfile), filename);
-       while (fd < 0 && errno == EMFILE && unuse_one_window(packed_git, -1))
+       while (fd < 0 && errno == EMFILE && unuse_one_window(NULL, -1))
                fd = create_tmpfile(tmpfile, sizeof(tmpfile), filename);
        if (fd < 0) {
                if (errno == EACCES)
index 3e856b80369c289a3d9399d60ec3be22bbbc6788..2c3a5fb363ff9b11a971b45e85819b2c0aaad157 100644 (file)
@@ -206,7 +206,9 @@ const char *find_unique_abbrev(const unsigned char *sha1, int len)
                if (exists
                    ? !status
                    : status == SHORT_NAME_NOT_FOUND) {
-                       hex[len] = 0;
+                       int cut_at = len + unique_abbrev_extra_length;
+                       cut_at = (cut_at < 40) ? cut_at : 40;
+                       hex[cut_at] = 0;
                        return hex;
                }
                len++;
@@ -934,6 +936,24 @@ int interpret_branch_name(const char *name, struct strbuf *buf)
        return len;
 }
 
+int strbuf_branchname(struct strbuf *sb, const char *name)
+{
+       int len = strlen(name);
+       if (interpret_branch_name(name, sb) == len)
+               return 0;
+       strbuf_add(sb, name, len);
+       return len;
+}
+
+int strbuf_check_branch_ref(struct strbuf *sb, const char *name)
+{
+       strbuf_branchname(sb, name);
+       if (name[0] == '-')
+               return CHECK_REF_FORMAT_ERROR;
+       strbuf_splice(sb, 0, 0, "refs/heads/", 11);
+       return check_ref_format(sb->buf);
+}
+
 /*
  * This is like "get_sha1_basic()", except it allows "sha1 expressions",
  * notably "xyz^" for "parent of xyz"
index 65b4cf43434a8b7327565d0e0f87d5378d098f99..9b3c4457f229041784edfa65218aa09de7a5eff8 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -386,21 +386,3 @@ int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint)
 
        return len;
 }
-
-int strbuf_branchname(struct strbuf *sb, const char *name)
-{
-       int len = strlen(name);
-       if (interpret_branch_name(name, sb) == len)
-               return 0;
-       strbuf_add(sb, name, len);
-       return len;
-}
-
-int strbuf_check_branch_ref(struct strbuf *sb, const char *name)
-{
-       strbuf_branchname(sb, name);
-       if (name[0] == '-')
-               return CHECK_REF_FORMAT_ERROR;
-       strbuf_splice(sb, 0, 0, "refs/heads/", 11);
-       return check_ref_format(sb->buf);
-}
index 88601200114f857a57214a9bf0d02ffd39a83504..3cacebd91adc2958e81d952523bd7cfe0078078c 100644 (file)
@@ -64,11 +64,13 @@ static inline void reset_lstat_cache(struct cache_def *cache)
  * of the prefix, where the cache should use the stat() function
  * instead of the lstat() function to test each path component.
  */
-static int lstat_cache(struct cache_def *cache, const char *name, int len,
-                      int track_flags, int prefix_len_stat_func)
+static int lstat_cache_matchlen(struct cache_def *cache,
+                               const char *name, int len,
+                               int *ret_flags, int track_flags,
+                               int prefix_len_stat_func)
 {
        int match_len, last_slash, last_slash_dir, previous_slash;
-       int match_flags, ret_flags, save_flags, max_len, ret;
+       int save_flags, max_len, ret;
        struct stat st;
 
        if (cache->track_flags != track_flags ||
@@ -90,13 +92,13 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len,
                match_len = last_slash =
                        longest_path_match(name, len, cache->path, cache->len,
                                           &previous_slash);
-               match_flags = cache->flags & track_flags & (FL_NOENT|FL_SYMLINK);
+               *ret_flags = cache->flags & track_flags & (FL_NOENT|FL_SYMLINK);
 
                if (!(track_flags & FL_FULLPATH) && match_len == len)
                        match_len = last_slash = previous_slash;
 
-               if (match_flags && match_len == cache->len)
-                       return match_flags;
+               if (*ret_flags && match_len == cache->len)
+                       return match_len;
                /*
                 * If we now have match_len > 0, we would know that
                 * the matched part will always be a directory.
@@ -105,16 +107,16 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len,
                 * a substring of the cache on a path component basis,
                 * we can return immediately.
                 */
-               match_flags = track_flags & FL_DIR;
-               if (match_flags && len == match_len)
-                       return match_flags;
+               *ret_flags = track_flags & FL_DIR;
+               if (*ret_flags && len == match_len)
+                       return match_len;
        }
 
        /*
         * Okay, no match from the cache so far, so now we have to
         * check the rest of the path components.
         */
-       ret_flags = FL_DIR;
+       *ret_flags = FL_DIR;
        last_slash_dir = last_slash;
        max_len = len < PATH_MAX ? len : PATH_MAX;
        while (match_len < max_len) {
@@ -133,16 +135,16 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len,
                        ret = lstat(cache->path, &st);
 
                if (ret) {
-                       ret_flags = FL_LSTATERR;
+                       *ret_flags = FL_LSTATERR;
                        if (errno == ENOENT)
-                               ret_flags |= FL_NOENT;
+                               *ret_flags |= FL_NOENT;
                } else if (S_ISDIR(st.st_mode)) {
                        last_slash_dir = last_slash;
                        continue;
                } else if (S_ISLNK(st.st_mode)) {
-                       ret_flags = FL_SYMLINK;
+                       *ret_flags = FL_SYMLINK;
                } else {
-                       ret_flags = FL_ERR;
+                       *ret_flags = FL_ERR;
                }
                break;
        }
@@ -152,7 +154,7 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len,
         * path types, FL_NOENT, FL_SYMLINK and FL_DIR, can be cached
         * for the moment!
         */
-       save_flags = ret_flags & track_flags & (FL_NOENT|FL_SYMLINK);
+       save_flags = *ret_flags & track_flags & (FL_NOENT|FL_SYMLINK);
        if (save_flags && last_slash > 0 && last_slash <= PATH_MAX) {
                cache->path[last_slash] = '\0';
                cache->len = last_slash;
@@ -176,7 +178,16 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len,
        } else {
                reset_lstat_cache(cache);
        }
-       return ret_flags;
+       return match_len;
+}
+
+static int lstat_cache(struct cache_def *cache, const char *name, int len,
+                      int track_flags, int prefix_len_stat_func)
+{
+       int flags;
+       (void)lstat_cache_matchlen(cache, name, len, &flags, track_flags,
+                       prefix_len_stat_func);
+       return flags;
 }
 
 #define USE_ONLY_LSTAT  0
@@ -198,15 +209,26 @@ int has_symlink_leading_path(const char *name, int len)
 }
 
 /*
- * Return non-zero if path 'name' has a leading symlink component or
+ * Return zero if path 'name' has a leading symlink component or
  * if some leading path component does not exists.
+ *
+ * Return -1 if leading path exists and is a directory.
+ *
+ * Return path length if leading path exists and is neither a
+ * directory nor a symlink.
  */
-int has_symlink_or_noent_leading_path(const char *name, int len)
+int check_leading_path(const char *name, int len)
 {
        struct cache_def *cache = &default_cache;       /* FIXME */
-       return lstat_cache(cache, name, len,
-                          FL_SYMLINK|FL_NOENT|FL_DIR, USE_ONLY_LSTAT) &
-               (FL_SYMLINK|FL_NOENT);
+       int flags;
+       int match_len = lstat_cache_matchlen(cache, name, len, &flags,
+                          FL_SYMLINK|FL_NOENT|FL_DIR, USE_ONLY_LSTAT);
+       if (flags & (FL_SYMLINK|FL_NOENT))
+               return 0;
+       else if (flags & FL_DIR)
+               return -1;
+       else
+               return match_len;
 }
 
 /*
index 212bd605b5193d26c38efda3bb2ed9fd95b8b462..d34208cc27623a6105f6b12f863c648429d2d34d 100644 (file)
@@ -39,7 +39,7 @@ test_expect_success \
     'echo "1A quick brown fox jumps over the" >file &&
      echo "lazy dog" >>file &&
      git add file &&
-     GIT_AUTHOR_NAME="A" git commit -a -m "Initial."'
+     GIT_AUTHOR_NAME="A" GIT_AUTHOR_EMAIL="A@test.git" git commit -a -m "Initial."'
 
 test_expect_success \
     'check all lines blamed on A' \
@@ -49,7 +49,7 @@ test_expect_success \
     'Setup new lines blamed on B' \
     'echo "2A quick brown fox jumps over the" >>file &&
      echo "lazy dog" >> file &&
-     GIT_AUTHOR_NAME="B" git commit -a -m "Second."'
+     GIT_AUTHOR_NAME="B" GIT_AUTHOR_EMAIL="B@test.git" git commit -a -m "Second."'
 
 test_expect_success \
     'Two lines blamed on A, two on B' \
@@ -60,7 +60,7 @@ test_expect_success \
     'git checkout -b branch1 master &&
      echo "3A slow green fox jumps into the" >> file &&
      echo "well." >> file &&
-     GIT_AUTHOR_NAME="B1" git commit -a -m "Branch1-1"'
+     GIT_AUTHOR_NAME="B1" GIT_AUTHOR_EMAIL="B1@test.git" git commit -a -m "Branch1-1"'
 
 test_expect_success \
     'Two lines blamed on A, two on B, two on B1' \
@@ -71,7 +71,7 @@ test_expect_success \
     'git checkout -b branch2 master &&
      sed -e "s/2A quick brown/4A quick brown lazy dog/" < file > file.new &&
      mv file.new file &&
-     GIT_AUTHOR_NAME="B2" git commit -a -m "Branch2-1"'
+     GIT_AUTHOR_NAME="B2" GIT_AUTHOR_EMAIL="B2@test.git" git commit -a -m "Branch2-1"'
 
 test_expect_success \
     'Two lines blamed on A, one on B, one on B2' \
@@ -105,7 +105,7 @@ test_expect_success \
 test_expect_success \
     'an incomplete line added' \
     'echo "incomplete" | tr -d "\\012" >>file &&
-    GIT_AUTHOR_NAME="C" git commit -a -m "Incomplete"'
+    GIT_AUTHOR_NAME="C" GIT_AUTHOR_EMAIL="C@test.git" git commit -a -m "Incomplete"'
 
 test_expect_success \
     'With incomplete lines.' \
@@ -119,7 +119,7 @@ test_expect_success \
        echo
     } | sed -e "s/^3A/99/" -e "/^1A/d" -e "/^incomplete/d" > file &&
     echo "incomplete" | tr -d "\\012" >>file &&
-    GIT_AUTHOR_NAME="D" git commit -a -m "edit"'
+    GIT_AUTHOR_NAME="D" GIT_AUTHOR_EMAIL="D@test.git" git commit -a -m "edit"'
 
 test_expect_success \
     'some edit' \
index e733f6516fdf98e48d5c7cd36ecc89d36166eea1..3f24384371bf227126e38da61f3b7efd3961ede8 100644 (file)
@@ -75,12 +75,14 @@ fi
 
 prepare_httpd() {
        mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH"
+       cp "$TEST_PATH"/passwd "$HTTPD_ROOT_PATH"
 
        ln -s "$LIB_HTTPD_MODULE_PATH" "$HTTPD_ROOT_PATH/modules"
 
        if test -n "$LIB_HTTPD_SSL"
        then
                HTTPD_URL=https://127.0.0.1:$LIB_HTTPD_PORT
+               AUTH_HTTPD_URL=https://user%40host:user%40host@127.0.0.1:$LIB_HTTPD_PORT
 
                RANDFILE_PATH="$HTTPD_ROOT_PATH"/.rnd openssl req \
                        -config "$TEST_PATH/ssl.cnf" \
@@ -92,6 +94,7 @@ prepare_httpd() {
                HTTPD_PARA="$HTTPD_PARA -DSSL"
        else
                HTTPD_URL=http://127.0.0.1:$LIB_HTTPD_PORT
+               AUTH_HTTPD_URL=http://user%40host:user%40host@127.0.0.1:$LIB_HTTPD_PORT
        fi
 
        if test -n "$LIB_HTTPD_DAV" -o -n "$LIB_HTTPD_SVN"
index 4961505d1dd99da529b43abd42522e6a005961b1..0a4cdfa93ece7d8a4177835b5569583c22303564 100644 (file)
@@ -17,8 +17,33 @@ ErrorLog error.log
 <IfModule !mod_env.c>
        LoadModule env_module modules/mod_env.so
 </IfModule>
+<IfModule !mod_rewrite.c>
+       LoadModule rewrite_module modules/mod_rewrite.so
+</IFModule>
+<IfModule !mod_version.c>
+       LoadModule version_module modules/mod_version.so
+</IfModule>
+
+<IfVersion < 2.1>
+<IfModule !mod_auth.c>
+       LoadModule auth_module modules/mod_auth.so
+</IfModule>
+</IfVersion>
+
+<IfVersion >= 2.1>
+<IfModule !mod_auth_basic.c>
+       LoadModule auth_basic_module modules/mod_auth_basic.so
+</IfModule>
+<IfModule !mod_authn_file.c>
+       LoadModule authn_file_module modules/mod_authn_file.so
+</IfModule>
+<IfModule !mod_authz_user.c>
+       LoadModule authz_user_module modules/mod_authz_user.so
+</IfModule>
+</IfVersion>
 
 Alias /dumb/ www/
+Alias /auth/ www/auth/
 
 <Location /smart/>
        SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
@@ -36,6 +61,10 @@ ScriptAlias /smart_noexport/ ${GIT_EXEC_PATH}/git-http-backend/
        Options ExecCGI
 </Files>
 
+RewriteEngine on
+RewriteRule ^/smart-redir-perm/(.*)$ /smart/$1 [R=301]
+RewriteRule ^/smart-redir-temp/(.*)$ /smart/$1 [R=302]
+
 <IfDefine SSL>
 LoadModule ssl_module modules/mod_ssl.so
 
@@ -48,6 +77,13 @@ SSLMutex file:ssl_mutex
 SSLEngine On
 </IfDefine>
 
+<Location /auth/>
+       AuthType Basic
+       AuthName "git-auth"
+       AuthUserFile passwd
+       Require valid-user
+</Location>
+
 <IfDefine DAV>
        LoadModule dav_module modules/mod_dav.so
        LoadModule dav_fs_module modules/mod_dav_fs.so
diff --git a/t/lib-httpd/passwd b/t/lib-httpd/passwd
new file mode 100644 (file)
index 0000000..f2fbcad
--- /dev/null
@@ -0,0 +1 @@
+user@host:nKpa8pZUHx/ic
diff --git a/t/t2006-checkout-index-basic.sh b/t/t2006-checkout-index-basic.sh
new file mode 100755 (executable)
index 0000000..b855983
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+test_description='basic checkout-index tests
+'
+
+. ./test-lib.sh
+
+test_expect_success 'checkout-index --gobbledegook' '
+       test_expect_code 129 git checkout-index --gobbledegook 2>err &&
+       grep "[Uu]sage" err
+'
+
+test_expect_success 'checkout-index -h in broken repository' '
+       mkdir broken &&
+       (
+               cd broken &&
+               git init &&
+               >.git/index &&
+               test_expect_code 129 git checkout-index -h >usage 2>&1
+       ) &&
+       grep "[Uu]sage" broken/usage
+'
+
+test_done
diff --git a/t/t2107-update-index-basic.sh b/t/t2107-update-index-basic.sh
new file mode 100755 (executable)
index 0000000..33f8be0
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+test_description='basic update-index tests
+
+Tests for command-line parsing and basic operation.
+'
+
+. ./test-lib.sh
+
+test_expect_success 'update-index --nonsense fails' '
+       test_must_fail git update-index --nonsense 2>msg &&
+       cat msg &&
+       test -s msg
+'
+
+test_expect_failure 'update-index --nonsense dumps usage' '
+       test_expect_code 129 git update-index --nonsense 2>err &&
+       grep "[Uu]sage: git update-index" err
+'
+
+test_expect_success 'update-index -h with corrupt index' '
+       mkdir broken &&
+       (
+               cd broken &&
+               git init &&
+               >.git/index &&
+               test_expect_code 129 git update-index -h >usage 2>&1
+       ) &&
+       grep "[Uu]sage: git update-index" broken/usage
+'
+
+test_done
diff --git a/t/t3004-ls-files-basic.sh b/t/t3004-ls-files-basic.sh
new file mode 100755 (executable)
index 0000000..490e052
--- /dev/null
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+test_description='basic ls-files tests
+
+This test runs git ls-files with various unusual or malformed
+command-line arguments.
+'
+
+. ./test-lib.sh
+
+>empty
+
+test_expect_success 'ls-files in empty repository' '
+       git ls-files >actual &&
+       test_cmp empty actual
+'
+
+test_expect_success 'ls-files with nonexistent path' '
+       git ls-files doesnotexist >actual &&
+       test_cmp empty actual
+'
+
+test_expect_success 'ls-files with nonsense option' '
+       test_expect_code 129 git ls-files --nonsense 2>actual &&
+       grep "[Uu]sage: git ls-files" actual
+'
+
+test_expect_success 'ls-files -h in corrupt repository' '
+       mkdir broken &&
+       (
+               cd broken &&
+               git init &&
+               >.git/index &&
+               test_expect_code 129 git ls-files -h >usage 2>&1
+       ) &&
+       grep "[Uu]sage: git ls-files " broken/usage
+'
+
+test_done
index 20d4f11dbe7edda104364ad282af94b13367224b..34794f8a70e1c9b71384363e022d08da4ac8872e 100755 (executable)
@@ -25,6 +25,10 @@ test_expect_success 'setup 1' '
        git branch submod &&
        git branch copy &&
        git branch rename &&
+       if test_have_prereq SYMLINKS
+       then
+               git branch rename-ln
+       fi &&
 
        echo hello >>a &&
        cp a d/e &&
@@ -255,7 +259,16 @@ test_expect_success 'setup 8' '
        git mv a e &&
        git add e &&
        test_tick &&
-       git commit -m "rename a->e"
+       git commit -m "rename a->e" &&
+       if test_have_prereq SYMLINKS
+       then
+               git checkout rename-ln &&
+               git mv a e &&
+               ln -s e a &&
+               git add a e &&
+               test_tick &&
+               git commit -m "rename a->e, symlink a->e"
+       fi
 '
 
 test_expect_success 'setup 9' '
@@ -615,4 +628,26 @@ test_expect_success 'merge-recursive copy vs. rename' '
        test_cmp expected actual
 '
 
+if test_have_prereq SYMLINKS
+then
+       test_expect_success '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 "100644 blob $o0   b"
+                       echo "100644 blob $o0   c"
+                       echo "100644 blob $o0   d/e"
+                       echo "100644 blob $o0   e"
+                       echo "100644 $o0 0      b"
+                       echo "100644 $o0 0      c"
+                       echo "100644 $o0 0      d/e"
+                       echo "100644 $o0 0      e"
+               ) >expected &&
+               test_cmp expected actual
+       '
+fi
+
+
 test_done
index f54a533456d74a3eb2f745dbc77c8ad5a5ae960f..f308235f5dd28da2c19d91dc63803312aacd2cda 100755 (executable)
@@ -26,6 +26,17 @@ test_expect_success \
      ! test -f .git/refs/heads/--help
 '
 
+test_expect_success 'branch -h in broken repository' '
+       mkdir broken &&
+       (
+               cd broken &&
+               git init &&
+               >.git/refs/heads/master &&
+               test_expect_code 129 git branch -h >usage 2>&1
+       ) &&
+       grep "[Uu]sage" broken/usage
+'
+
 test_expect_success \
     'git branch abc should create a branch' \
     'git branch abc && test -f .git/refs/heads/abc'
index 7e84ab97903a890a7d9fabc304cad7531e9b915b..dc2e04a0161c8d6b3f6eefc1873852a377a26393 100755 (executable)
@@ -962,6 +962,7 @@ Date:   Thu Apr 7 15:27:13 2005 -0700
 
 Notes (other):
     a fresh note
+$whitespace
     another fresh note
 EOF
 
@@ -983,8 +984,11 @@ Date:   Thu Apr 7 15:27:13 2005 -0700
 
 Notes (other):
     a fresh note
+$whitespace
     another fresh note
+$whitespace
     append 1
+$whitespace
     append 2
 EOF
 
@@ -1061,4 +1065,23 @@ test_expect_success 'git notes copy diagnoses too many or too few parameters' '
        test_must_fail git notes copy one two three
 '
 
+test_expect_success 'git notes get-ref (no overrides)' '
+       git config --unset core.notesRef &&
+       unset GIT_NOTES_REF &&
+       test "$(git notes get-ref)" = "refs/notes/commits"
+'
+
+test_expect_success 'git notes get-ref (core.notesRef)' '
+       git config core.notesRef refs/notes/foo &&
+       test "$(git notes get-ref)" = "refs/notes/foo"
+'
+
+test_expect_success 'git notes get-ref (GIT_NOTES_REF)' '
+       test "$(GIT_NOTES_REF=refs/notes/bar git notes get-ref)" = "refs/notes/bar"
+'
+
+test_expect_success 'git notes get-ref (--ref)' '
+       test "$(GIT_NOTES_REF=refs/notes/bar git notes --ref=baz get-ref)" = "refs/notes/baz"
+'
+
 test_done
index 75ec18778e1be732593ae130aa257eca3290e36f..704aee81ef5618048bba5209629a0dfde136fdb3 100755 (executable)
@@ -168,15 +168,16 @@ INPUT_END
 }
 
 verify_concatenated_notes () {
-    git log | grep "^    " > output &&
-    i=$number_of_commits &&
-    while [ $i -gt 0 ]; do
-        echo "    commit #$i" &&
-        echo "    first note for commit #$i" &&
-        echo "    second note for commit #$i" &&
-        i=$(($i-1));
-    done > expect &&
-    test_cmp expect output
+       git log | grep "^    " > output &&
+       i=$number_of_commits &&
+       while [ $i -gt 0 ]; do
+               echo "    commit #$i" &&
+               echo "    first note for commit #$i" &&
+               echo "    " &&
+               echo "    second note for commit #$i" &&
+               i=$(($i-1));
+       done > expect &&
+       test_cmp expect output
 }
 
 test_expect_success 'test notes in no fanout concatenated with 2/38-fanout' 'test_concatenated_notes "s|^..|&/|" ""'
diff --git a/t/t3308-notes-merge.sh b/t/t3308-notes-merge.sh
new file mode 100755 (executable)
index 0000000..24d82b4
--- /dev/null
@@ -0,0 +1,368 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Johan Herland
+#
+
+test_description='Test merging of notes trees'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       test_commit 1st &&
+       test_commit 2nd &&
+       test_commit 3rd &&
+       test_commit 4th &&
+       test_commit 5th &&
+       # Create notes on 4 first commits
+       git config core.notesRef refs/notes/x &&
+       git notes add -m "Notes on 1st commit" 1st &&
+       git notes add -m "Notes on 2nd commit" 2nd &&
+       git notes add -m "Notes on 3rd commit" 3rd &&
+       git notes add -m "Notes on 4th commit" 4th
+'
+
+commit_sha1=$(git rev-parse 1st^{commit})
+commit_sha2=$(git rev-parse 2nd^{commit})
+commit_sha3=$(git rev-parse 3rd^{commit})
+commit_sha4=$(git rev-parse 4th^{commit})
+commit_sha5=$(git rev-parse 5th^{commit})
+
+verify_notes () {
+       notes_ref="$1"
+       git -c core.notesRef="refs/notes/$notes_ref" notes |
+               sort >"output_notes_$notes_ref" &&
+       test_cmp "expect_notes_$notes_ref" "output_notes_$notes_ref" &&
+       git -c core.notesRef="refs/notes/$notes_ref" log --format="%H %s%n%N" \
+               >"output_log_$notes_ref" &&
+       test_cmp "expect_log_$notes_ref" "output_log_$notes_ref"
+}
+
+cat <<EOF | sort >expect_notes_x
+5e93d24084d32e1cb61f7070505b9d2530cca987 $commit_sha4
+8366731eeee53787d2bdf8fc1eff7d94757e8da0 $commit_sha3
+eede89064cd42441590d6afec6c37b321ada3389 $commit_sha2
+daa55ffad6cb99bf64226532147ffcaf5ce8bdd1 $commit_sha1
+EOF
+
+cat >expect_log_x <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+Notes on 4th commit
+
+$commit_sha3 3rd
+Notes on 3rd commit
+
+$commit_sha2 2nd
+Notes on 2nd commit
+
+$commit_sha1 1st
+Notes on 1st commit
+
+EOF
+
+test_expect_success 'verify initial notes (x)' '
+       verify_notes x
+'
+
+cp expect_notes_x expect_notes_y
+cp expect_log_x expect_log_y
+
+test_expect_success 'fail to merge empty notes ref into empty notes ref (z => y)' '
+       test_must_fail git -c "core.notesRef=refs/notes/y" notes merge z
+'
+
+test_expect_success 'fail to merge into various non-notes refs' '
+       test_must_fail git -c "core.notesRef=refs/notes" notes merge x &&
+       test_must_fail git -c "core.notesRef=refs/notes/" notes merge x &&
+       mkdir -p .git/refs/notes/dir &&
+       test_must_fail git -c "core.notesRef=refs/notes/dir" notes merge x &&
+       test_must_fail git -c "core.notesRef=refs/notes/dir/" notes merge x &&
+       test_must_fail git -c "core.notesRef=refs/heads/master" notes merge x &&
+       test_must_fail git -c "core.notesRef=refs/notes/y:" notes merge x &&
+       test_must_fail git -c "core.notesRef=refs/notes/y:foo" notes merge x &&
+       test_must_fail git -c "core.notesRef=refs/notes/foo^{bar" notes merge x
+'
+
+test_expect_success 'fail to merge various non-note-trees' '
+       git config core.notesRef refs/notes/y &&
+       test_must_fail git notes merge refs/notes &&
+       test_must_fail git notes merge refs/notes/ &&
+       test_must_fail git notes merge refs/notes/dir &&
+       test_must_fail git notes merge refs/notes/dir/ &&
+       test_must_fail git notes merge refs/heads/master &&
+       test_must_fail git notes merge x: &&
+       test_must_fail git notes merge x:foo &&
+       test_must_fail git notes merge foo^{bar
+'
+
+test_expect_success 'merge notes into empty notes ref (x => y)' '
+       git config core.notesRef refs/notes/y &&
+       git notes merge x &&
+       verify_notes y &&
+       # x and y should point to the same notes commit
+       test "$(git rev-parse refs/notes/x)" = "$(git rev-parse refs/notes/y)"
+'
+
+test_expect_success 'merge empty notes ref (z => y)' '
+       git notes merge z &&
+       # y should not change (still == x)
+       test "$(git rev-parse refs/notes/x)" = "$(git rev-parse refs/notes/y)"
+'
+
+test_expect_success 'change notes on other notes ref (y)' '
+       # Not touching notes to 1st commit
+       git notes remove 2nd &&
+       git notes append -m "More notes on 3rd commit" 3rd &&
+       git notes add -f -m "New notes on 4th commit" 4th &&
+       git notes add -m "Notes on 5th commit" 5th
+'
+
+test_expect_success 'merge previous notes commit (y^ => y) => No-op' '
+       pre_state="$(git rev-parse refs/notes/y)" &&
+       git notes merge y^ &&
+       # y should not move
+       test "$pre_state" = "$(git rev-parse refs/notes/y)"
+'
+
+cat <<EOF | sort >expect_notes_y
+0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5
+dec2502dac3ea161543f71930044deff93fa945c $commit_sha4
+4069cdb399fd45463ec6eef8e051a16a03592d91 $commit_sha3
+daa55ffad6cb99bf64226532147ffcaf5ce8bdd1 $commit_sha1
+EOF
+
+cat >expect_log_y <<EOF
+$commit_sha5 5th
+Notes on 5th commit
+
+$commit_sha4 4th
+New notes on 4th commit
+
+$commit_sha3 3rd
+Notes on 3rd commit
+
+More notes on 3rd commit
+
+$commit_sha2 2nd
+
+$commit_sha1 1st
+Notes on 1st commit
+
+EOF
+
+test_expect_success 'verify changed notes on other notes ref (y)' '
+       verify_notes y
+'
+
+test_expect_success 'verify unchanged notes on original notes ref (x)' '
+       verify_notes x
+'
+
+test_expect_success 'merge original notes (x) into changed notes (y) => No-op' '
+       git notes merge -vvv x &&
+       verify_notes y &&
+       verify_notes x
+'
+
+cp expect_notes_y expect_notes_x
+cp expect_log_y expect_log_x
+
+test_expect_success 'merge changed (y) into original (x) => Fast-forward' '
+       git config core.notesRef refs/notes/x &&
+       git notes merge y &&
+       verify_notes x &&
+       verify_notes y &&
+       # x and y should point to same the notes commit
+       test "$(git rev-parse refs/notes/x)" = "$(git rev-parse refs/notes/y)"
+'
+
+test_expect_success 'merge empty notes ref (z => y)' '
+       # Prepare empty (but valid) notes ref (z)
+       git config core.notesRef refs/notes/z &&
+       git notes add -m "foo" &&
+       git notes remove &&
+       git notes >output_notes_z &&
+       test_cmp /dev/null output_notes_z &&
+       # Do the merge (z => y)
+       git config core.notesRef refs/notes/y &&
+       git notes merge z &&
+       verify_notes y &&
+       # y should no longer point to the same notes commit as x
+       test "$(git rev-parse refs/notes/x)" != "$(git rev-parse refs/notes/y)"
+'
+
+cat <<EOF | sort >expect_notes_y
+0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5
+dec2502dac3ea161543f71930044deff93fa945c $commit_sha4
+4069cdb399fd45463ec6eef8e051a16a03592d91 $commit_sha3
+d000d30e6ddcfce3a8122c403226a2ce2fd04d9d $commit_sha2
+43add6bd0c8c0bc871ac7991e0f5573cfba27804 $commit_sha1
+EOF
+
+cat >expect_log_y <<EOF
+$commit_sha5 5th
+Notes on 5th commit
+
+$commit_sha4 4th
+New notes on 4th commit
+
+$commit_sha3 3rd
+Notes on 3rd commit
+
+More notes on 3rd commit
+
+$commit_sha2 2nd
+New notes on 2nd commit
+
+$commit_sha1 1st
+Notes on 1st commit
+
+More notes on 1st commit
+
+EOF
+
+test_expect_success 'change notes on other notes ref (y)' '
+       # Append to 1st commit notes
+       git notes append -m "More notes on 1st commit" 1st &&
+       # Add new notes to 2nd commit
+       git notes add -m "New notes on 2nd commit" 2nd &&
+       verify_notes y
+'
+
+cat <<EOF | sort >expect_notes_x
+0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5
+1f257a3a90328557c452f0817d6cc50c89d315d4 $commit_sha4
+daa55ffad6cb99bf64226532147ffcaf5ce8bdd1 $commit_sha1
+EOF
+
+cat >expect_log_x <<EOF
+$commit_sha5 5th
+Notes on 5th commit
+
+$commit_sha4 4th
+New notes on 4th commit
+
+More notes on 4th commit
+
+$commit_sha3 3rd
+
+$commit_sha2 2nd
+
+$commit_sha1 1st
+Notes on 1st commit
+
+EOF
+
+test_expect_success 'change notes on notes ref (x)' '
+       git config core.notesRef refs/notes/x &&
+       git notes remove 3rd &&
+       git notes append -m "More notes on 4th commit" 4th &&
+       verify_notes x
+'
+
+cat <<EOF | sort >expect_notes_x
+0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5
+1f257a3a90328557c452f0817d6cc50c89d315d4 $commit_sha4
+d000d30e6ddcfce3a8122c403226a2ce2fd04d9d $commit_sha2
+43add6bd0c8c0bc871ac7991e0f5573cfba27804 $commit_sha1
+EOF
+
+cat >expect_log_x <<EOF
+$commit_sha5 5th
+Notes on 5th commit
+
+$commit_sha4 4th
+New notes on 4th commit
+
+More notes on 4th commit
+
+$commit_sha3 3rd
+
+$commit_sha2 2nd
+New notes on 2nd commit
+
+$commit_sha1 1st
+Notes on 1st commit
+
+More notes on 1st commit
+
+EOF
+
+test_expect_success 'merge y into x => Non-conflicting 3-way merge' '
+       git notes merge y &&
+       verify_notes x &&
+       verify_notes y
+'
+
+cat <<EOF | sort >expect_notes_w
+05a4927951bcef347f51486575b878b2b60137f2 $commit_sha3
+d000d30e6ddcfce3a8122c403226a2ce2fd04d9d $commit_sha2
+EOF
+
+cat >expect_log_w <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+
+$commit_sha3 3rd
+New notes on 3rd commit
+
+$commit_sha2 2nd
+New notes on 2nd commit
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'create notes on new, separate notes ref (w)' '
+       git config core.notesRef refs/notes/w &&
+       # Add same note as refs/notes/y on 2nd commit
+       git notes add -m "New notes on 2nd commit" 2nd &&
+       # Add new note on 3rd commit (non-conflicting)
+       git notes add -m "New notes on 3rd commit" 3rd &&
+       # Verify state of notes on new, separate notes ref (w)
+       verify_notes w
+'
+
+cat <<EOF | sort >expect_notes_x
+0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5
+1f257a3a90328557c452f0817d6cc50c89d315d4 $commit_sha4
+05a4927951bcef347f51486575b878b2b60137f2 $commit_sha3
+d000d30e6ddcfce3a8122c403226a2ce2fd04d9d $commit_sha2
+43add6bd0c8c0bc871ac7991e0f5573cfba27804 $commit_sha1
+EOF
+
+cat >expect_log_x <<EOF
+$commit_sha5 5th
+Notes on 5th commit
+
+$commit_sha4 4th
+New notes on 4th commit
+
+More notes on 4th commit
+
+$commit_sha3 3rd
+New notes on 3rd commit
+
+$commit_sha2 2nd
+New notes on 2nd commit
+
+$commit_sha1 1st
+Notes on 1st commit
+
+More notes on 1st commit
+
+EOF
+
+test_expect_success 'merge w into x => Non-conflicting history-less merge' '
+       git config core.notesRef refs/notes/x &&
+       git notes merge w &&
+       # Verify new state of notes on other notes ref (x)
+       verify_notes x &&
+       # Also verify that nothing changed on other notes refs (y and w)
+       verify_notes y &&
+       verify_notes w
+'
+
+test_done
diff --git a/t/t3309-notes-merge-auto-resolve.sh b/t/t3309-notes-merge-auto-resolve.sh
new file mode 100755 (executable)
index 0000000..461fd84
--- /dev/null
@@ -0,0 +1,647 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Johan Herland
+#
+
+test_description='Test notes merging with auto-resolving strategies'
+
+. ./test-lib.sh
+
+# Set up a notes merge scenario with all kinds of potential conflicts
+test_expect_success 'setup commits' '
+       test_commit 1st &&
+       test_commit 2nd &&
+       test_commit 3rd &&
+       test_commit 4th &&
+       test_commit 5th &&
+       test_commit 6th &&
+       test_commit 7th &&
+       test_commit 8th &&
+       test_commit 9th &&
+       test_commit 10th &&
+       test_commit 11th &&
+       test_commit 12th &&
+       test_commit 13th &&
+       test_commit 14th &&
+       test_commit 15th
+'
+
+commit_sha1=$(git rev-parse 1st^{commit})
+commit_sha2=$(git rev-parse 2nd^{commit})
+commit_sha3=$(git rev-parse 3rd^{commit})
+commit_sha4=$(git rev-parse 4th^{commit})
+commit_sha5=$(git rev-parse 5th^{commit})
+commit_sha6=$(git rev-parse 6th^{commit})
+commit_sha7=$(git rev-parse 7th^{commit})
+commit_sha8=$(git rev-parse 8th^{commit})
+commit_sha9=$(git rev-parse 9th^{commit})
+commit_sha10=$(git rev-parse 10th^{commit})
+commit_sha11=$(git rev-parse 11th^{commit})
+commit_sha12=$(git rev-parse 12th^{commit})
+commit_sha13=$(git rev-parse 13th^{commit})
+commit_sha14=$(git rev-parse 14th^{commit})
+commit_sha15=$(git rev-parse 15th^{commit})
+
+verify_notes () {
+       notes_ref="$1"
+       suffix="$2"
+       git -c core.notesRef="refs/notes/$notes_ref" notes |
+               sort >"output_notes_$suffix" &&
+       test_cmp "expect_notes_$suffix" "output_notes_$suffix" &&
+       git -c core.notesRef="refs/notes/$notes_ref" log --format="%H %s%n%N" \
+               >"output_log_$suffix" &&
+       test_cmp "expect_log_$suffix" "output_log_$suffix"
+}
+
+test_expect_success 'setup merge base (x)' '
+       git config core.notesRef refs/notes/x &&
+       git notes add -m "x notes on 6th commit" 6th &&
+       git notes add -m "x notes on 7th commit" 7th &&
+       git notes add -m "x notes on 8th commit" 8th &&
+       git notes add -m "x notes on 9th commit" 9th &&
+       git notes add -m "x notes on 10th commit" 10th &&
+       git notes add -m "x notes on 11th commit" 11th &&
+       git notes add -m "x notes on 12th commit" 12th &&
+       git notes add -m "x notes on 13th commit" 13th &&
+       git notes add -m "x notes on 14th commit" 14th &&
+       git notes add -m "x notes on 15th commit" 15th
+'
+
+cat <<EOF | sort >expect_notes_x
+457a85d6c814ea208550f15fcc48f804ac8dc023 $commit_sha15
+b0c95b954301d69da2bc3723f4cb1680d355937c $commit_sha14
+5d30216a129eeffa97d9694ffe8c74317a560315 $commit_sha13
+dd161bc149470fd890dd4ab52a4cbd79bbd18c36 $commit_sha12
+7abbc45126d680336fb24294f013a7cdfa3ed545 $commit_sha11
+b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
+20c613c835011c48a5abe29170a2402ca6354910 $commit_sha9
+a3daf8a1e4e5dc3409a303ad8481d57bfea7f5d6 $commit_sha8
+897003322b53bc6ca098e9324ee508362347e734 $commit_sha7
+11d97fdebfa5ceee540a3da07bce6fa0222bc082 $commit_sha6
+EOF
+
+cat >expect_log_x <<EOF
+$commit_sha15 15th
+x notes on 15th commit
+
+$commit_sha14 14th
+x notes on 14th commit
+
+$commit_sha13 13th
+x notes on 13th commit
+
+$commit_sha12 12th
+x notes on 12th commit
+
+$commit_sha11 11th
+x notes on 11th commit
+
+$commit_sha10 10th
+x notes on 10th commit
+
+$commit_sha9 9th
+x notes on 9th commit
+
+$commit_sha8 8th
+x notes on 8th commit
+
+$commit_sha7 7th
+x notes on 7th commit
+
+$commit_sha6 6th
+x notes on 6th commit
+
+$commit_sha5 5th
+
+$commit_sha4 4th
+
+$commit_sha3 3rd
+
+$commit_sha2 2nd
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'verify state of merge base (x)' 'verify_notes x x'
+
+test_expect_success 'setup local branch (y)' '
+       git update-ref refs/notes/y refs/notes/x &&
+       git config core.notesRef refs/notes/y &&
+       git notes add -f -m "y notes on 3rd commit" 3rd &&
+       git notes add -f -m "y notes on 4th commit" 4th &&
+       git notes add -f -m "y notes on 5th commit" 5th &&
+       git notes remove 6th &&
+       git notes remove 7th &&
+       git notes remove 8th &&
+       git notes add -f -m "y notes on 12th commit" 12th &&
+       git notes add -f -m "y notes on 13th commit" 13th &&
+       git notes add -f -m "y notes on 14th commit" 14th &&
+       git notes add -f -m "y notes on 15th commit" 15th
+'
+
+cat <<EOF | sort >expect_notes_y
+68b8630d25516028bed862719855b3d6768d7833 $commit_sha15
+5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
+3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
+a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12
+7abbc45126d680336fb24294f013a7cdfa3ed545 $commit_sha11
+b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
+20c613c835011c48a5abe29170a2402ca6354910 $commit_sha9
+154508c7a0bcad82b6fe4b472bc4c26b3bf0825b $commit_sha5
+e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
+5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+EOF
+
+cat >expect_log_y <<EOF
+$commit_sha15 15th
+y notes on 15th commit
+
+$commit_sha14 14th
+y notes on 14th commit
+
+$commit_sha13 13th
+y notes on 13th commit
+
+$commit_sha12 12th
+y notes on 12th commit
+
+$commit_sha11 11th
+x notes on 11th commit
+
+$commit_sha10 10th
+x notes on 10th commit
+
+$commit_sha9 9th
+x notes on 9th commit
+
+$commit_sha8 8th
+
+$commit_sha7 7th
+
+$commit_sha6 6th
+
+$commit_sha5 5th
+y notes on 5th commit
+
+$commit_sha4 4th
+y notes on 4th commit
+
+$commit_sha3 3rd
+y notes on 3rd commit
+
+$commit_sha2 2nd
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'verify state of local branch (y)' 'verify_notes y y'
+
+test_expect_success 'setup remote branch (z)' '
+       git update-ref refs/notes/z refs/notes/x &&
+       git config core.notesRef refs/notes/z &&
+       git notes add -f -m "z notes on 2nd commit" 2nd &&
+       git notes add -f -m "y notes on 4th commit" 4th &&
+       git notes add -f -m "z notes on 5th commit" 5th &&
+       git notes remove 6th &&
+       git notes add -f -m "z notes on 8th commit" 8th &&
+       git notes remove 9th &&
+       git notes add -f -m "z notes on 11th commit" 11th &&
+       git notes remove 12th &&
+       git notes add -f -m "y notes on 14th commit" 14th &&
+       git notes add -f -m "z notes on 15th commit" 15th
+'
+
+cat <<EOF | sort >expect_notes_z
+9b4b2c61f0615412da3c10f98ff85b57c04ec765 $commit_sha15
+5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
+5d30216a129eeffa97d9694ffe8c74317a560315 $commit_sha13
+7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
+b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
+851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8
+897003322b53bc6ca098e9324ee508362347e734 $commit_sha7
+99fc34adfc400b95c67b013115e37e31aa9a6d23 $commit_sha5
+e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+EOF
+
+cat >expect_log_z <<EOF
+$commit_sha15 15th
+z notes on 15th commit
+
+$commit_sha14 14th
+y notes on 14th commit
+
+$commit_sha13 13th
+x notes on 13th commit
+
+$commit_sha12 12th
+
+$commit_sha11 11th
+z notes on 11th commit
+
+$commit_sha10 10th
+x notes on 10th commit
+
+$commit_sha9 9th
+
+$commit_sha8 8th
+z notes on 8th commit
+
+$commit_sha7 7th
+x notes on 7th commit
+
+$commit_sha6 6th
+
+$commit_sha5 5th
+z notes on 5th commit
+
+$commit_sha4 4th
+y notes on 4th commit
+
+$commit_sha3 3rd
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'verify state of remote branch (z)' 'verify_notes z z'
+
+# At this point, before merging z into y, we have the following status:
+#
+# commit | base/x  | local/y | remote/z | diff from x to y/z         | result
+# -------|---------|---------|----------|----------------------------|-------
+# 1st    | [none]  | [none]  | [none]   | unchanged / unchanged      | [none]
+# 2nd    | [none]  | [none]  | 283b482  | unchanged / added          | 283b482
+# 3rd    | [none]  | 5772f42 | [none]   | added     / unchanged      | 5772f42
+# 4th    | [none]  | e2bfd06 | e2bfd06  | added     / added (same)   | e2bfd06
+# 5th    | [none]  | 154508c | 99fc34a  | added     / added (diff)   | ???
+# 6th    | 11d97fd | [none]  | [none]   | removed   / removed        | [none]
+# 7th    | 8970033 | [none]  | 8970033  | removed   / unchanged      | [none]
+# 8th    | a3daf8a | [none]  | 851e163  | removed   / changed        | ???
+# 9th    | 20c613c | 20c613c | [none]   | unchanged / removed        | [none]
+# 10th   | b8d03e1 | b8d03e1 | b8d03e1  | unchanged / unchanged      | b8d03e1
+# 11th   | 7abbc45 | 7abbc45 | 7e3c535  | unchanged / changed        | 7e3c535
+# 12th   | dd161bc | a66055f | [none]   | changed   / removed        | ???
+# 13th   | 5d30216 | 3a631fd | 5d30216  | changed   / unchanged      | 3a631fd
+# 14th   | b0c95b9 | 5de7ea7 | 5de7ea7  | changed   / changed (same) | 5de7ea7
+# 15th   | 457a85d | 68b8630 | 9b4b2c6  | changed   / changed (diff) | ???
+
+test_expect_success 'merge z into y with invalid strategy => Fail/No changes' '
+       git config core.notesRef refs/notes/y &&
+       test_must_fail git notes merge --strategy=foo z &&
+       # Verify no changes (y)
+       verify_notes y y
+'
+
+cat <<EOF | sort >expect_notes_ours
+68b8630d25516028bed862719855b3d6768d7833 $commit_sha15
+5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
+3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
+a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12
+7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
+b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
+154508c7a0bcad82b6fe4b472bc4c26b3bf0825b $commit_sha5
+e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
+5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+EOF
+
+cat >expect_log_ours <<EOF
+$commit_sha15 15th
+y notes on 15th commit
+
+$commit_sha14 14th
+y notes on 14th commit
+
+$commit_sha13 13th
+y notes on 13th commit
+
+$commit_sha12 12th
+y notes on 12th commit
+
+$commit_sha11 11th
+z notes on 11th commit
+
+$commit_sha10 10th
+x notes on 10th commit
+
+$commit_sha9 9th
+
+$commit_sha8 8th
+
+$commit_sha7 7th
+
+$commit_sha6 6th
+
+$commit_sha5 5th
+y notes on 5th commit
+
+$commit_sha4 4th
+y notes on 4th commit
+
+$commit_sha3 3rd
+y notes on 3rd commit
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'merge z into y with "ours" strategy => Non-conflicting 3-way merge' '
+       git notes merge --strategy=ours z &&
+       verify_notes y ours
+'
+
+test_expect_success 'reset to pre-merge state (y)' '
+       git update-ref refs/notes/y refs/notes/y^1 &&
+       # Verify pre-merge state
+       verify_notes y y
+'
+
+cat <<EOF | sort >expect_notes_theirs
+9b4b2c61f0615412da3c10f98ff85b57c04ec765 $commit_sha15
+5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
+3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
+7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
+b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
+851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8
+99fc34adfc400b95c67b013115e37e31aa9a6d23 $commit_sha5
+e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
+5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+EOF
+
+cat >expect_log_theirs <<EOF
+$commit_sha15 15th
+z notes on 15th commit
+
+$commit_sha14 14th
+y notes on 14th commit
+
+$commit_sha13 13th
+y notes on 13th commit
+
+$commit_sha12 12th
+
+$commit_sha11 11th
+z notes on 11th commit
+
+$commit_sha10 10th
+x notes on 10th commit
+
+$commit_sha9 9th
+
+$commit_sha8 8th
+z notes on 8th commit
+
+$commit_sha7 7th
+
+$commit_sha6 6th
+
+$commit_sha5 5th
+z notes on 5th commit
+
+$commit_sha4 4th
+y notes on 4th commit
+
+$commit_sha3 3rd
+y notes on 3rd commit
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'merge z into y with "theirs" strategy => Non-conflicting 3-way merge' '
+       git notes merge --strategy=theirs z &&
+       verify_notes y theirs
+'
+
+test_expect_success 'reset to pre-merge state (y)' '
+       git update-ref refs/notes/y refs/notes/y^1 &&
+       # Verify pre-merge state
+       verify_notes y y
+'
+
+cat <<EOF | sort >expect_notes_union
+7c4e546efd0fe939f876beb262ece02797880b54 $commit_sha15
+5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
+3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
+a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12
+7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
+b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
+851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8
+6c841cc36ea496027290967ca96bd2bef54dbb47 $commit_sha5
+e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
+5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+EOF
+
+cat >expect_log_union <<EOF
+$commit_sha15 15th
+y notes on 15th commit
+
+z notes on 15th commit
+
+$commit_sha14 14th
+y notes on 14th commit
+
+$commit_sha13 13th
+y notes on 13th commit
+
+$commit_sha12 12th
+y notes on 12th commit
+
+$commit_sha11 11th
+z notes on 11th commit
+
+$commit_sha10 10th
+x notes on 10th commit
+
+$commit_sha9 9th
+
+$commit_sha8 8th
+z notes on 8th commit
+
+$commit_sha7 7th
+
+$commit_sha6 6th
+
+$commit_sha5 5th
+y notes on 5th commit
+
+z notes on 5th commit
+
+$commit_sha4 4th
+y notes on 4th commit
+
+$commit_sha3 3rd
+y notes on 3rd commit
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'merge z into y with "union" strategy => Non-conflicting 3-way merge' '
+       git notes merge --strategy=union z &&
+       verify_notes y union
+'
+
+test_expect_success 'reset to pre-merge state (y)' '
+       git update-ref refs/notes/y refs/notes/y^1 &&
+       # Verify pre-merge state
+       verify_notes y y
+'
+
+cat <<EOF | sort >expect_notes_union2
+d682107b8bf7a7aea1e537a8d5cb6a12b60135f1 $commit_sha15
+5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
+3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
+a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12
+7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
+b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
+851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8
+357b6ca14c7afd59b7f8b8aaaa6b8b723771135b $commit_sha5
+e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
+5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+EOF
+
+cat >expect_log_union2 <<EOF
+$commit_sha15 15th
+z notes on 15th commit
+
+y notes on 15th commit
+
+$commit_sha14 14th
+y notes on 14th commit
+
+$commit_sha13 13th
+y notes on 13th commit
+
+$commit_sha12 12th
+y notes on 12th commit
+
+$commit_sha11 11th
+z notes on 11th commit
+
+$commit_sha10 10th
+x notes on 10th commit
+
+$commit_sha9 9th
+
+$commit_sha8 8th
+z notes on 8th commit
+
+$commit_sha7 7th
+
+$commit_sha6 6th
+
+$commit_sha5 5th
+z notes on 5th commit
+
+y notes on 5th commit
+
+$commit_sha4 4th
+y notes on 4th commit
+
+$commit_sha3 3rd
+y notes on 3rd commit
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'merge y into z with "union" strategy => Non-conflicting 3-way merge' '
+       git config core.notesRef refs/notes/z &&
+       git notes merge --strategy=union y &&
+       verify_notes z union2
+'
+
+test_expect_success 'reset to pre-merge state (z)' '
+       git update-ref refs/notes/z refs/notes/z^1 &&
+       # Verify pre-merge state
+       verify_notes z z
+'
+
+cat <<EOF | sort >expect_notes_cat_sort_uniq
+6be90240b5f54594203e25d9f2f64b7567175aee $commit_sha15
+5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
+3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
+a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12
+7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
+b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
+851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8
+660311d7f78dc53db12ac373a43fca7465381a7e $commit_sha5
+e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
+5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+EOF
+
+cat >expect_log_cat_sort_uniq <<EOF
+$commit_sha15 15th
+y notes on 15th commit
+z notes on 15th commit
+
+$commit_sha14 14th
+y notes on 14th commit
+
+$commit_sha13 13th
+y notes on 13th commit
+
+$commit_sha12 12th
+y notes on 12th commit
+
+$commit_sha11 11th
+z notes on 11th commit
+
+$commit_sha10 10th
+x notes on 10th commit
+
+$commit_sha9 9th
+
+$commit_sha8 8th
+z notes on 8th commit
+
+$commit_sha7 7th
+
+$commit_sha6 6th
+
+$commit_sha5 5th
+y notes on 5th commit
+z notes on 5th commit
+
+$commit_sha4 4th
+y notes on 4th commit
+
+$commit_sha3 3rd
+y notes on 3rd commit
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'merge y into z with "cat_sort_uniq" strategy => Non-conflicting 3-way merge' '
+       git notes merge --strategy=cat_sort_uniq y &&
+       verify_notes z cat_sort_uniq
+'
+
+test_done
diff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh
new file mode 100755 (executable)
index 0000000..4ec4d11
--- /dev/null
@@ -0,0 +1,556 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Johan Herland
+#
+
+test_description='Test notes merging with manual conflict resolution'
+
+. ./test-lib.sh
+
+# Set up a notes merge scenario with different kinds of conflicts
+test_expect_success 'setup commits' '
+       test_commit 1st &&
+       test_commit 2nd &&
+       test_commit 3rd &&
+       test_commit 4th &&
+       test_commit 5th
+'
+
+commit_sha1=$(git rev-parse 1st^{commit})
+commit_sha2=$(git rev-parse 2nd^{commit})
+commit_sha3=$(git rev-parse 3rd^{commit})
+commit_sha4=$(git rev-parse 4th^{commit})
+commit_sha5=$(git rev-parse 5th^{commit})
+
+verify_notes () {
+       notes_ref="$1"
+       git -c core.notesRef="refs/notes/$notes_ref" notes |
+               sort >"output_notes_$notes_ref" &&
+       test_cmp "expect_notes_$notes_ref" "output_notes_$notes_ref" &&
+       git -c core.notesRef="refs/notes/$notes_ref" log --format="%H %s%n%N" \
+               >"output_log_$notes_ref" &&
+       test_cmp "expect_log_$notes_ref" "output_log_$notes_ref"
+}
+
+cat <<EOF | sort >expect_notes_x
+6e8e3febca3c2bb896704335cc4d0c34cb2f8715 $commit_sha4
+e5388c10860456ee60673025345fe2e153eb8cf8 $commit_sha3
+ceefa674873670e7ecd131814d909723cce2b669 $commit_sha2
+EOF
+
+cat >expect_log_x <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+x notes on 4th commit
+
+$commit_sha3 3rd
+x notes on 3rd commit
+
+$commit_sha2 2nd
+x notes on 2nd commit
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'setup merge base (x)' '
+       git config core.notesRef refs/notes/x &&
+       git notes add -m "x notes on 2nd commit" 2nd &&
+       git notes add -m "x notes on 3rd commit" 3rd &&
+       git notes add -m "x notes on 4th commit" 4th &&
+       verify_notes x
+'
+
+cat <<EOF | sort >expect_notes_y
+e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
+5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+b0a6021ec006d07e80e9b20ec9b444cbd9d560d3 $commit_sha1
+EOF
+
+cat >expect_log_y <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+y notes on 4th commit
+
+$commit_sha3 3rd
+y notes on 3rd commit
+
+$commit_sha2 2nd
+
+$commit_sha1 1st
+y notes on 1st commit
+
+EOF
+
+test_expect_success 'setup local branch (y)' '
+       git update-ref refs/notes/y refs/notes/x &&
+       git config core.notesRef refs/notes/y &&
+       git notes add -f -m "y notes on 1st commit" 1st &&
+       git notes remove 2nd &&
+       git notes add -f -m "y notes on 3rd commit" 3rd &&
+       git notes add -f -m "y notes on 4th commit" 4th &&
+       verify_notes y
+'
+
+cat <<EOF | sort >expect_notes_z
+cff59c793c20bb49a4e01bc06fb06bad642e0d54 $commit_sha4
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+0a81da8956346e19bcb27a906f04af327e03e31b $commit_sha1
+EOF
+
+cat >expect_log_z <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+z notes on 4th commit
+
+$commit_sha3 3rd
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+z notes on 1st commit
+
+EOF
+
+test_expect_success 'setup remote branch (z)' '
+       git update-ref refs/notes/z refs/notes/x &&
+       git config core.notesRef refs/notes/z &&
+       git notes add -f -m "z notes on 1st commit" 1st &&
+       git notes add -f -m "z notes on 2nd commit" 2nd &&
+       git notes remove 3rd &&
+       git notes add -f -m "z notes on 4th commit" 4th &&
+       verify_notes z
+'
+
+# At this point, before merging z into y, we have the following status:
+#
+# commit | base/x  | local/y | remote/z | diff from x to y/z
+# -------|---------|---------|----------|---------------------------
+# 1st    | [none]  | b0a6021 | 0a81da8  | added     / added (diff)
+# 2nd    | ceefa67 | [none]  | 283b482  | removed   / changed
+# 3rd    | e5388c1 | 5772f42 | [none]   | changed   / removed
+# 4th    | 6e8e3fe | e2bfd06 | cff59c7  | changed   / changed (diff)
+# 5th    | [none]  | [none]  | [none]   | [none]
+
+cat <<EOF | sort >expect_conflicts
+$commit_sha1
+$commit_sha2
+$commit_sha3
+$commit_sha4
+EOF
+
+cat >expect_conflict_$commit_sha1 <<EOF
+<<<<<<< refs/notes/m
+y notes on 1st commit
+=======
+z notes on 1st commit
+>>>>>>> refs/notes/z
+EOF
+
+cat >expect_conflict_$commit_sha2 <<EOF
+z notes on 2nd commit
+EOF
+
+cat >expect_conflict_$commit_sha3 <<EOF
+y notes on 3rd commit
+EOF
+
+cat >expect_conflict_$commit_sha4 <<EOF
+<<<<<<< refs/notes/m
+y notes on 4th commit
+=======
+z notes on 4th commit
+>>>>>>> refs/notes/z
+EOF
+
+cp expect_notes_y expect_notes_m
+cp expect_log_y expect_log_m
+
+git rev-parse refs/notes/y > pre_merge_y
+git rev-parse refs/notes/z > pre_merge_z
+
+test_expect_success 'merge z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
+       git update-ref refs/notes/m refs/notes/y &&
+       git config core.notesRef refs/notes/m &&
+       test_must_fail git notes merge z >output &&
+       # Output should point to where to resolve conflicts
+       grep -q "\\.git/NOTES_MERGE_WORKTREE" output &&
+       # Inspect merge conflicts
+       ls .git/NOTES_MERGE_WORKTREE >output_conflicts &&
+       test_cmp expect_conflicts output_conflicts &&
+       ( for f in $(cat expect_conflicts); do
+               test_cmp "expect_conflict_$f" ".git/NOTES_MERGE_WORKTREE/$f" ||
+               exit 1
+       done ) &&
+       # Verify that current notes tree (pre-merge) has not changed (m == y)
+       verify_notes y &&
+       verify_notes m &&
+       test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)"
+'
+
+cat <<EOF | sort >expect_notes_z
+00494adecf2d9635a02fa431308d67993f853968 $commit_sha4
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+0a81da8956346e19bcb27a906f04af327e03e31b $commit_sha1
+EOF
+
+cat >expect_log_z <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+z notes on 4th commit
+
+More z notes on 4th commit
+
+$commit_sha3 3rd
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+z notes on 1st commit
+
+EOF
+
+test_expect_success 'change notes in z' '
+       git notes --ref z append -m "More z notes on 4th commit" 4th &&
+       verify_notes z
+'
+
+test_expect_success 'cannot do merge w/conflicts when previous merge is unfinished' '
+       test -d .git/NOTES_MERGE_WORKTREE &&
+       test_must_fail git notes merge z >output 2>&1 &&
+       # Output should indicate what is wrong
+       grep -q "\\.git/NOTES_MERGE_\\* exists" output
+'
+
+# Setup non-conflicting merge between x and new notes ref w
+
+cat <<EOF | sort >expect_notes_w
+ceefa674873670e7ecd131814d909723cce2b669 $commit_sha2
+f75d1df88cbfe4258d49852f26cfc83f2ad4494b $commit_sha1
+EOF
+
+cat >expect_log_w <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+
+$commit_sha3 3rd
+
+$commit_sha2 2nd
+x notes on 2nd commit
+
+$commit_sha1 1st
+w notes on 1st commit
+
+EOF
+
+test_expect_success 'setup unrelated notes ref (w)' '
+       git config core.notesRef refs/notes/w &&
+       git notes add -m "w notes on 1st commit" 1st &&
+       git notes add -m "x notes on 2nd commit" 2nd &&
+       verify_notes w
+'
+
+cat <<EOF | sort >expect_notes_w
+6e8e3febca3c2bb896704335cc4d0c34cb2f8715 $commit_sha4
+e5388c10860456ee60673025345fe2e153eb8cf8 $commit_sha3
+ceefa674873670e7ecd131814d909723cce2b669 $commit_sha2
+f75d1df88cbfe4258d49852f26cfc83f2ad4494b $commit_sha1
+EOF
+
+cat >expect_log_w <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+x notes on 4th commit
+
+$commit_sha3 3rd
+x notes on 3rd commit
+
+$commit_sha2 2nd
+x notes on 2nd commit
+
+$commit_sha1 1st
+w notes on 1st commit
+
+EOF
+
+test_expect_success 'can do merge without conflicts even if previous merge is unfinished (x => w)' '
+       test -d .git/NOTES_MERGE_WORKTREE &&
+       git notes merge x &&
+       verify_notes w &&
+       # Verify that other notes refs has not changed (x and y)
+       verify_notes x &&
+       verify_notes y
+'
+
+cat <<EOF | sort >expect_notes_m
+021faa20e931fb48986ffc6282b4bb05553ac946 $commit_sha4
+5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+0a59e787e6d688aa6309e56e8c1b89431a0fc1c1 $commit_sha1
+EOF
+
+cat >expect_log_m <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+y and z notes on 4th commit
+
+$commit_sha3 3rd
+y notes on 3rd commit
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+y and z notes on 1st commit
+
+EOF
+
+test_expect_success 'finalize conflicting merge (z => m)' '
+       # Resolve conflicts and finalize merge
+       cat >.git/NOTES_MERGE_WORKTREE/$commit_sha1 <<EOF &&
+y and z notes on 1st commit
+EOF
+       cat >.git/NOTES_MERGE_WORKTREE/$commit_sha4 <<EOF &&
+y and z notes on 4th commit
+EOF
+       git notes merge --commit &&
+       # No .git/NOTES_MERGE_* files left
+       test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
+       test_cmp /dev/null output &&
+       # Merge commit has pre-merge y and pre-merge z as parents
+       test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" &&
+       test "$(git rev-parse refs/notes/m^2)" = "$(cat pre_merge_z)" &&
+       # Merge commit mentions the notes refs merged
+       git log -1 --format=%B refs/notes/m > merge_commit_msg &&
+       grep -q refs/notes/m merge_commit_msg &&
+       grep -q refs/notes/z merge_commit_msg &&
+       # Merge commit mentions conflicting notes
+       grep -q "Conflicts" merge_commit_msg &&
+       ( for sha1 in $(cat expect_conflicts); do
+               grep -q "$sha1" merge_commit_msg ||
+               exit 1
+       done ) &&
+       # Verify contents of merge result
+       verify_notes m &&
+       # Verify that other notes refs has not changed (w, x, y and z)
+       verify_notes w &&
+       verify_notes x &&
+       verify_notes y &&
+       verify_notes z
+'
+
+cat >expect_conflict_$commit_sha4 <<EOF
+<<<<<<< refs/notes/m
+y notes on 4th commit
+=======
+z notes on 4th commit
+
+More z notes on 4th commit
+>>>>>>> refs/notes/z
+EOF
+
+cp expect_notes_y expect_notes_m
+cp expect_log_y expect_log_m
+
+git rev-parse refs/notes/y > pre_merge_y
+git rev-parse refs/notes/z > pre_merge_z
+
+test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
+       git update-ref refs/notes/m refs/notes/y &&
+       git config core.notesRef refs/notes/m &&
+       test_must_fail git notes merge z >output &&
+       # Output should point to where to resolve conflicts
+       grep -q "\\.git/NOTES_MERGE_WORKTREE" output &&
+       # Inspect merge conflicts
+       ls .git/NOTES_MERGE_WORKTREE >output_conflicts &&
+       test_cmp expect_conflicts output_conflicts &&
+       ( for f in $(cat expect_conflicts); do
+               test_cmp "expect_conflict_$f" ".git/NOTES_MERGE_WORKTREE/$f" ||
+               exit 1
+       done ) &&
+       # Verify that current notes tree (pre-merge) has not changed (m == y)
+       verify_notes y &&
+       verify_notes m &&
+       test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)"
+'
+
+test_expect_success 'abort notes merge' '
+       git notes merge --abort &&
+       # No .git/NOTES_MERGE_* files left
+       test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
+       test_cmp /dev/null output &&
+       # m has not moved (still == y)
+       test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)"
+       # Verify that other notes refs has not changed (w, x, y and z)
+       verify_notes w &&
+       verify_notes x &&
+       verify_notes y &&
+       verify_notes z
+'
+
+git rev-parse refs/notes/y > pre_merge_y
+git rev-parse refs/notes/z > pre_merge_z
+
+test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
+       test_must_fail git notes merge z >output &&
+       # Output should point to where to resolve conflicts
+       grep -q "\\.git/NOTES_MERGE_WORKTREE" output &&
+       # Inspect merge conflicts
+       ls .git/NOTES_MERGE_WORKTREE >output_conflicts &&
+       test_cmp expect_conflicts output_conflicts &&
+       ( for f in $(cat expect_conflicts); do
+               test_cmp "expect_conflict_$f" ".git/NOTES_MERGE_WORKTREE/$f" ||
+               exit 1
+       done ) &&
+       # Verify that current notes tree (pre-merge) has not changed (m == y)
+       verify_notes y &&
+       verify_notes m &&
+       test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)"
+'
+
+cat <<EOF | sort >expect_notes_m
+304dfb4325cf243025b9957486eb605a9b51c199 $commit_sha5
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+0a59e787e6d688aa6309e56e8c1b89431a0fc1c1 $commit_sha1
+EOF
+
+cat >expect_log_m <<EOF
+$commit_sha5 5th
+new note on 5th commit
+
+$commit_sha4 4th
+
+$commit_sha3 3rd
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+y and z notes on 1st commit
+
+EOF
+
+test_expect_success 'add + remove notes in finalized merge (z => m)' '
+       # Resolve one conflict
+       cat >.git/NOTES_MERGE_WORKTREE/$commit_sha1 <<EOF &&
+y and z notes on 1st commit
+EOF
+       # Remove another conflict
+       rm .git/NOTES_MERGE_WORKTREE/$commit_sha4 &&
+       # Remove a D/F conflict
+       rm .git/NOTES_MERGE_WORKTREE/$commit_sha3 &&
+       # Add a new note
+       echo "new note on 5th commit" > .git/NOTES_MERGE_WORKTREE/$commit_sha5 &&
+       # Finalize merge
+       git notes merge --commit &&
+       # No .git/NOTES_MERGE_* files left
+       test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
+       test_cmp /dev/null output &&
+       # Merge commit has pre-merge y and pre-merge z as parents
+       test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" &&
+       test "$(git rev-parse refs/notes/m^2)" = "$(cat pre_merge_z)" &&
+       # Merge commit mentions the notes refs merged
+       git log -1 --format=%B refs/notes/m > merge_commit_msg &&
+       grep -q refs/notes/m merge_commit_msg &&
+       grep -q refs/notes/z merge_commit_msg &&
+       # Merge commit mentions conflicting notes
+       grep -q "Conflicts" merge_commit_msg &&
+       ( for sha1 in $(cat expect_conflicts); do
+               grep -q "$sha1" merge_commit_msg ||
+               exit 1
+       done ) &&
+       # Verify contents of merge result
+       verify_notes m &&
+       # Verify that other notes refs has not changed (w, x, y and z)
+       verify_notes w &&
+       verify_notes x &&
+       verify_notes y &&
+       verify_notes z
+'
+
+cp expect_notes_y expect_notes_m
+cp expect_log_y expect_log_m
+
+test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
+       git update-ref refs/notes/m refs/notes/y &&
+       test_must_fail git notes merge z >output &&
+       # Output should point to where to resolve conflicts
+       grep -q "\\.git/NOTES_MERGE_WORKTREE" output &&
+       # Inspect merge conflicts
+       ls .git/NOTES_MERGE_WORKTREE >output_conflicts &&
+       test_cmp expect_conflicts output_conflicts &&
+       ( for f in $(cat expect_conflicts); do
+               test_cmp "expect_conflict_$f" ".git/NOTES_MERGE_WORKTREE/$f" ||
+               exit 1
+       done ) &&
+       # Verify that current notes tree (pre-merge) has not changed (m == y)
+       verify_notes y &&
+       verify_notes m &&
+       test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)"
+'
+
+cp expect_notes_w expect_notes_m
+cp expect_log_w expect_log_m
+
+test_expect_success 'reset notes ref m to somewhere else (w)' '
+       git update-ref refs/notes/m refs/notes/w &&
+       verify_notes m &&
+       test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)"
+'
+
+test_expect_success 'fail to finalize conflicting merge if underlying ref has moved in the meantime (m != NOTES_MERGE_PARTIAL^1)' '
+       # Resolve conflicts
+       cat >.git/NOTES_MERGE_WORKTREE/$commit_sha1 <<EOF &&
+y and z notes on 1st commit
+EOF
+       cat >.git/NOTES_MERGE_WORKTREE/$commit_sha4 <<EOF &&
+y and z notes on 4th commit
+EOF
+       # Fail to finalize merge
+       test_must_fail git notes merge --commit >output 2>&1 &&
+       # .git/NOTES_MERGE_* must remain
+       test -f .git/NOTES_MERGE_PARTIAL &&
+       test -f .git/NOTES_MERGE_REF &&
+       test -f .git/NOTES_MERGE_WORKTREE/$commit_sha1 &&
+       test -f .git/NOTES_MERGE_WORKTREE/$commit_sha2 &&
+       test -f .git/NOTES_MERGE_WORKTREE/$commit_sha3 &&
+       test -f .git/NOTES_MERGE_WORKTREE/$commit_sha4 &&
+       # Refs are unchanged
+       test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)"
+       test "$(git rev-parse refs/notes/y)" = "$(git rev-parse NOTES_MERGE_PARTIAL^1)"
+       test "$(git rev-parse refs/notes/m)" != "$(git rev-parse NOTES_MERGE_PARTIAL^1)"
+       # Mention refs/notes/m, and its current and expected value in output
+       grep -q "refs/notes/m" output &&
+       grep -q "$(git rev-parse refs/notes/m)" output &&
+       grep -q "$(git rev-parse NOTES_MERGE_PARTIAL^1)" output &&
+       # Verify that other notes refs has not changed (w, x, y and z)
+       verify_notes w &&
+       verify_notes x &&
+       verify_notes y &&
+       verify_notes z
+'
+
+test_expect_success 'resolve situation by aborting the notes merge' '
+       git notes merge --abort &&
+       # No .git/NOTES_MERGE_* files left
+       test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
+       test_cmp /dev/null output &&
+       # m has not moved (still == w)
+       test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)"
+       # Verify that other notes refs has not changed (w, x, y and z)
+       verify_notes w &&
+       verify_notes x &&
+       verify_notes y &&
+       verify_notes z
+'
+
+test_done
diff --git a/t/t3311-notes-merge-fanout.sh b/t/t3311-notes-merge-fanout.sh
new file mode 100755 (executable)
index 0000000..93516ef
--- /dev/null
@@ -0,0 +1,436 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Johan Herland
+#
+
+test_description='Test notes merging at various fanout levels'
+
+. ./test-lib.sh
+
+verify_notes () {
+       notes_ref="$1"
+       commit="$2"
+       if test -f "expect_notes_$notes_ref"
+       then
+               git -c core.notesRef="refs/notes/$notes_ref" notes |
+                       sort >"output_notes_$notes_ref" &&
+               test_cmp "expect_notes_$notes_ref" "output_notes_$notes_ref" ||
+                       return 1
+       fi &&
+       git -c core.notesRef="refs/notes/$notes_ref" log --format="%H %s%n%N" \
+               "$commit" >"output_log_$notes_ref" &&
+       test_cmp "expect_log_$notes_ref" "output_log_$notes_ref"
+}
+
+verify_fanout () {
+       notes_ref="$1"
+       # Expect entire notes tree to have a fanout == 1
+       git rev-parse --quiet --verify "refs/notes/$notes_ref" >/dev/null &&
+       git ls-tree -r --name-only "refs/notes/$notes_ref" |
+       while read path
+       do
+               case "$path" in
+               ??/??????????????????????????????????????)
+                       : true
+                       ;;
+               *)
+                       echo "Invalid path \"$path\"" &&
+                       return 1
+                       ;;
+               esac
+       done
+}
+
+verify_no_fanout () {
+       notes_ref="$1"
+       # Expect entire notes tree to have a fanout == 0
+       git rev-parse --quiet --verify "refs/notes/$notes_ref" >/dev/null &&
+       git ls-tree -r --name-only "refs/notes/$notes_ref" |
+       while read path
+       do
+               case "$path" in
+               ????????????????????????????????????????)
+                       : true
+                       ;;
+               *)
+                       echo "Invalid path \"$path\"" &&
+                       return 1
+                       ;;
+               esac
+       done
+}
+
+# Set up a notes merge scenario with different kinds of conflicts
+test_expect_success 'setup a few initial commits with notes (notes ref: x)' '
+       git config core.notesRef refs/notes/x &&
+       for i in 1 2 3 4 5
+       do
+               test_commit "commit$i" >/dev/null &&
+               git notes add -m "notes for commit$i" || return 1
+       done
+'
+
+commit_sha1=$(git rev-parse commit1^{commit})
+commit_sha2=$(git rev-parse commit2^{commit})
+commit_sha3=$(git rev-parse commit3^{commit})
+commit_sha4=$(git rev-parse commit4^{commit})
+commit_sha5=$(git rev-parse commit5^{commit})
+
+cat <<EOF | sort >expect_notes_x
+aed91155c7a72c2188e781fdf40e0f3761b299db $commit_sha5
+99fab268f9d7ee7b011e091a436c78def8eeee69 $commit_sha4
+953c20ae26c7aa0b428c20693fe38bc687f9d1a9 $commit_sha3
+6358796131b8916eaa2dde6902642942a1cb37e1 $commit_sha2
+b02d459c32f0e68f2fe0981033bb34f38776ba47 $commit_sha1
+EOF
+
+cat >expect_log_x <<EOF
+$commit_sha5 commit5
+notes for commit5
+
+$commit_sha4 commit4
+notes for commit4
+
+$commit_sha3 commit3
+notes for commit3
+
+$commit_sha2 commit2
+notes for commit2
+
+$commit_sha1 commit1
+notes for commit1
+
+EOF
+
+test_expect_success 'sanity check (x)' '
+       verify_notes x commit5 &&
+       verify_no_fanout x
+'
+
+num=300
+
+cp expect_log_x expect_log_y
+
+test_expect_success 'Add a few hundred commits w/notes to trigger fanout (x -> y)' '
+       git update-ref refs/notes/y refs/notes/x &&
+       git config core.notesRef refs/notes/y &&
+       i=5 &&
+       while test $i -lt $num
+       do
+               i=$(($i + 1)) &&
+               test_commit "commit$i" >/dev/null &&
+               git notes add -m "notes for commit$i" || return 1
+       done &&
+       test "$(git rev-parse refs/notes/y)" != "$(git rev-parse refs/notes/x)" &&
+       # Expected number of commits and notes
+       test $(git rev-list HEAD | wc -l) = $num &&
+       test $(git notes list | wc -l) = $num &&
+       # 5 first notes unchanged
+       verify_notes y commit5
+'
+
+test_expect_success 'notes tree has fanout (y)' 'verify_fanout y'
+
+test_expect_success 'No-op merge (already included) (x => y)' '
+       git update-ref refs/notes/m refs/notes/y &&
+       git config core.notesRef refs/notes/m &&
+       git notes merge x &&
+       test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/y)"
+'
+
+test_expect_success 'Fast-forward merge (y => x)' '
+       git update-ref refs/notes/m refs/notes/x &&
+       git notes merge y &&
+       test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/y)"
+'
+
+cat <<EOF | sort >expect_notes_z
+9f506ee70e20379d7f78204c77b334f43d77410d $commit_sha3
+23a47d6ea7d589895faf800752054818e1e7627b $commit_sha2
+b02d459c32f0e68f2fe0981033bb34f38776ba47 $commit_sha1
+EOF
+
+cat >expect_log_z <<EOF
+$commit_sha5 commit5
+
+$commit_sha4 commit4
+
+$commit_sha3 commit3
+notes for commit3
+
+appended notes for commit3
+
+$commit_sha2 commit2
+new notes for commit2
+
+$commit_sha1 commit1
+notes for commit1
+
+EOF
+
+test_expect_success 'change some of the initial 5 notes (x -> z)' '
+       git update-ref refs/notes/z refs/notes/x &&
+       git config core.notesRef refs/notes/z &&
+       git notes add -f -m "new notes for commit2" commit2 &&
+       git notes append -m "appended notes for commit3" commit3 &&
+       git notes remove commit4 &&
+       git notes remove commit5 &&
+       verify_notes z commit5
+'
+
+test_expect_success 'notes tree has no fanout (z)' 'verify_no_fanout z'
+
+cp expect_log_z expect_log_m
+
+test_expect_success 'successful merge without conflicts (y => z)' '
+       git update-ref refs/notes/m refs/notes/z &&
+       git config core.notesRef refs/notes/m &&
+       git notes merge y &&
+       verify_notes m commit5 &&
+       # x/y/z unchanged
+       verify_notes x commit5 &&
+       verify_notes y commit5 &&
+       verify_notes z commit5
+'
+
+test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m'
+
+cat >expect_log_w <<EOF
+$commit_sha5 commit5
+
+$commit_sha4 commit4
+other notes for commit4
+
+$commit_sha3 commit3
+other notes for commit3
+
+$commit_sha2 commit2
+notes for commit2
+
+$commit_sha1 commit1
+other notes for commit1
+
+EOF
+
+test_expect_success 'introduce conflicting changes (y -> w)' '
+       git update-ref refs/notes/w refs/notes/y &&
+       git config core.notesRef refs/notes/w &&
+       git notes add -f -m "other notes for commit1" commit1 &&
+       git notes add -f -m "other notes for commit3" commit3 &&
+       git notes add -f -m "other notes for commit4" commit4 &&
+       git notes remove commit5 &&
+       verify_notes w commit5
+'
+
+cat >expect_log_m <<EOF
+$commit_sha5 commit5
+
+$commit_sha4 commit4
+other notes for commit4
+
+$commit_sha3 commit3
+other notes for commit3
+
+$commit_sha2 commit2
+new notes for commit2
+
+$commit_sha1 commit1
+other notes for commit1
+
+EOF
+
+test_expect_success 'successful merge using "ours" strategy (z => w)' '
+       git update-ref refs/notes/m refs/notes/w &&
+       git config core.notesRef refs/notes/m &&
+       git notes merge -s ours z &&
+       verify_notes m commit5 &&
+       # w/x/y/z unchanged
+       verify_notes w commit5 &&
+       verify_notes x commit5 &&
+       verify_notes y commit5 &&
+       verify_notes z commit5
+'
+
+test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m'
+
+cat >expect_log_m <<EOF
+$commit_sha5 commit5
+
+$commit_sha4 commit4
+
+$commit_sha3 commit3
+notes for commit3
+
+appended notes for commit3
+
+$commit_sha2 commit2
+new notes for commit2
+
+$commit_sha1 commit1
+other notes for commit1
+
+EOF
+
+test_expect_success 'successful merge using "theirs" strategy (z => w)' '
+       git update-ref refs/notes/m refs/notes/w &&
+       git notes merge -s theirs z &&
+       verify_notes m commit5 &&
+       # w/x/y/z unchanged
+       verify_notes w commit5 &&
+       verify_notes x commit5 &&
+       verify_notes y commit5 &&
+       verify_notes z commit5
+'
+
+test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m'
+
+cat >expect_log_m <<EOF
+$commit_sha5 commit5
+
+$commit_sha4 commit4
+other notes for commit4
+
+$commit_sha3 commit3
+other notes for commit3
+
+notes for commit3
+
+appended notes for commit3
+
+$commit_sha2 commit2
+new notes for commit2
+
+$commit_sha1 commit1
+other notes for commit1
+
+EOF
+
+test_expect_success 'successful merge using "union" strategy (z => w)' '
+       git update-ref refs/notes/m refs/notes/w &&
+       git notes merge -s union z &&
+       verify_notes m commit5 &&
+       # w/x/y/z unchanged
+       verify_notes w commit5 &&
+       verify_notes x commit5 &&
+       verify_notes y commit5 &&
+       verify_notes z commit5
+'
+
+test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m'
+
+cat >expect_log_m <<EOF
+$commit_sha5 commit5
+
+$commit_sha4 commit4
+other notes for commit4
+
+$commit_sha3 commit3
+appended notes for commit3
+notes for commit3
+other notes for commit3
+
+$commit_sha2 commit2
+new notes for commit2
+
+$commit_sha1 commit1
+other notes for commit1
+
+EOF
+
+test_expect_success 'successful merge using "cat_sort_uniq" strategy (z => w)' '
+       git update-ref refs/notes/m refs/notes/w &&
+       git notes merge -s cat_sort_uniq z &&
+       verify_notes m commit5 &&
+       # w/x/y/z unchanged
+       verify_notes w commit5 &&
+       verify_notes x commit5 &&
+       verify_notes y commit5 &&
+       verify_notes z commit5
+'
+
+test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m'
+
+# We're merging z into w. Here are the conflicts we expect:
+#
+# commit | x -> w    | x -> z    | conflict?
+# -------|-----------|-----------|----------
+# 1      | changed   | unchanged | no, use w
+# 2      | unchanged | changed   | no, use z
+# 3      | changed   | changed   | yes (w, then z in conflict markers)
+# 4      | changed   | deleted   | yes (w)
+# 5      | deleted   | deleted   | no, deleted
+
+test_expect_success 'fails to merge using "manual" strategy (z => w)' '
+       git update-ref refs/notes/m refs/notes/w &&
+       test_must_fail git notes merge z
+'
+
+test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m'
+
+cat <<EOF | sort >expect_conflicts
+$commit_sha3
+$commit_sha4
+EOF
+
+cat >expect_conflict_$commit_sha3 <<EOF
+<<<<<<< refs/notes/m
+other notes for commit3
+=======
+notes for commit3
+
+appended notes for commit3
+>>>>>>> refs/notes/z
+EOF
+
+cat >expect_conflict_$commit_sha4 <<EOF
+other notes for commit4
+EOF
+
+test_expect_success 'verify conflict entries (with no fanout)' '
+       ls .git/NOTES_MERGE_WORKTREE >output_conflicts &&
+       test_cmp expect_conflicts output_conflicts &&
+       ( for f in $(cat expect_conflicts); do
+               test_cmp "expect_conflict_$f" ".git/NOTES_MERGE_WORKTREE/$f" ||
+               exit 1
+       done ) &&
+       # Verify that current notes tree (pre-merge) has not changed (m == w)
+       test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)"
+'
+
+cat >expect_log_m <<EOF
+$commit_sha5 commit5
+
+$commit_sha4 commit4
+other notes for commit4
+
+$commit_sha3 commit3
+other notes for commit3
+
+appended notes for commit3
+
+$commit_sha2 commit2
+new notes for commit2
+
+$commit_sha1 commit1
+other notes for commit1
+
+EOF
+
+test_expect_success 'resolve and finalize merge (z => w)' '
+       cat >.git/NOTES_MERGE_WORKTREE/$commit_sha3 <<EOF &&
+other notes for commit3
+
+appended notes for commit3
+EOF
+       git notes merge --commit &&
+       verify_notes m commit5 &&
+       # w/x/y/z unchanged
+       verify_notes w commit5 &&
+       verify_notes x commit5 &&
+       verify_notes y commit5 &&
+       verify_notes z commit5
+'
+
+test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m'
+
+test_done
index 5cb7e70d54267b53ffb4b0f4c46875308d9de5a6..d3a3bd2679e061ce391930d2b59831aa7610926f 100755 (executable)
@@ -647,6 +647,7 @@ test_expect_success 'rebase -i can copy notes' '
 
 cat >expect <<EOF
 an earlier note
+
 a note
 EOF
 
index ca16b70373b5595f4a5e3c460638c7aa2edb8663..b38be8e93723991d717b6b7fb690560efb58c36d 100755 (executable)
@@ -14,6 +14,7 @@ test_expect_success setup '
        git add . &&
        test_tick &&
        git commit -m "first commit" &&
+       git tag first-commit &&
        echo 3 >file3 &&
        git add . &&
        test_tick &&
@@ -21,7 +22,7 @@ test_expect_success setup '
        git tag base
 '
 
-test_auto_fixup() {
+test_auto_fixup () {
        git reset --hard base &&
        echo 1 >file1 &&
        git add -u &&
@@ -50,7 +51,7 @@ test_expect_success 'auto fixup (config)' '
        test_must_fail test_auto_fixup final-fixup-config-false
 '
 
-test_auto_squash() {
+test_auto_squash () {
        git reset --hard base &&
        echo 1 >file1 &&
        git add -u &&
@@ -168,4 +169,28 @@ test_expect_success 'auto squash that matches longer sha1' '
        test 1 = $(git cat-file commit HEAD^ | grep squash | wc -l)
 '
 
+test_auto_commit_flags () {
+       git reset --hard base &&
+       echo 1 >file1 &&
+       git add -u &&
+       test_tick &&
+       git commit --$1 first-commit &&
+       git tag final-commit-$1 &&
+       test_tick &&
+       git rebase --autosquash -i HEAD^^^ &&
+       git log --oneline >actual &&
+       test 3 = $(wc -l <actual) &&
+       git diff --exit-code final-commit-$1 &&
+       test 1 = "$(git cat-file blob HEAD^:file1)" &&
+       test $2 = $(git cat-file commit HEAD^ | grep first | wc -l)
+}
+
+test_expect_success 'use commit --fixup' '
+       test_auto_commit_flags fixup 1
+'
+
+test_expect_success 'use commit --squash' '
+       test_auto_commit_flags squash 2
+'
+
 test_done
index bc7aedd0484ed5ad1115cac0f22943445d658f47..043954422c82d9afbb318bcb7d1aa5685a391c85 100755 (executable)
@@ -81,6 +81,16 @@ test_expect_success 'revert after renaming branch' '
 
 '
 
+test_expect_success 'cherry-pick on stat-dirty working tree' '
+       git clone . copy &&
+       (
+               cd copy &&
+               git checkout initial &&
+               test-chmtime +40 oops &&
+               git cherry-pick added
+       )
+'
+
 test_expect_success 'revert forbidden on dirty working tree' '
 
        echo content >extra_file &&
index a5ccdbf8fc22efb03891c6b5fa2c9715350a191f..948ca1bce66cab317bf74984b22384f2625418c1 100755 (executable)
@@ -32,4 +32,70 @@ test_expect_success SYMLINKS 'Cherry-pick succeeds with rename across D/F confli
        git cherry-pick branch
 '
 
+test_expect_success 'Setup rename with file on one side matching directory name on other' '
+       git checkout --orphan nick-testcase &&
+       git rm -rf . &&
+
+       >empty &&
+       git add empty &&
+       git commit -m "Empty file" &&
+
+       git checkout -b simple &&
+       mv empty file &&
+       mkdir empty &&
+       mv file empty &&
+       git add empty/file &&
+       git commit -m "Empty file under empty dir" &&
+
+       echo content >newfile &&
+       git add newfile &&
+       git commit -m "New file"
+'
+
+test_expect_success 'Cherry-pick succeeds with was_a_dir/file -> was_a_dir (resolve)' '
+       git reset --hard &&
+       git checkout -q nick-testcase^0 &&
+       git cherry-pick --strategy=resolve simple
+'
+
+test_expect_success 'Cherry-pick succeeds with was_a_dir/file -> was_a_dir (recursive)' '
+       git reset --hard &&
+       git checkout -q nick-testcase^0 &&
+       git cherry-pick --strategy=recursive simple
+'
+
+test_expect_success 'Setup rename with file on one side matching different dirname on other' '
+       git reset --hard &&
+       git checkout --orphan mergeme &&
+       git rm -rf . &&
+
+       mkdir sub &&
+       mkdir othersub &&
+       echo content > sub/file &&
+       echo foo > othersub/whatever &&
+       git add -A &&
+       git commit -m "Common commmit" &&
+
+       git rm -rf othersub &&
+       git mv sub/file othersub &&
+       git commit -m "Commit to merge" &&
+
+       git checkout -b newhead mergeme~1 &&
+       >independent-change &&
+       git add independent-change &&
+       git commit -m "Completely unrelated change"
+'
+
+test_expect_success 'Cherry-pick with rename to different D/F conflict succeeds (resolve)' '
+       git reset --hard &&
+       git checkout -q newhead^0 &&
+       git cherry-pick --strategy=resolve mergeme
+'
+
+test_expect_success 'Cherry-pick with rename to different D/F conflict succeeds (recursive)' '
+       git reset --hard &&
+       git checkout -q newhead^0 &&
+       git cherry-pick --strategy=recursive mergeme
+'
+
 test_done
index 256c4c970145aa9f59e58ee1b0da4c6281b6d9e5..c06a5ee7660c3fd7ca15860cfb761b2c4d953e08 100755 (executable)
@@ -133,4 +133,33 @@ do
        '
 done
 
+test_commit_autosquash_flags () {
+       H=$1
+       flag=$2
+       test_expect_success "commit --$flag with $H encoding" '
+               git config i18n.commitencoding $H &&
+               git checkout -b $H-$flag C0 &&
+               echo $H >>F &&
+               git commit -a -F "$TEST_DIRECTORY"/t3900/$H.txt &&
+               test_tick &&
+               echo intermediate stuff >>G &&
+               git add G &&
+               git commit -a -m "intermediate commit" &&
+               test_tick &&
+               echo $H $flag >>F &&
+               git commit -a --$flag HEAD~1 $3 &&
+               E=$(git cat-file commit '$H-$flag' |
+                       sed -ne "s/^encoding //p") &&
+               test "z$E" = "z$H" &&
+               git config --unset-all i18n.commitencoding &&
+               git rebase --autosquash -i HEAD^^^ &&
+               git log --oneline >actual &&
+               test 3 = $(wc -l <actual)
+       '
+}
+
+test_commit_autosquash_flags eucJP fixup
+
+test_commit_autosquash_flags ISO-2022-JP squash '-m "squash message"'
+
 test_done
index f7c85ec604df5e037990065ab9d3d82d86587076..3fa836f9d3c19f351ce5451049eb191776cbd4a5 100755 (executable)
@@ -179,6 +179,15 @@ test_expect_success 'trailing empty lines (2)' '
 
 '
 
+test_expect_success 'checkdiff shows correct line number for trailing blank lines' '
+
+       printf "a\nb\n" > G &&
+       git add G &&
+       printf "x\nx\nx\na\nb\nc\n\n" > G &&
+       [ "$(git diff --check -- G)" = "G:7: new blank line at EOF." ]
+
+'
+
 test_expect_success 'do not color trailing cr in context' '
        test_might_fail git config --unset core.whitespace &&
        rm -f .gitattributes &&
index 2b2d00b33410282b6decd0af9118ee3f79489e34..579c9e61054521301464bfc31351040d45966836 100755 (executable)
@@ -56,4 +56,30 @@ test_expect_success 'apply with too large -p and fancy filename' '
        grep "removing 3 leading" err
 '
 
+test_expect_success 'apply (-p2) diff, mode change only' '
+       cat >patch.chmod <<-\EOF &&
+       diff --git a/sub/file1 b/sub/file1
+       old mode 100644
+       new mode 100755
+       EOF
+       chmod 644 file1 &&
+       git apply -p2 patch.chmod &&
+       test -x file1
+'
+
+test_expect_success 'apply (-p2) diff, rename' '
+       cat >patch.rename <<-\EOF &&
+       diff --git a/sub/file1 b/sub/file2
+       similarity index 100%
+       rename from sub/file1
+       rename to sub/file2
+       EOF
+       echo A >expected &&
+
+       cp file1.saved file1 &&
+       rm -f file2 &&
+       git apply -p2 patch.rename &&
+       test_cmp expected file2
+'
+
 test_done
index bb1ffe3b6cfe491137b34993fe2e3bed99908dff..a2bc1cd37d852a9cab79c946bf88ee9dc958b460 100755 (executable)
@@ -30,6 +30,7 @@ test_expect_success setup '
        epocWest="1969-12-31 16:00:00.000000000 -0800" &&
         epocGMT="1970-01-01 00:00:00.000000000 +0000" &&
        epocEast="1970-01-01 09:00:00.000000000 +0900" &&
+       epocWest2="1969-12-31 16:00:00 -08:00" &&
 
        sed -e "s/TS0/$epocWest/" -e "s/TS1/$timeWest/" <c >createWest.patch &&
        sed -e "s/TS0/$epocEast/" -e "s/TS1/$timeEast/" <c >createEast.patch &&
@@ -46,6 +47,7 @@ test_expect_success setup '
        sed -e "s/TS0/$timeWest/" -e "s/TS1/$epocWest/" <d >removeWest.patch &&
        sed -e "s/TS0/$timeEast/" -e "s/TS1/$epocEast/" <d >removeEast.patch &&
        sed -e "s/TS0/$timeGMT/" -e "s/TS1/$epocGMT/" <d >removeGMT.patch &&
+       sed -e "s/TS0/$timeWest/" -e "s/TS1/$epocWest2/" <d >removeWest2.patch &&
 
        echo something >something &&
        >empty
index 1e5aad57ab799819c6f319eaf813149d8bdda136..bf5dc57286493b0b94caf927304e92db11e54f2c 100755 (executable)
@@ -72,4 +72,20 @@ test_expect_success 'whitespace-damaged traditional patch' '
        test_cmp expected postimage.txt
 '
 
+test_expect_success 'traditional patch with colon in timezone' '
+       echo postimage >expected &&
+       reset_preimage &&
+       rm -f "post image.txt" &&
+       git apply "$vector/funny-tz.diff" &&
+       test_cmp expected "post image.txt"
+'
+
+test_expect_success 'traditional, whitespace-damaged, colon in timezone' '
+       echo postimage >expected &&
+       reset_preimage &&
+       rm -f "post image.txt" &&
+       git apply "$vector/damaged-tz.diff" &&
+       test_cmp expected "post image.txt"
+'
+
 test_done
diff --git a/t/t4135/damaged-tz.diff b/t/t4135/damaged-tz.diff
new file mode 100644 (file)
index 0000000..07aaf08
--- /dev/null
@@ -0,0 +1,5 @@
+diff -urN -X /usr/people/jes/exclude-linux linux-2.6.12-rc2-mm3-vanilla/post image.txt linux-2.6.12-rc2-mm3/post image.txt
+--- linux-2.6.12-rc2-mm3-vanilla/post image.txt 1969-12-31 16:00:00 -08:00
++++ linux-2.6.12-rc2-mm3/post image.txt 2005-04-12 02:14:06 -07:00
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/funny-tz.diff b/t/t4135/funny-tz.diff
new file mode 100644 (file)
index 0000000..998e3a8
--- /dev/null
@@ -0,0 +1,5 @@
+diff -urN -X /usr/people/jes/exclude-linux linux-2.6.12-rc2-mm3-vanilla/post image.txt linux-2.6.12-rc2-mm3/post image.txt
+--- linux-2.6.12-rc2-mm3-vanilla/post image.txt        1969-12-31 16:00:00 -08:00
++++ linux-2.6.12-rc2-mm3/post image.txt        2005-04-12 02:14:06 -07:00
+@@ -0,0 +1 @@
++postimage
index a8c33d57031db00cc096a80861ecfb7e72faa635..2fcc31a6f3d346e533b697eecf853e219d174cd0 100755 (executable)
@@ -421,6 +421,15 @@ test_expect_success 'log.decorate configuration' '
        git log --oneline --decorate=full >actual &&
        test_cmp expect.full actual &&
 
+       git config --unset-all log.decorate &&
+       git config log.decorate 1 &&
+       git log --oneline >actual &&
+       test_cmp expect.short actual &&
+       git log --oneline --decorate=full >actual &&
+       test_cmp expect.full actual &&
+       git log --oneline --decorate=no >actual &&
+       test_cmp expect.none actual &&
+
        git config --unset-all log.decorate &&
        git config log.decorate short &&
        git log --oneline >actual &&
index 2fb48d09edb47129d9cea8b9686e46a6d8f35615..8c2ac353b7736c4bce7ed71333284863d9c4e514 100755 (executable)
@@ -34,6 +34,13 @@ test_expect_success 'clone http repository' '
        test_cmp file clone/file
 '
 
+test_expect_success 'clone http repository with authentication' '
+       mkdir "$HTTPD_DOCUMENT_ROOT_PATH/auth/" &&
+       cp -Rf "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" "$HTTPD_DOCUMENT_ROOT_PATH/auth/repo.git" &&
+       git clone $AUTH_HTTPD_URL/auth/repo.git clone-auth &&
+       test_cmp file clone-auth/file
+'
+
 test_expect_success 'fetch changes via http' '
        echo content >>file &&
        git commit -a -m two &&
index fd19121372aecc0806e17e62d639855f391045d3..26d355725f5e8d317c71cb466ea091ec8f741d63 100755 (executable)
@@ -101,5 +101,13 @@ test_expect_success 'used upload-pack service' '
        test_cmp exp act
 '
 
+test_expect_success 'follow redirects (301)' '
+       git clone $HTTPD_URL/smart-redir-perm/repo.git --quiet repo-p
+'
+
+test_expect_success 'follow redirects (302)' '
+       git clone $HTTPD_URL/smart-redir-temp/repo.git --quiet repo-t
+'
+
 stop_httpd
 test_done
index 5d91d056d2ff97bb4253671cde8ac9a207bd6bd9..eec8f4e3edd85e350f5b8d8c86415317cce2aba1 100755 (executable)
@@ -6,19 +6,22 @@
 test_description='Test merge with directory/file conflicts'
 . ./test-lib.sh
 
-test_expect_success 'prepare repository' \
-'echo "Hello" > init &&
-git add init &&
-git commit -m "Initial commit" &&
-git branch B &&
-mkdir dir &&
-echo "foo" > dir/foo &&
-git add dir/foo &&
-git commit -m "File: dir/foo" &&
-git checkout B &&
-echo "file dir" > dir &&
-git add dir &&
-git commit -m "File: dir"'
+test_expect_success 'prepare repository' '
+       echo Hello >init &&
+       git add init &&
+       git commit -m initial &&
+
+       git branch B &&
+       mkdir dir &&
+       echo foo >dir/foo &&
+       git add dir/foo &&
+       git commit -m "File: dir/foo" &&
+
+       git checkout B &&
+       echo file dir >dir &&
+       git add dir &&
+       git commit -m "File: dir"
+'
 
 test_expect_success 'Merge with d/f conflicts' '
        test_expect_code 1 git merge "merge msg" B master
@@ -47,4 +50,51 @@ test_expect_success 'F/D conflict' '
        git merge master
 '
 
+test_expect_success 'setup modify/delete + directory/file conflict' '
+       git checkout --orphan modify &&
+       git rm -rf . &&
+       git clean -fdqx &&
+
+       printf "a\nb\nc\nd\ne\nf\ng\nh\n" >letters &&
+       git add letters &&
+       git commit -m initial &&
+
+       echo i >>letters &&
+       git add letters &&
+       git commit -m modified &&
+
+       git checkout -b delete HEAD^ &&
+       git rm letters &&
+       mkdir letters &&
+       >letters/file &&
+       git add letters &&
+       git commit -m deleted
+'
+
+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 -f letters/file &&
+       test -f letters~modify
+'
+
+test_expect_success 'modify/delete + directory/file conflict; other way' '
+       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 -f letters/file &&
+       test -f letters~HEAD
+'
+
 test_done
index 83efc7abf5f15c9a5603107f2223454cd32f9abc..1ed259d864b4ef4c8c7060fa5f22634a25c8e032 100755 (executable)
@@ -3,6 +3,11 @@
 test_description='Merge-recursive merging renames'
 . ./test-lib.sh
 
+modify () {
+       sed -e "$1" <"$2" >"$2.x" &&
+       mv "$2.x" "$2"
+}
+
 test_expect_success setup \
 '
 cat >A <<\EOF &&
@@ -243,4 +248,365 @@ test_expect_success 'merge of identical changes in a renamed file' '
        GIT_MERGE_VERBOSITY=3 git merge change+rename | grep "^Skipped B"
 '
 
+test_expect_success 'setup for rename + d/f conflicts' '
+       git reset --hard &&
+       git checkout --orphan dir-in-way &&
+       git rm -rf . &&
+
+       mkdir sub &&
+       mkdir dir &&
+       printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >sub/file &&
+       echo foo >dir/file-in-the-way &&
+       git add -A &&
+       git commit -m "Common commmit" &&
+
+       echo 11 >>sub/file &&
+       echo more >>dir/file-in-the-way &&
+       git add -u &&
+       git commit -m "Commit to merge, with dir in the way" &&
+
+       git checkout -b dir-not-in-way &&
+       git reset --soft HEAD^ &&
+       git rm -rf dir &&
+       git commit -m "Commit to merge, with dir removed" -- dir sub/file &&
+
+       git checkout -b renamed-file-has-no-conflicts dir-in-way~1 &&
+       git rm -rf dir &&
+       git rm sub/file &&
+       printf "1\n2\n3\n4\n5555\n6\n7\n8\n9\n10\n" >dir &&
+       git add dir &&
+       git commit -m "Independent change" &&
+
+       git checkout -b renamed-file-has-conflicts dir-in-way~1 &&
+       git rm -rf dir &&
+       git mv sub/file dir &&
+       echo 12 >>dir &&
+       git add dir &&
+       git commit -m "Conflicting change"
+'
+
+printf "1\n2\n3\n4\n5555\n6\n7\n8\n9\n10\n11\n" >expected
+
+test_expect_success 'Rename+D/F conflict; renamed file merges + dir not in way' '
+       git reset --hard &&
+       git checkout -q renamed-file-has-no-conflicts^0 &&
+       git merge --strategy=recursive dir-not-in-way &&
+       git diff --quiet &&
+       test -f dir &&
+       test_cmp expected dir
+'
+
+test_expect_success 'Rename+D/F conflict; renamed file merges but dir in way' '
+       git reset --hard &&
+       rm -rf dir~* &&
+       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 "Auto-merging dir" output &&
+       grep "Adding as dir~HEAD instead" output &&
+
+       test 2 -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 &&
+       test_must_fail git diff --cached --quiet &&
+
+       test -f dir/file-in-the-way &&
+       test -f dir~HEAD &&
+       test_cmp expected dir~HEAD
+'
+
+test_expect_success 'Same as previous, but merged other way' '
+       git reset --hard &&
+       rm -rf dir~* &&
+       git checkout -q dir-in-way^0 &&
+       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 "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 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" &&
+
+       test_must_fail git diff --quiet &&
+       test_must_fail git diff --cached --quiet &&
+
+       test -f dir/file-in-the-way &&
+       test -f dir~renamed-file-has-no-conflicts &&
+       test_cmp expected dir~renamed-file-has-no-conflicts
+'
+
+cat >expected <<\EOF &&
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+<<<<<<< HEAD
+12
+=======
+11
+>>>>>>> dir-not-in-way
+EOF
+
+test_expect_success 'Rename+D/F conflict; renamed file cannot merge, dir not in way' '
+       git reset --hard &&
+       rm -rf dir~* &&
+       git checkout -q renamed-file-has-conflicts^0 &&
+       test_must_fail git merge --strategy=recursive dir-not-in-way &&
+
+       test 3 -eq "$(git ls-files -u | wc -l)" &&
+       test 3 -eq "$(git ls-files -u dir | wc -l)" &&
+
+       test_must_fail git diff --quiet &&
+       test_must_fail git diff --cached --quiet &&
+
+       test -f dir &&
+       test_cmp expected dir
+'
+
+test_expect_success 'Rename+D/F conflict; renamed file cannot merge and dir in the way' '
+       modify s/dir-not-in-way/dir-in-way/ expected &&
+
+       git reset --hard &&
+       rm -rf dir~* &&
+       git checkout -q renamed-file-has-conflicts^0 &&
+       test_must_fail git merge --strategy=recursive dir-in-way &&
+
+       test 5 -eq "$(git ls-files -u | wc -l)" &&
+       test 3 -eq "$(git ls-files -u dir | grep -v file-in-the-way | wc -l)" &&
+       test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" &&
+
+       test_must_fail git diff --quiet &&
+       test_must_fail git diff --cached --quiet &&
+
+       test -f dir/file-in-the-way &&
+       test -f dir~HEAD &&
+       test_cmp expected dir~HEAD
+'
+
+cat >expected <<\EOF &&
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+<<<<<<< HEAD
+11
+=======
+12
+>>>>>>> renamed-file-has-conflicts
+EOF
+
+test_expect_success 'Same as previous, but merged other way' '
+       git reset --hard &&
+       rm -rf dir~* &&
+       git checkout -q dir-in-way^0 &&
+       test_must_fail git merge --strategy=recursive renamed-file-has-conflicts &&
+
+       test 5 -eq "$(git ls-files -u | wc -l)" &&
+       test 3 -eq "$(git ls-files -u dir | grep -v file-in-the-way | wc -l)" &&
+       test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" &&
+
+       test_must_fail git diff --quiet &&
+       test_must_fail git diff --cached --quiet &&
+
+       test -f dir/file-in-the-way &&
+       test -f dir~renamed-file-has-conflicts &&
+       test_cmp expected dir~renamed-file-has-conflicts
+'
+
+test_expect_success 'setup both rename source and destination involved in D/F conflict' '
+       git reset --hard &&
+       git checkout --orphan rename-dest &&
+       git rm -rf . &&
+       git clean -fdqx &&
+
+       mkdir one &&
+       echo stuff >one/file &&
+       git add -A &&
+       git commit -m "Common commmit" &&
+
+       git mv one/file destdir &&
+       git commit -m "Renamed to destdir" &&
+
+       git checkout -b source-conflict HEAD~1 &&
+       git rm -rf one &&
+       mkdir destdir &&
+       touch one destdir/foo &&
+       git add -A &&
+       git commit -m "Conflicts in the way"
+'
+
+test_expect_success 'both rename source and destination involved in D/F conflict' '
+       git reset --hard &&
+       rm -rf dir~* &&
+       git checkout -q rename-dest^0 &&
+       test_must_fail git merge --strategy=recursive source-conflict &&
+
+       test 1 -eq "$(git ls-files -u | wc -l)" &&
+
+       test_must_fail git diff --quiet &&
+
+       test -f destdir/foo &&
+       test -f one &&
+       test -f destdir~HEAD &&
+       test "stuff" = "$(cat destdir~HEAD)"
+'
+
+test_expect_success 'setup pair rename to parent of other (D/F conflicts)' '
+       git reset --hard &&
+       git checkout --orphan rename-two &&
+       git rm -rf . &&
+       git clean -fdqx &&
+
+       mkdir one &&
+       mkdir two &&
+       echo stuff >one/file &&
+       echo other >two/file &&
+       git add -A &&
+       git commit -m "Common commmit" &&
+
+       git rm -rf one &&
+       git mv two/file one &&
+       git commit -m "Rename two/file -> one" &&
+
+       git checkout -b rename-one HEAD~1 &&
+       git rm -rf two &&
+       git mv one/file two &&
+       rm -r one &&
+       git commit -m "Rename one/file -> two"
+'
+
+test_expect_success 'pair rename to parent of other (D/F conflicts) w/ untracked dir' '
+       git checkout -q rename-one^0 &&
+       mkdir one &&
+       test_must_fail git merge --strategy=recursive rename-two &&
+
+       test 2 -eq "$(git ls-files -u | wc -l)" &&
+       test 1 -eq "$(git ls-files -u one | wc -l)" &&
+       test 1 -eq "$(git ls-files -u two | wc -l)" &&
+
+       test_must_fail git diff --quiet &&
+
+       test 4 -eq $(find . | grep -v .git | wc -l) &&
+
+       test -d one &&
+       test -f one~rename-two &&
+       test -f two &&
+       test "other" = $(cat one~rename-two) &&
+       test "stuff" = $(cat two)
+'
+
+test_expect_success 'pair rename to parent of other (D/F conflicts) w/ clean start' '
+       git reset --hard &&
+       git clean -fdqx &&
+       test_must_fail git merge --strategy=recursive rename-two &&
+
+       test 2 -eq "$(git ls-files -u | wc -l)" &&
+       test 1 -eq "$(git ls-files -u one | wc -l)" &&
+       test 1 -eq "$(git ls-files -u two | wc -l)" &&
+
+       test_must_fail git diff --quiet &&
+
+       test 3 -eq $(find . | grep -v .git | wc -l) &&
+
+       test -f one &&
+       test -f two &&
+       test "other" = $(cat one) &&
+       test "stuff" = $(cat two)
+'
+
+test_expect_success 'setup rename of one file to two, with directories in the way' '
+       git reset --hard &&
+       git checkout --orphan first-rename &&
+       git rm -rf . &&
+       git clean -fdqx &&
+
+       echo stuff >original &&
+       git add -A &&
+       git commit -m "Common commmit" &&
+
+       mkdir two &&
+       >two/file &&
+       git add two/file &&
+       git mv original one &&
+       git commit -m "Put two/file in the way, rename to one" &&
+
+       git checkout -b second-rename HEAD~1 &&
+       mkdir one &&
+       >one/file &&
+       git add one/file &&
+       git mv original two &&
+       git commit -m "Put one/file in the way, rename to two"
+'
+
+test_expect_success 'check handling of differently renamed file with D/F conflicts' '
+       git checkout -q first-rename^0 &&
+       test_must_fail git merge --strategy=recursive second-rename &&
+
+       test 5 -eq "$(git ls-files -s | wc -l)" &&
+       test 3 -eq "$(git ls-files -u | wc -l)" &&
+       test 1 -eq "$(git ls-files -u one | wc -l)" &&
+       test 1 -eq "$(git ls-files -u two | wc -l)" &&
+       test 1 -eq "$(git ls-files -u original | wc -l)" &&
+       test 2 -eq "$(git ls-files -o | wc -l)" &&
+
+       test -f one/file &&
+       test -f two/file &&
+       test -f one~HEAD &&
+       test -f two~second-rename &&
+       ! test -f original
+'
+
+test_expect_success 'setup rename one file to two; directories moving out of the way' '
+       git reset --hard &&
+       git checkout --orphan first-rename-redo &&
+       git rm -rf . &&
+       git clean -fdqx &&
+
+       echo stuff >original &&
+       mkdir one two &&
+       touch one/file two/file &&
+       git add -A &&
+       git commit -m "Common commmit" &&
+
+       git rm -rf one &&
+       git mv original one &&
+       git commit -m "Rename to one" &&
+
+       git checkout -b second-rename-redo HEAD~1 &&
+       git rm -rf two &&
+       git mv original two &&
+       git commit -m "Rename to two"
+'
+
+test_expect_success 'check handling of differently renamed file with D/F conflicts' '
+       git checkout -q first-rename-redo^0 &&
+       test_must_fail git merge --strategy=recursive second-rename-redo &&
+
+       test 3 -eq "$(git ls-files -u | wc -l)" &&
+       test 1 -eq "$(git ls-files -u one | wc -l)" &&
+       test 1 -eq "$(git ls-files -u two | wc -l)" &&
+       test 1 -eq "$(git ls-files -u original | wc -l)" &&
+       test 0 -eq "$(git ls-files -o | wc -l)" &&
+
+       test -f one &&
+       test -f two &&
+       ! test -f original
+'
+
 test_done
index eac5ebac24a0174fa20625a19835861573147a26..fdb6c253718e3d4c8acfcd1fc197607aac04b4c4 100755 (executable)
@@ -70,4 +70,34 @@ test_expect_success 'set merge.renamelimit to 5' '
 test_rename 5 ok
 test_rename 6 fail
 
+test_expect_success 'setup large simple rename' '
+       git config --unset merge.renamelimit &&
+       git config --unset diff.renamelimit &&
+
+       git reset --hard initial &&
+       for i in $(count 200); do
+               make_text foo bar baz >$i
+       done &&
+       git add . &&
+       git commit -m create-files &&
+
+       git branch simple-change &&
+       git checkout -b simple-rename &&
+
+       mkdir builtin &&
+       git mv [0-9]* builtin/ &&
+       git commit -m renamed &&
+
+       git checkout simple-change &&
+       >unrelated-change &&
+       git add unrelated-change &&
+       git commit -m unrelated-change
+'
+
+test_expect_success 'massive simple rename does not spam added files' '
+       unset GIT_MERGE_VERBOSITY &&
+       git merge --no-stat simple-rename | grep -v Removing >output &&
+       test 5 -gt "$(wc -l < output)"
+'
+
 test_done
index 004c365ad6b32fff16c3c96ef2b6a173157b02a8..871577d90cc2817ffcc0cfe1ecf76aab6be61723 100755 (executable)
@@ -14,7 +14,7 @@ test_description='recursive merge corner cases'
 #  R1  R2
 #
 
-test_expect_success setup '
+test_expect_success 'setup basic criss-cross + rename with no modifications' '
        ten="0 1 2 3 4 5 6 7 8 9" &&
        for i in $ten
        do
@@ -45,11 +45,190 @@ test_expect_success setup '
        git tag R2
 '
 
-test_expect_success merge '
+test_expect_success 'merge simple rename+criss-cross with no modifications' '
        git reset --hard &&
        git checkout L2^0 &&
 
-       test_must_fail git merge -s recursive R2^0
+       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 $(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)
+'
+
+#
+# Same as before, but modify L1 slightly:
+#
+#  L1m L2
+#   o---o
+#  / \ / \
+# o   X   ?
+#  \ / \ /
+#   o---o
+#  R1  R2
+#
+
+test_expect_success 'setup criss-cross + rename merges with basic modification' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       ten="0 1 2 3 4 5 6 7 8 9"
+       for i in $ten
+       do
+               echo line $i in a sample file
+       done >one &&
+       for i in $ten
+       do
+               echo line $i in another sample file
+       done >two &&
+       git add one two &&
+       test_tick && git commit -m initial &&
+
+       git branch L1 &&
+       git checkout -b R1 &&
+       git mv one three &&
+       echo more >>two &&
+       git add two &&
+       test_tick && git commit -m R1 &&
+
+       git checkout L1 &&
+       git mv two three &&
+       test_tick && git commit -m L1 &&
+
+       git checkout L1^0 &&
+       test_tick && git merge -s ours R1 &&
+       git tag L2 &&
+
+       git checkout R1^0 &&
+       test_tick && git merge -s ours L1 &&
+       git tag R2
+'
+
+test_expect_success 'merge criss-cross + rename merges with basic modification' '
+       git reset --hard &&
+       git checkout L2^0 &&
+
+       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 $(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)
+'
+
+#
+# For the next test, we start with three commits in two lines of development
+# which setup a rename/add conflict:
+#   Commit A: File 'a' exists
+#   Commit B: Rename 'a' -> 'new_a'
+#   Commit C: Modify 'a', create different 'new_a'
+# Later, two different people merge and resolve differently:
+#   Commit D: Merge B & C, ignoring separately created 'new_a'
+#   Commit E: Merge B & C making use of some piece of secondary 'new_a'
+# Finally, someone goes to merge D & E.  Does git detect the conflict?
+#
+#      B   D
+#      o---o
+#     / \ / \
+#  A o   X   ? F
+#     \ / \ /
+#      o---o
+#      C   E
+#
+
+test_expect_success 'setup differently handled merges of rename/add conflict' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       printf "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n" >a &&
+       git add a &&
+       test_tick && git commit -m A &&
+
+       git branch B &&
+       git checkout -b C &&
+       echo 10 >>a &&
+       echo "other content" >>new_a &&
+       git add a new_a &&
+       test_tick && git commit -m C &&
+
+       git checkout B &&
+       git mv a new_a &&
+       test_tick && git commit -m B &&
+
+       git checkout B^0 &&
+       test_must_fail git merge C &&
+       git clean -f &&
+       test_tick && git commit -m D &&
+       git tag D &&
+
+       git checkout C^0 &&
+       test_must_fail git merge B &&
+       rm new_a~HEAD new_a &&
+       printf "Incorrectly merged content" >>new_a &&
+       git add -u &&
+       test_tick && git commit -m E &&
+       git tag E
+'
+
+test_expect_success 'git detects differently handled merges conflict' '
+       git reset --hard &&
+       git checkout D^0 &&
+
+       git merge -s recursive E^0 && {
+               echo "BAD: should have conflicted"
+               test "Incorrectly merged content" = "$(cat new_a)" &&
+                       echo "BAD: Silently accepted wrong content"
+               return 1
+       }
+
+       test 3 = $(git ls-files -s | wc -l) &&
+       test 3 = $(git ls-files -u | wc -l) &&
+       test 0 = $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse :2:new_a) = $(git rev-parse D:new_a) &&
+       test $(git rev-parse :3:new_a) = $(git rev-parse E:new_a) &&
+
+       git cat-file -p B:new_a >>merged &&
+       git cat-file -p C:new_a >>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:new_a) = $(git hash-object merged)
 '
 
 test_done
diff --git a/t/t6500-gc.sh b/t/t6500-gc.sh
new file mode 100755 (executable)
index 0000000..82f3639
--- /dev/null
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+test_description='basic git gc tests
+'
+
+. ./test-lib.sh
+
+test_expect_success 'gc empty repository' '
+       git gc
+'
+
+test_expect_success 'gc --gobbledegook' '
+       test_expect_code 129 git gc --nonsense 2>err &&
+       grep "[Uu]sage: git gc" err
+'
+
+test_expect_success 'gc -h with invalid configuration' '
+       mkdir broken &&
+       (
+               cd broken &&
+               git init &&
+               echo "[gc] pruneexpire = CORRUPT" >>.git/config &&
+               test_expect_code 129 git gc -h >usage 2>&1
+       ) &&
+       grep "[Uu]sage" broken/usage
+'
+
+test_done
index f160af3ccc8714700218efcd3b38c216ff7bab43..3e7baaf89f9bada46fc084e568544ce9df833594 100755 (executable)
@@ -1030,6 +1030,72 @@ test_expect_success GPG \
        test_cmp expect actual
 '
 
+# usage with rfc1991 signatures
+echo "rfc1991" > gpghome/gpg.conf
+get_tag_header rfc1991-signed-tag $commit commit $time >expect
+echo "RFC1991 signed tag" >>expect
+echo '-----BEGIN PGP MESSAGE-----' >>expect
+test_expect_success GPG \
+       'creating a signed tag with rfc1991' '
+       git tag -s -m "RFC1991 signed tag" rfc1991-signed-tag $commit &&
+       get_tag_msg rfc1991-signed-tag >actual &&
+       test_cmp expect actual
+'
+
+cat >fakeeditor <<'EOF'
+#!/bin/sh
+cp "$1" actual
+EOF
+chmod +x fakeeditor
+
+test_expect_success GPG \
+       'reediting a signed tag body omits signature' '
+       echo "RFC1991 signed tag" >expect &&
+       GIT_EDITOR=./fakeeditor git tag -f -s rfc1991-signed-tag $commit &&
+       test_cmp expect actual
+'
+
+test_expect_success GPG \
+       'verifying rfc1991 signature' '
+       git tag -v rfc1991-signed-tag
+'
+
+test_expect_success GPG \
+       'list tag with rfc1991 signature' '
+       echo "rfc1991-signed-tag RFC1991 signed tag" >expect &&
+       git tag -l -n1 rfc1991-signed-tag >actual &&
+       test_cmp expect actual &&
+       git tag -l -n2 rfc1991-signed-tag >actual &&
+       test_cmp expect actual &&
+       git tag -l -n999 rfc1991-signed-tag >actual &&
+       test_cmp expect actual
+'
+
+rm -f gpghome/gpg.conf
+
+test_expect_success GPG \
+       'verifying rfc1991 signature without --rfc1991' '
+       git tag -v rfc1991-signed-tag
+'
+
+test_expect_success GPG \
+       'list tag with rfc1991 signature without --rfc1991' '
+       echo "rfc1991-signed-tag RFC1991 signed tag" >expect &&
+       git tag -l -n1 rfc1991-signed-tag >actual &&
+       test_cmp expect actual &&
+       git tag -l -n2 rfc1991-signed-tag >actual &&
+       test_cmp expect actual &&
+       git tag -l -n999 rfc1991-signed-tag >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success GPG \
+       'reediting a signed tag body omits signature' '
+       echo "RFC1991 signed tag" >expect &&
+       GIT_EDITOR=./fakeeditor git tag -f -s rfc1991-signed-tag $commit &&
+       test_cmp expect actual
+'
+
 # try to sign with bad user.signingkey
 git config user.signingkey BobTheMouse
 test_expect_success GPG \
index e9d8b9110d3bb14a0c1d5b325b875abce57b3318..ed7575d0fdf9eebf65d808fa810ab4cc03095025 100755 (executable)
@@ -401,4 +401,33 @@ test_core_pager_subdir    expect_success 'git -p shortlog'
 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;
+       echo "foo:initial" >expect &&
+       >actual &&
+       git config --unset core.pager &&
+       git 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;
+       echo "foo:initial" >expect &&
+       >actual &&
+       git config core.pager "exit 1"
+       git 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 overridden by environment' '
+       GIT_PAGER="sed s/^/foo:/ >actual" && export GIT_PAGER &&
+       >actual &&
+       echo "foo:initial" >expect &&
+       git config pager.log "exit 1" &&
+       test_terminal git log --format=%s -1 &&
+       test_cmp expect actual
+'
+
 test_done
index c802ef826115cb130370a815ee1b15a5b10290ee..02f67b73b762850f3c7f0faf0886bf4011f57308 100755 (executable)
@@ -179,7 +179,7 @@ test_expect_success 'git clean -d with prefix and path' '
 
 '
 
-test_expect_success 'git clean symbolic link' '
+test_expect_success SYMLINKS 'git clean symbolic link' '
 
        mkdir -p build docs &&
        touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
index aa9c577e9e306bce05cd0fe076c56017c1b97e41..162527c2114955df2c55db9bf37db59d05fe75f8 100755 (executable)
@@ -215,4 +215,84 @@ test_expect_success 'Commit a message with --allow-empty-message' '
        commit_msg_is "hello there"
 '
 
+commit_for_rebase_autosquash_setup () {
+       echo "first content line" >>foo &&
+       git add foo &&
+       cat >log <<EOF &&
+target message subject line
+
+target message body line 1
+target message body line 2
+EOF
+       git commit -F log &&
+       echo "second content line" >>foo &&
+       git add foo &&
+       git commit -m "intermediate commit" &&
+       echo "third content line" >>foo &&
+       git add foo
+}
+
+test_expect_success 'commit --fixup provides correct one-line commit message' '
+       commit_for_rebase_autosquash_setup &&
+       git commit --fixup HEAD~1 &&
+       commit_msg_is "fixup! target message subject line"
+'
+
+test_expect_success 'commit --squash works with -F' '
+       commit_for_rebase_autosquash_setup &&
+       echo "log message from file" >msgfile &&
+       git commit --squash HEAD~1 -F msgfile  &&
+       commit_msg_is "squash! target message subject linelog message from file"
+'
+
+test_expect_success 'commit --squash works with -m' '
+       commit_for_rebase_autosquash_setup &&
+       git commit --squash HEAD~1 -m "foo bar\nbaz" &&
+       commit_msg_is "squash! target message subject linefoo bar\nbaz"
+'
+
+test_expect_success 'commit --squash works with -C' '
+       commit_for_rebase_autosquash_setup &&
+       git commit --squash HEAD~1 -C HEAD &&
+       commit_msg_is "squash! target message subject lineintermediate commit"
+'
+
+test_expect_success 'commit --squash works with -c' '
+       commit_for_rebase_autosquash_setup &&
+       test_set_editor "$TEST_DIRECTORY"/t7500/edit-content &&
+       git commit --squash HEAD~1 -c HEAD &&
+       commit_msg_is "squash! target message subject lineedited commit"
+'
+
+test_expect_success 'commit --squash works with -C for same commit' '
+       commit_for_rebase_autosquash_setup &&
+       git commit --squash HEAD -C HEAD &&
+       commit_msg_is "squash! intermediate commit"
+'
+
+test_expect_success 'commit --squash works with -c for same commit' '
+       commit_for_rebase_autosquash_setup &&
+       test_set_editor "$TEST_DIRECTORY"/t7500/edit-content &&
+       git commit --squash HEAD -c HEAD &&
+       commit_msg_is "squash! edited commit"
+'
+
+test_expect_success 'commit --squash works with editor' '
+       commit_for_rebase_autosquash_setup &&
+       test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
+       git commit --squash HEAD~1 &&
+       commit_msg_is "squash! target message subject linecommit message"
+'
+
+test_expect_success 'invalid message options when using --fixup' '
+       echo changes >>foo &&
+       echo "message" >log &&
+       git add foo &&
+       test_must_fail git commit --fixup HEAD~1 --squash HEAD~2 &&
+       test_must_fail git commit --fixup HEAD~1 -C HEAD~2 &&
+       test_must_fail git commit --fixup HEAD~1 -c HEAD~2 &&
+       test_must_fail git commit --fixup HEAD~1 -m "cmdline message" &&
+       test_must_fail git commit --fixup HEAD~1 -F log
+'
+
 test_done
diff --git a/t/t7500/edit-content b/t/t7500/edit-content
new file mode 100755 (executable)
index 0000000..08db9fd
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/sh
+sed -e "s/intermediate/edited/g" <"$1" >"$1-"
+mv "$1-" "$1"
+exit 0
index 4de3e27950ca34ffe3f5e72052108ce9d74b0227..b73ab42936a8ea10dcdf40a6ad8b83d4a1d10056 100755 (executable)
@@ -7,6 +7,30 @@ test_description='git status'
 
 . ./test-lib.sh
 
+test_expect_success 'status -h in broken repository' '
+       mkdir broken &&
+       test_when_finished "rm -fr broken" &&
+       (
+               cd broken &&
+               git init &&
+               echo "[status] showuntrackedfiles = CORRUPT" >>.git/config &&
+               test_expect_code 129 git status -h >usage 2>&1
+       ) &&
+       grep "[Uu]sage" broken/usage
+'
+
+test_expect_success 'commit -h in broken repository' '
+       mkdir broken &&
+       test_when_finished "rm -fr broken" &&
+       (
+               cd broken &&
+               git init &&
+               echo "[status] showuntrackedfiles = CORRUPT" >>.git/config &&
+               test_expect_code 129 git commit -h >usage 2>&1
+       ) &&
+       grep "[Uu]sage" broken/usage
+'
+
 test_expect_success 'setup' '
        : >tracked &&
        : >modified &&
index b4f40e4c3a53388e72347e8e3894edd05f17dab5..b147a1bd69e96e48d3808028a3775a609471639a 100755 (executable)
@@ -144,6 +144,17 @@ test_expect_success 'test option parsing' '
        test_must_fail git merge
 '
 
+test_expect_success 'merge -h with invalid index' '
+       mkdir broken &&
+       (
+               cd broken &&
+               git init &&
+               >.git/index &&
+               test_expect_code 129 git merge -h 2>usage
+       ) &&
+       grep "[Uu]sage: git merge" broken/usage
+'
+
 test_expect_success 'reject non-strategy with a git-merge-foo name' '
        test_must_fail git merge -s index c1
 '
index 3988900fc33f0f5f9cdd416096df619aec2d577e..4d5ce4e682c1ea69034c3f7789a8ac0c89b12466 100755 (executable)
@@ -7,48 +7,54 @@ Do not overwrite changes.'
 . ./test-lib.sh
 
 test_expect_success 'setup' '
-       echo c0 > c0.c &&
-       git add c0.c &&
-       git commit -m c0 &&
-       git tag c0 &&
-       echo c1 > c1.c &&
-       git add c1.c &&
-       git commit -m c1 &&
-       git tag c1 &&
+       test_commit c0 c0.c &&
+       test_commit c1 c1.c &&
+       test_commit c1a c1.c "c1 a" &&
        git reset --hard c0 &&
-       echo c2 > c2.c &&
-       git add c2.c &&
-       git commit -m c2 &&
-       git tag c2 &&
-       git reset --hard c1 &&
-       echo "c1 a" > c1.c &&
-       git add c1.c &&
-       git commit -m "c1 a" &&
-       git tag c1a &&
+       test_commit c2 c2.c &&
+       git reset --hard c0 &&
+       mkdir sub &&
+       echo "sub/f" > sub/f &&
+       mkdir sub2 &&
+       echo "sub2/f" > sub2/f &&
+       git add sub/f sub2/f &&
+       git commit -m sub &&
+       git tag sub &&
        echo "VERY IMPORTANT CHANGES" > important
 '
 
 test_expect_success 'will not overwrite untracked file' '
        git reset --hard c1 &&
-       cat important > c2.c &&
+       cp important c2.c &&
        test_must_fail git merge c2 &&
+       test_path_is_missing .git/MERGE_HEAD &&
        test_cmp important c2.c
 '
 
+test_expect_success 'will overwrite tracked file' '
+       git reset --hard c1 &&
+       cp important c2.c &&
+       git add c2.c &&
+       git commit -m important &&
+       git checkout c2
+'
+
 test_expect_success 'will not overwrite new file' '
        git reset --hard c1 &&
-       cat important > c2.c &&
+       cp important c2.c &&
        git add c2.c &&
        test_must_fail git merge c2 &&
+       test_path_is_missing .git/MERGE_HEAD &&
        test_cmp important c2.c
 '
 
 test_expect_success 'will not overwrite staged changes' '
        git reset --hard c1 &&
-       cat important > c2.c &&
+       cp important c2.c &&
        git add c2.c &&
        rm c2.c &&
        test_must_fail git merge c2 &&
+       test_path_is_missing .git/MERGE_HEAD &&
        git checkout c2.c &&
        test_cmp important c2.c
 '
@@ -57,7 +63,7 @@ test_expect_success 'will not overwrite removed file' '
        git reset --hard c1 &&
        git rm c1.c &&
        git commit -m "rm c1.c" &&
-       cat important > c1.c &&
+       cp important c1.c &&
        test_must_fail git merge c1a &&
        test_cmp important c1.c
 '
@@ -66,9 +72,10 @@ test_expect_success 'will not overwrite re-added file' '
        git reset --hard c1 &&
        git rm c1.c &&
        git commit -m "rm c1.c" &&
-       cat important > c1.c &&
+       cp important c1.c &&
        git add c1.c &&
        test_must_fail git merge c1a &&
+       test_path_is_missing .git/MERGE_HEAD &&
        test_cmp important c1.c
 '
 
@@ -76,14 +83,63 @@ test_expect_success 'will not overwrite removed file with staged changes' '
        git reset --hard c1 &&
        git rm c1.c &&
        git commit -m "rm c1.c" &&
-       cat important > c1.c &&
+       cp important c1.c &&
        git add c1.c &&
        rm c1.c &&
        test_must_fail git merge c1a &&
+       test_path_is_missing .git/MERGE_HEAD &&
        git checkout c1.c &&
        test_cmp important c1.c
 '
 
+test_expect_success 'will not overwrite untracked subtree' '
+       git reset --hard c0 &&
+       rm -rf sub &&
+       mkdir -p sub/f &&
+       cp important sub/f/important &&
+       test_must_fail git merge sub &&
+       test_path_is_missing .git/MERGE_HEAD &&
+       test_cmp important sub/f/important
+'
+
+cat >expect <<\EOF
+error: The following untracked working tree files would be overwritten by merge:
+       sub
+       sub2
+Please move or remove them before you can merge.
+EOF
+
+test_expect_success 'will not overwrite untracked file in leading path' '
+       git reset --hard c0 &&
+       rm -rf sub &&
+       cp important sub &&
+       cp important sub2 &&
+       test_must_fail git merge sub 2>out &&
+       test_cmp out expect &&
+       test_path_is_missing .git/MERGE_HEAD &&
+       test_cmp important sub &&
+       test_cmp important sub2 &&
+       rm -f sub sub2
+'
+
+test_expect_failure SYMLINKS 'will not overwrite untracked symlink in leading path' '
+       git reset --hard c0 &&
+       rm -rf sub &&
+       mkdir sub2 &&
+       ln -s sub2 sub &&
+       test_must_fail git merge sub &&
+       test_path_is_missing .git/MERGE_HEAD
+'
+
+test_expect_success SYMLINKS 'will not be confused by symlink in leading path' '
+       git reset --hard c0 &&
+       rm -rf sub &&
+       ln -s sub2 sub &&
+       git add sub &&
+       git commit -m ln &&
+       git checkout sub
+'
+
 cat >expect <<\EOF
 error: Untracked working tree file 'c0.c' would be overwritten by merge.
 fatal: read-tree failed
diff --git a/t/t7609-merge-abort.sh b/t/t7609-merge-abort.sh
new file mode 100755 (executable)
index 0000000..61890bc
--- /dev/null
@@ -0,0 +1,313 @@
+#!/bin/sh
+
+test_description='test aborting in-progress merges
+
+Set up repo with conflicting and non-conflicting branches:
+
+There are three files foo/bar/baz, and the following graph illustrates the
+content of these files in each commit:
+
+# foo/bar/baz --- foo/bar/bazz     <-- master
+#             \
+#              --- foo/barf/bazf   <-- conflict_branch
+#               \
+#                --- foo/bart/baz  <-- clean_branch
+
+Next, test git merge --abort with the following variables:
+- before/after successful merge (should fail when not in merge context)
+- with/without conflicts
+- clean/dirty index before merge
+- clean/dirty worktree before merge
+- dirty index before merge matches contents on remote branch
+- changed/unchanged worktree after merge
+- changed/unchanged index after merge
+'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       # Create the above repo
+       echo foo > foo &&
+       echo bar > bar &&
+       echo baz > baz &&
+       git add foo bar baz &&
+       git commit -m initial &&
+       echo bazz > baz &&
+       git commit -a -m "second" &&
+       git checkout -b conflict_branch HEAD^ &&
+       echo barf > bar &&
+       echo bazf > baz &&
+       git commit -a -m "conflict" &&
+       git checkout -b clean_branch HEAD^ &&
+       echo bart > bar &&
+       git commit -a -m "clean" &&
+       git checkout master
+'
+
+pre_merge_head="$(git rev-parse HEAD)"
+
+test_expect_success 'fails without MERGE_HEAD (unstarted merge)' '
+       test_must_fail git merge --abort 2>output &&
+       grep -q MERGE_HEAD output &&
+       test ! -f .git/MERGE_HEAD &&
+       test "$pre_merge_head" = "$(git rev-parse HEAD)"
+'
+
+test_expect_success 'fails without MERGE_HEAD (completed merge)' '
+       git merge clean_branch &&
+       test ! -f .git/MERGE_HEAD &&
+       # Merge successfully completed
+       post_merge_head="$(git rev-parse HEAD)" &&
+       test_must_fail git merge --abort 2>output &&
+       grep -q MERGE_HEAD output &&
+       test ! -f .git/MERGE_HEAD &&
+       test "$post_merge_head" = "$(git rev-parse HEAD)"
+'
+
+test_expect_success 'Forget previous merge' '
+       git reset --hard "$pre_merge_head"
+'
+
+test_expect_success 'Abort after --no-commit' '
+       # Redo merge, but stop before creating merge commit
+       git merge --no-commit clean_branch &&
+       test -f .git/MERGE_HEAD &&
+       # Abort non-conflicting merge
+       git merge --abort &&
+       test ! -f .git/MERGE_HEAD &&
+       test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+       test -z "$(git diff)" &&
+       test -z "$(git diff --staged)"
+'
+
+test_expect_success 'Abort after conflicts' '
+       # Create conflicting merge
+       test_must_fail git merge conflict_branch &&
+       test -f .git/MERGE_HEAD &&
+       # Abort conflicting merge
+       git merge --abort &&
+       test ! -f .git/MERGE_HEAD &&
+       test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+       test -z "$(git diff)" &&
+       test -z "$(git diff --staged)"
+'
+
+test_expect_success 'Clean merge with dirty index fails' '
+       echo xyzzy >> foo &&
+       git add foo &&
+       git diff --staged > expect &&
+       test_must_fail git merge clean_branch &&
+       test ! -f .git/MERGE_HEAD &&
+       test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+       test -z "$(git diff)" &&
+       git diff --staged > actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'Conflicting merge with dirty index fails' '
+       test_must_fail git merge conflict_branch &&
+       test ! -f .git/MERGE_HEAD &&
+       test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+       test -z "$(git diff)" &&
+       git diff --staged > actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'Reset index (but preserve worktree changes)' '
+       git reset "$pre_merge_head" &&
+       git diff > actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'Abort clean merge with non-conflicting dirty worktree' '
+       git merge --no-commit clean_branch &&
+       test -f .git/MERGE_HEAD &&
+       # Abort merge
+       git merge --abort &&
+       test ! -f .git/MERGE_HEAD &&
+       test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+       test -z "$(git diff --staged)" &&
+       git diff > actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'Abort conflicting merge with non-conflicting dirty worktree' '
+       test_must_fail git merge conflict_branch &&
+       test -f .git/MERGE_HEAD &&
+       # Abort merge
+       git merge --abort &&
+       test ! -f .git/MERGE_HEAD &&
+       test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+       test -z "$(git diff --staged)" &&
+       git diff > actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'Reset worktree changes' '
+       git reset --hard "$pre_merge_head"
+'
+
+test_expect_success 'Fail clean merge with conflicting dirty worktree' '
+       echo xyzzy >> bar &&
+       git diff > expect &&
+       test_must_fail git merge --no-commit clean_branch &&
+       test ! -f .git/MERGE_HEAD &&
+       test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+       test -z "$(git diff --staged)" &&
+       git diff > actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'Fail conflicting merge with conflicting dirty worktree' '
+       test_must_fail git merge conflict_branch &&
+       test ! -f .git/MERGE_HEAD &&
+       test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+       test -z "$(git diff --staged)" &&
+       git diff > actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'Reset worktree changes' '
+       git reset --hard "$pre_merge_head"
+'
+
+test_expect_success 'Fail clean merge with matching dirty worktree' '
+       echo bart > bar &&
+       git diff > expect &&
+       test_must_fail git merge --no-commit clean_branch &&
+       test ! -f .git/MERGE_HEAD &&
+       test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+       test -z "$(git diff --staged)" &&
+       git diff > actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'Abort clean merge with matching dirty index' '
+       git add bar &&
+       git diff --staged > expect &&
+       git merge --no-commit clean_branch &&
+       test -f .git/MERGE_HEAD &&
+       ### When aborting the merge, git will discard all staged changes,
+       ### including those that were staged pre-merge. In other words,
+       ### --abort will LOSE any staged changes (the staged changes that
+       ### are lost must match the merge result, or the merge would not
+       ### have been allowed to start). Change expectations accordingly:
+       rm expect &&
+       touch expect &&
+       # Abort merge
+       git merge --abort &&
+       test ! -f .git/MERGE_HEAD &&
+       test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+       git diff --staged > actual &&
+       test_cmp expect actual &&
+       test -z "$(git diff)"
+'
+
+test_expect_success 'Reset worktree changes' '
+       git reset --hard "$pre_merge_head"
+'
+
+test_expect_success 'Fail conflicting merge with matching dirty worktree' '
+       echo barf > bar &&
+       git diff > expect &&
+       test_must_fail git merge conflict_branch &&
+       test ! -f .git/MERGE_HEAD &&
+       test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+       test -z "$(git diff --staged)" &&
+       git diff > actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'Abort conflicting merge with matching dirty index' '
+       git add bar &&
+       git diff --staged > expect &&
+       test_must_fail git merge conflict_branch &&
+       test -f .git/MERGE_HEAD &&
+       ### When aborting the merge, git will discard all staged changes,
+       ### including those that were staged pre-merge. In other words,
+       ### --abort will LOSE any staged changes (the staged changes that
+       ### are lost must match the merge result, or the merge would not
+       ### have been allowed to start). Change expectations accordingly:
+       rm expect &&
+       touch expect &&
+       # Abort merge
+       git merge --abort &&
+       test ! -f .git/MERGE_HEAD &&
+       test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+       git diff --staged > actual &&
+       test_cmp expect actual &&
+       test -z "$(git diff)"
+'
+
+test_expect_success 'Reset worktree changes' '
+       git reset --hard "$pre_merge_head"
+'
+
+test_expect_success 'Abort merge with pre- and post-merge worktree changes' '
+       # Pre-merge worktree changes
+       echo xyzzy > foo &&
+       echo barf > bar &&
+       git add bar &&
+       git diff > expect &&
+       git diff --staged > expect-staged &&
+       # Perform merge
+       test_must_fail git merge conflict_branch &&
+       test -f .git/MERGE_HEAD &&
+       # Post-merge worktree changes
+       echo yzxxz > foo &&
+       echo blech > baz &&
+       ### When aborting the merge, git will discard staged changes (bar)
+       ### and unmerged changes (baz). Other changes that are neither
+       ### staged nor marked as unmerged (foo), will be preserved. For
+       ### these changed, git cannot tell pre-merge changes apart from
+       ### post-merge changes, so the post-merge changes will be
+       ### preserved. Change expectations accordingly:
+       git diff -- foo > expect &&
+       rm expect-staged &&
+       touch expect-staged &&
+       # Abort merge
+       git merge --abort &&
+       test ! -f .git/MERGE_HEAD &&
+       test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+       git diff > actual &&
+       test_cmp expect actual &&
+       git diff --staged > actual-staged &&
+       test_cmp expect-staged actual-staged
+'
+
+test_expect_success 'Reset worktree changes' '
+       git reset --hard "$pre_merge_head"
+'
+
+test_expect_success 'Abort merge with pre- and post-merge index changes' '
+       # Pre-merge worktree changes
+       echo xyzzy > foo &&
+       echo barf > bar &&
+       git add bar &&
+       git diff > expect &&
+       git diff --staged > expect-staged &&
+       # Perform merge
+       test_must_fail git merge conflict_branch &&
+       test -f .git/MERGE_HEAD &&
+       # Post-merge worktree changes
+       echo yzxxz > foo &&
+       echo blech > baz &&
+       git add foo bar &&
+       ### When aborting the merge, git will discard all staged changes
+       ### (foo, bar and baz), and no changes will be preserved. Whether
+       ### the changes were staged pre- or post-merge does not matter
+       ### (except for not preventing starting the merge).
+       ### Change expectations accordingly:
+       rm expect expect-staged &&
+       touch expect &&
+       touch expect-staged &&
+       # Abort merge
+       git merge --abort &&
+       test ! -f .git/MERGE_HEAD &&
+       test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+       git diff > actual &&
+       test_cmp expect actual &&
+       git diff --staged > actual-staged &&
+       test_cmp expect-staged actual-staged
+'
+
+test_done
index 114d2bd7855f71ec1d48f2017438f897c5255d36..c994836c53224dd0a8302f7a6ab63b35f8b493e3 100755 (executable)
@@ -27,10 +27,10 @@ test_expect_success 'setup' '
 
 cat >expect <<\EOF
 error: The following untracked working tree files would be overwritten by merge:
-       two
-       three
-       four
        five
+       four
+       three
+       two
 Please move or remove them before you can merge.
 EOF
 
@@ -49,9 +49,9 @@ test_expect_success 'untracked files overwritten by merge (fast and non-fast for
 
 cat >expect <<\EOF
 error: Your local changes to the following files would be overwritten by merge:
-       two
-       three
        four
+       three
+       two
 Please, commit your changes or stash them before you can merge.
 error: The following untracked working tree files would be overwritten by merge:
        five
@@ -68,8 +68,8 @@ test_expect_success 'untracked files or local changes ovewritten by merge' '
 
 cat >expect <<\EOF
 error: Your local changes to the following files would be overwritten by checkout:
-       rep/two
        rep/one
+       rep/two
 Please, commit your changes or stash them before you can switch branches.
 EOF
 
@@ -89,8 +89,8 @@ test_expect_success 'cannot switch branches because of local changes' '
 
 cat >expect <<\EOF
 error: Your local changes to the following files would be overwritten by checkout:
-       rep/two
        rep/one
+       rep/two
 Please, commit your changes or stash them before you can switch branches.
 EOF
 
@@ -102,8 +102,8 @@ test_expect_success 'not uptodate file porcelain checkout error' '
 
 cat >expect <<\EOF
 error: Updating the following directories would lose untracked files in it:
-       rep2
        rep
+       rep2
 
 EOF
 
index 597cf0486fbe1034594d3eec821f5278d9648d43..d3a51e12698bed50d4eef1474e75dbfd0be0d034 100755 (executable)
@@ -6,4 +6,9 @@ test_description='git blame'
 PROG='git blame -c'
 . "$TEST_DIRECTORY"/annotate-tests.sh
 
+PROG='git blame -c -e'
+test_expect_success 'Blame --show-email works' '
+    check_count "<A@test.git>" 1 "<B@test.git>" 1 "<B1@test.git>" 1 "<B2@test.git>" 1 "<author@example.com>" 1 "<C@test.git>" 1 "<D@test.git>" 1
+'
+
 test_done
index 337ea59711ac7aca78a27afb92693f88e98aed9d..4594e1ae2f36b5a5582cdb1f2421fdfbd2f8c996 100755 (executable)
@@ -37,13 +37,11 @@ test_expect_success 'git svn gc runs' 'git svn gc'
 
 test_expect_success 'git svn index removed' '! test -f .git/svn/refs/remotes/git-svn/index'
 
-if perl -MCompress::Zlib -e 0 2>/dev/null
+if test -r .git/svn/refs/remotes/git-svn/unhandled.log.gz
 then
        test_expect_success 'git svn gc produces a valid gzip file' '
                 gunzip .git/svn/refs/remotes/git-svn/unhandled.log.gz
                '
-else
-       say "# Perl Compress::Zlib unavailable, skipping gunzip test"
 fi
 
 test_expect_success 'git svn gc does not change unhandled.log files' '
diff --git a/t/t9158-git-svn-mergeinfo.sh b/t/t9158-git-svn-mergeinfo.sh
new file mode 100644 (file)
index 0000000..3ab4390
--- /dev/null
@@ -0,0 +1,41 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Steven Walter
+#
+
+test_description='git svn mergeinfo propagation'
+
+. ./lib-git-svn.sh
+
+say 'define NO_SVN_TESTS to skip git svn tests'
+
+test_expect_success 'initialize source svn repo' '
+       svn_cmd mkdir -m x "$svnrepo"/trunk &&
+       svn_cmd co "$svnrepo"/trunk "$SVN_TREE" &&
+       (
+               cd "$SVN_TREE" &&
+               touch foo &&
+               svn_cmd add foo &&
+               svn_cmd commit -m "initial commit"
+       ) &&
+       rm -rf "$SVN_TREE"
+'
+
+test_expect_success 'clone svn repo' '
+       git svn init "$svnrepo"/trunk &&
+       git svn fetch
+'
+
+test_expect_success 'change svn:mergeinfo' '
+       touch bar &&
+       git add bar &&
+       git commit -m "bar" &&
+       git svn dcommit --mergeinfo="/branches/foo:1-10"
+'
+
+test_expect_success 'verify svn:mergeinfo' '
+       mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/trunk)
+       test "$mergeinfo" = "/branches/foo:1-10"
+'
+
+test_done
index 14d17691b1c48e2b3211812a16d6dda7e83ad31a..e8034d410fe50a770c3122c98967d80937bb1943 100755 (executable)
@@ -928,6 +928,114 @@ test_expect_success \
         git diff-tree -C --find-copies-harder -r N5^^ N5 >actual &&
         compare_diff_raw expect actual'
 
+test_expect_success \
+       'N: reject foo/ syntax' \
+       'subdir=$(git rev-parse refs/heads/branch^0:file2) &&
+        test_must_fail git fast-import <<-INPUT_END
+       commit refs/heads/N5B
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<COMMIT
+       copy with invalid syntax
+       COMMIT
+
+       from refs/heads/branch^0
+       M 040000 $subdir file3/
+       INPUT_END'
+
+test_expect_success \
+       'N: copy to root by id and modify' \
+       'echo "hello, world" >expect.foo &&
+        echo hello >expect.bar &&
+        git fast-import <<-SETUP_END &&
+       commit refs/heads/N7
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<COMMIT
+       hello, tree
+       COMMIT
+
+       deleteall
+       M 644 inline foo/bar
+       data <<EOF
+       hello
+       EOF
+       SETUP_END
+
+        tree=$(git rev-parse --verify N7:) &&
+        git fast-import <<-INPUT_END &&
+       commit refs/heads/N8
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<COMMIT
+       copy to root by id and modify
+       COMMIT
+
+       M 040000 $tree ""
+       M 644 inline foo/foo
+       data <<EOF
+       hello, world
+       EOF
+       INPUT_END
+        git show N8:foo/foo >actual.foo &&
+        git show N8:foo/bar >actual.bar &&
+        test_cmp expect.foo actual.foo &&
+        test_cmp expect.bar actual.bar'
+
+test_expect_success \
+       'N: extract subtree' \
+       'branch=$(git rev-parse --verify refs/heads/branch^{tree}) &&
+        cat >input <<-INPUT_END &&
+       commit refs/heads/N9
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<COMMIT
+       extract subtree branch:newdir
+       COMMIT
+
+       M 040000 $branch ""
+       C "newdir" ""
+       INPUT_END
+        git fast-import <input &&
+        git diff --exit-code branch:newdir N9'
+
+test_expect_success \
+       'N: modify subtree, extract it, and modify again' \
+       'echo hello >expect.baz &&
+        echo hello, world >expect.qux &&
+        git fast-import <<-SETUP_END &&
+       commit refs/heads/N10
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<COMMIT
+       hello, tree
+       COMMIT
+
+       deleteall
+       M 644 inline foo/bar/baz
+       data <<EOF
+       hello
+       EOF
+       SETUP_END
+
+        tree=$(git rev-parse --verify N10:) &&
+        git fast-import <<-INPUT_END &&
+       commit refs/heads/N11
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<COMMIT
+       copy to root by id and modify
+       COMMIT
+
+       M 040000 $tree ""
+       M 100644 inline foo/bar/qux
+       data <<EOF
+       hello, world
+       EOF
+       R "foo" ""
+       C "bar/qux" "bar/quux"
+       INPUT_END
+        git show N11:bar/baz >actual.baz &&
+        git show N11:bar/qux >actual.qux &&
+        git show N11:bar/quux >actual.quux &&
+        test_cmp expect.baz actual.baz &&
+        test_cmp expect.qux actual.qux &&
+        test_cmp expect.qux actual.quux'
+
 ###
 ### series O
 ###
index a5c99d85074a3e04d699afd9b0990a89afe33f54..7cf8cd8a2fed2191b31082c92a95598a3e06513d 100755 (executable)
@@ -255,13 +255,18 @@ EOF
 
 INPUT_END
 
+whitespace="    "
+
 cat >expect <<EXPECT_END
     fourth commit
     pre-prefix of note for fourth commit
+$whitespace
     prefix of note for fourth commit
+$whitespace
     third note for fourth commit
     third commit
     prefix of note for third commit
+$whitespace
     third note for third commit
     second commit
     third note for second commit
diff --git a/tag.c b/tag.c
index 28641cf85a0d32357269129f0e8b92bb9e21f399..f789744ccaf5d5fb6f63ba0911a869492affc162 100644 (file)
--- a/tag.c
+++ b/tag.c
@@ -4,6 +4,9 @@
 #include "tree.h"
 #include "blob.h"
 
+#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
+#define PGP_MESSAGE "-----BEGIN PGP MESSAGE-----"
+
 const char *tag_type = "tag";
 
 struct object *deref_tag(struct object *o, const char *warn, int warnlen)
@@ -133,3 +136,15 @@ int parse_tag(struct tag *item)
        free(data);
        return ret;
 }
+
+size_t parse_signature(const char *buf, unsigned long size)
+{
+       char *eol;
+       size_t len = 0;
+       while (len < size && prefixcmp(buf + len, PGP_SIGNATURE) &&
+                       prefixcmp(buf + len, PGP_MESSAGE)) {
+               eol = memchr(buf + len, '\n', size - len);
+               len += eol ? eol - (buf + len) + 1 : size - len;
+       }
+       return len;
+}
diff --git a/tag.h b/tag.h
index 47662724a6d7d07eeeacd5c8528d94d750ecf878..85223700396f5ddb00c046a1a9fdb63c92aae40e 100644 (file)
--- a/tag.h
+++ b/tag.h
@@ -16,5 +16,6 @@ extern struct tag *lookup_tag(const unsigned char *sha1);
 extern int parse_tag_buffer(struct tag *item, void *data, unsigned long size);
 extern int parse_tag(struct tag *item);
 extern struct object *deref_tag(struct object *, const char *, int);
+extern size_t parse_signature(const char *buf, unsigned long size);
 
 #endif /* TAG_H */
index 1727a03333b80f2a01ca029162a4b511480a86e8..6fb98c333c22a67512516bbf047c2cac270a66ae 100644 (file)
@@ -1,7 +1,11 @@
 #ifndef THREAD_COMPAT_H
 #define THREAD_COMPAT_H
 
+#ifndef NO_PTHREADS
+#include <pthread.h>
+
 extern int online_cpus(void);
 extern int init_recursive_mutex(pthread_mutex_t*);
 
+#endif
 #endif /* THREAD_COMPAT_H */
index acfc88e3f1f0e99863512bf2c6ea82f1283cc35d..4e4754c32bd53f28f9335c2a4529f5804f3086f2 100644 (file)
@@ -8,6 +8,7 @@
 #include "quote.h"
 #include "remote.h"
 #include "string-list.h"
+#include "thread-utils.h"
 
 static int debug;
 
@@ -862,3 +863,314 @@ int transport_helper_init(struct transport *transport, const char *name)
        transport->smart_options = &(data->transport_options);
        return 0;
 }
+
+/*
+ * Linux pipes can buffer 65536 bytes at once (and most platforms can
+ * buffer less), so attempt reads and writes with up to that size.
+ */
+#define BUFFERSIZE 65536
+/* This should be enough to hold debugging message. */
+#define PBUFFERSIZE 8192
+
+/* Print bidirectional transfer loop debug message. */
+static void transfer_debug(const char *fmt, ...)
+{
+       va_list args;
+       char msgbuf[PBUFFERSIZE];
+       static int debug_enabled = -1;
+
+       if (debug_enabled < 0)
+               debug_enabled = getenv("GIT_TRANSLOOP_DEBUG") ? 1 : 0;
+       if (!debug_enabled)
+               return;
+
+       va_start(args, fmt);
+       vsnprintf(msgbuf, PBUFFERSIZE, fmt, args);
+       va_end(args);
+       fprintf(stderr, "Transfer loop debugging: %s\n", msgbuf);
+}
+
+/* Stream state: More data may be coming in this direction. */
+#define SSTATE_TRANSFERING 0
+/*
+ * Stream state: No more data coming in this direction, flushing rest of
+ * data.
+ */
+#define SSTATE_FLUSHING 1
+/* Stream state: Transfer in this direction finished. */
+#define SSTATE_FINISHED 2
+
+#define STATE_NEEDS_READING(state) ((state) <= SSTATE_TRANSFERING)
+#define STATE_NEEDS_WRITING(state) ((state) <= SSTATE_FLUSHING)
+#define STATE_NEEDS_CLOSING(state) ((state) == SSTATE_FLUSHING)
+
+/* Unidirectional transfer. */
+struct unidirectional_transfer {
+       /* Source */
+       int src;
+       /* Destination */
+       int dest;
+       /* Is source socket? */
+       int src_is_sock;
+       /* Is destination socket? */
+       int dest_is_sock;
+       /* Transfer state (TRANSFERING/FLUSHING/FINISHED) */
+       int state;
+       /* Buffer. */
+       char buf[BUFFERSIZE];
+       /* Buffer used. */
+       size_t bufuse;
+       /* Name of source. */
+       const char *src_name;
+       /* Name of destination. */
+       const char *dest_name;
+};
+
+/* Closes the target (for writing) if transfer has finished. */
+static void udt_close_if_finished(struct unidirectional_transfer *t)
+{
+       if (STATE_NEEDS_CLOSING(t->state) && !t->bufuse) {
+               t->state = SSTATE_FINISHED;
+               if (t->dest_is_sock)
+                       shutdown(t->dest, SHUT_WR);
+               else
+                       close(t->dest);
+               transfer_debug("Closed %s.", t->dest_name);
+       }
+}
+
+/*
+ * Tries to read read data from source into buffer. If buffer is full,
+ * no data is read. Returns 0 on success, -1 on error.
+ */
+static int udt_do_read(struct unidirectional_transfer *t)
+{
+       ssize_t bytes;
+
+       if (t->bufuse == BUFFERSIZE)
+               return 0;       /* No space for more. */
+
+       transfer_debug("%s is readable", t->src_name);
+       bytes = read(t->src, t->buf + t->bufuse, BUFFERSIZE - t->bufuse);
+       if (bytes < 0 && errno != EWOULDBLOCK && errno != EAGAIN &&
+               errno != EINTR) {
+               error("read(%s) failed: %s", t->src_name, strerror(errno));
+               return -1;
+       } else if (bytes == 0) {
+               transfer_debug("%s EOF (with %i bytes in buffer)",
+                       t->src_name, t->bufuse);
+               t->state = SSTATE_FLUSHING;
+       } else if (bytes > 0) {
+               t->bufuse += bytes;
+               transfer_debug("Read %i bytes from %s (buffer now at %i)",
+                       (int)bytes, t->src_name, (int)t->bufuse);
+       }
+       return 0;
+}
+
+/* Tries to write data from buffer into destination. If buffer is empty,
+ * no data is written. Returns 0 on success, -1 on error.
+ */
+static int udt_do_write(struct unidirectional_transfer *t)
+{
+       size_t bytes;
+
+       if (t->bufuse == 0)
+               return 0;       /* Nothing to write. */
+
+       transfer_debug("%s is writable", t->dest_name);
+       bytes = write(t->dest, t->buf, t->bufuse);
+       if (bytes < 0 && errno != EWOULDBLOCK && errno != EAGAIN &&
+               errno != EINTR) {
+               error("write(%s) failed: %s", t->dest_name, strerror(errno));
+               return -1;
+       } else if (bytes > 0) {
+               t->bufuse -= bytes;
+               if (t->bufuse)
+                       memmove(t->buf, t->buf + bytes, t->bufuse);
+               transfer_debug("Wrote %i bytes to %s (buffer now at %i)",
+                       (int)bytes, t->dest_name, (int)t->bufuse);
+       }
+       return 0;
+}
+
+
+/* State of bidirectional transfer loop. */
+struct bidirectional_transfer_state {
+       /* Direction from program to git. */
+       struct unidirectional_transfer ptg;
+       /* Direction from git to program. */
+       struct unidirectional_transfer gtp;
+};
+
+static void *udt_copy_task_routine(void *udt)
+{
+       struct unidirectional_transfer *t = (struct unidirectional_transfer *)udt;
+       while (t->state != SSTATE_FINISHED) {
+               if (STATE_NEEDS_READING(t->state))
+                       if (udt_do_read(t))
+                               return NULL;
+               if (STATE_NEEDS_WRITING(t->state))
+                       if (udt_do_write(t))
+                               return NULL;
+               if (STATE_NEEDS_CLOSING(t->state))
+                       udt_close_if_finished(t);
+       }
+       return udt;     /* Just some non-NULL value. */
+}
+
+#ifndef NO_PTHREADS
+
+/*
+ * Join thread, with apporiate errors on failure. Name is name for the
+ * thread (for error messages). Returns 0 on success, 1 on failure.
+ */
+static int tloop_join(pthread_t thread, const char *name)
+{
+       int err;
+       void *tret;
+       err = pthread_join(thread, &tret);
+       if (!tret) {
+               error("%s thread failed", name);
+               return 1;
+       }
+       if (err) {
+               error("%s thread failed to join: %s", name, strerror(err));
+               return 1;
+       }
+       return 0;
+}
+
+/*
+ * Spawn the transfer tasks and then wait for them. Returns 0 on success,
+ * -1 on failure.
+ */
+static int tloop_spawnwait_tasks(struct bidirectional_transfer_state *s)
+{
+       pthread_t gtp_thread;
+       pthread_t ptg_thread;
+       int err;
+       int ret = 0;
+       err = pthread_create(&gtp_thread, NULL, udt_copy_task_routine,
+               &s->gtp);
+       if (err)
+               die("Can't start thread for copying data: %s", strerror(err));
+       err = pthread_create(&ptg_thread, NULL, udt_copy_task_routine,
+               &s->ptg);
+       if (err)
+               die("Can't start thread for copying data: %s", strerror(err));
+
+       ret |= tloop_join(gtp_thread, "Git to program copy");
+       ret |= tloop_join(ptg_thread, "Program to git copy");
+       return ret;
+}
+#else
+
+/* Close the source and target (for writing) for transfer. */
+static void udt_kill_transfer(struct unidirectional_transfer *t)
+{
+       t->state = SSTATE_FINISHED;
+       /*
+        * Socket read end left open isn't a disaster if nobody
+        * attempts to read from it (mingw compat headers do not
+        * have SHUT_RD)...
+        *
+        * We can't fully close the socket since otherwise gtp
+        * task would first close the socket it sends data to
+        * while closing the ptg file descriptors.
+        */
+       if (!t->src_is_sock)
+               close(t->src);
+       if (t->dest_is_sock)
+               shutdown(t->dest, SHUT_WR);
+       else
+               close(t->dest);
+}
+
+/*
+ * Join process, with apporiate errors on failure. Name is name for the
+ * process (for error messages). Returns 0 on success, 1 on failure.
+ */
+static int tloop_join(pid_t pid, const char *name)
+{
+       int tret;
+       if (waitpid(pid, &tret, 0) < 0) {
+               error("%s process failed to wait: %s", name, strerror(errno));
+               return 1;
+       }
+       if (!WIFEXITED(tret) || WEXITSTATUS(tret)) {
+               error("%s process failed", name);
+               return 1;
+       }
+       return 0;
+}
+
+/*
+ * Spawn the transfer tasks and then wait for them. Returns 0 on success,
+ * -1 on failure.
+ */
+static int tloop_spawnwait_tasks(struct bidirectional_transfer_state *s)
+{
+       pid_t pid1, pid2;
+       int ret = 0;
+
+       /* Fork thread #1: git to program. */
+       pid1 = fork();
+       if (pid1 < 0)
+               die_errno("Can't start thread for copying data");
+       else if (pid1 == 0) {
+               udt_kill_transfer(&s->ptg);
+               exit(udt_copy_task_routine(&s->gtp) ? 0 : 1);
+       }
+
+       /* Fork thread #2: program to git. */
+       pid2 = fork();
+       if (pid2 < 0)
+               die_errno("Can't start thread for copying data");
+       else if (pid2 == 0) {
+               udt_kill_transfer(&s->gtp);
+               exit(udt_copy_task_routine(&s->ptg) ? 0 : 1);
+       }
+
+       /*
+        * Close both streams in parent as to not interfere with
+        * end of file detection and wait for both tasks to finish.
+        */
+       udt_kill_transfer(&s->gtp);
+       udt_kill_transfer(&s->ptg);
+       ret |= tloop_join(pid1, "Git to program copy");
+       ret |= tloop_join(pid2, "Program to git copy");
+       return ret;
+}
+#endif
+
+/*
+ * Copies data from stdin to output and from input to stdout simultaneously.
+ * Additionally filtering through given filter. If filter is NULL, uses
+ * identity filter.
+ */
+int bidirectional_transfer_loop(int input, int output)
+{
+       struct bidirectional_transfer_state state;
+
+       /* Fill the state fields. */
+       state.ptg.src = input;
+       state.ptg.dest = 1;
+       state.ptg.src_is_sock = (input == output);
+       state.ptg.dest_is_sock = 0;
+       state.ptg.state = SSTATE_TRANSFERING;
+       state.ptg.bufuse = 0;
+       state.ptg.src_name = "remote input";
+       state.ptg.dest_name = "stdout";
+
+       state.gtp.src = 0;
+       state.gtp.dest = output;
+       state.gtp.src_is_sock = 0;
+       state.gtp.dest_is_sock = (input == output);
+       state.gtp.state = SSTATE_TRANSFERING;
+       state.gtp.bufuse = 0;
+       state.gtp.src_name = "stdin";
+       state.gtp.dest_name = "remote output";
+
+       return tloop_spawnwait_tasks(&state);
+}
index c59d97388e6fdb1dccd4dca0d41dccc3d129f8fb..e803c0e7baf3785fac35fca64072e05e4f668a26 100644 (file)
@@ -154,6 +154,7 @@ int transport_connect(struct transport *transport, const char *name,
 
 /* Transport methods defined outside transport.c */
 int transport_helper_init(struct transport *transport, const char *name);
+int bidirectional_transfer_loop(int input, int output);
 
 /* common methods used by transport.c and builtin-send-pack.c */
 void transport_verify_remote_names(int nr_heads, const char **heads);
index 803445aa7be140c3707bcebc72aaf6fc6af45e4b..d5a453079a02ff8b7a63a3453d07935e0807c005 100644 (file)
@@ -53,6 +53,7 @@ const char *unpack_plumbing_errors[NB_UNPACK_TREES_ERROR_TYPES] = {
 void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
                                  const char *cmd)
 {
+       int i;
        const char **msgs = opts->msgs;
        const char *msg;
        char *tmp;
@@ -96,6 +97,9 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
                "The following Working tree files would be removed by sparse checkout update:\n%s";
 
        opts->show_all_errors = 1;
+       /* rejected paths may not have a static buffer */
+       for (i = 0; i < ARRAY_SIZE(opts->unpack_rejects); i++)
+               opts->unpack_rejects[i].strdup_strings = 1;
 }
 
 static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
@@ -124,7 +128,6 @@ static int add_rejected_path(struct unpack_trees_options *o,
                             enum unpack_trees_error_types e,
                             const char *path)
 {
-       struct rejected_paths_list *newentry;
        if (!o->show_all_errors)
                return error(ERRORMSG(o, e), path);
 
@@ -132,45 +135,28 @@ static int add_rejected_path(struct unpack_trees_options *o,
         * Otherwise, insert in a list for future display by
         * display_error_msgs()
         */
-       newentry = xmalloc(sizeof(struct rejected_paths_list));
-       newentry->path = (char *)path;
-       newentry->next = o->unpack_rejects[e];
-       o->unpack_rejects[e] = newentry;
+       string_list_append(&o->unpack_rejects[e], path);
        return -1;
 }
 
-/*
- * free all the structures allocated for the error <e>
- */
-static void free_rejected_paths(struct unpack_trees_options *o,
-                               enum unpack_trees_error_types e)
-{
-       while (o->unpack_rejects[e]) {
-               struct rejected_paths_list *del = o->unpack_rejects[e];
-               o->unpack_rejects[e] = o->unpack_rejects[e]->next;
-               free(del);
-       }
-       free(o->unpack_rejects[e]);
-}
-
 /*
  * display all the error messages stored in a nice way
  */
 static void display_error_msgs(struct unpack_trees_options *o)
 {
-       int e;
+       int e, i;
        int something_displayed = 0;
        for (e = 0; e < NB_UNPACK_TREES_ERROR_TYPES; e++) {
-               if (o->unpack_rejects[e]) {
-                       struct rejected_paths_list *rp;
+               struct string_list *rejects = &o->unpack_rejects[e];
+               if (rejects->nr > 0) {
                        struct strbuf path = STRBUF_INIT;
                        something_displayed = 1;
-                       for (rp = o->unpack_rejects[e]; rp; rp = rp->next)
-                               strbuf_addf(&path, "\t%s\n", rp->path);
+                       for (i = 0; i < rejects->nr; i++)
+                               strbuf_addf(&path, "\t%s\n", rejects->items[i].string);
                        error(ERRORMSG(o, e), path.buf);
                        strbuf_release(&path);
-                       free_rejected_paths(o, e);
                }
+               string_list_clear(rejects, 0);
        }
        if (something_displayed)
                printf("Aborting\n");
@@ -182,7 +168,7 @@ static void display_error_msgs(struct unpack_trees_options *o)
  */
 static void unlink_entry(struct cache_entry *ce)
 {
-       if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce)))
+       if (!check_leading_path(ce->name, ce_namelen(ce)))
                return;
        if (remove_or_warn(ce->ce_mode, ce->name))
                return;
@@ -1127,14 +1113,65 @@ static int verify_clean_subdirectory(struct cache_entry *ce,
  * See if we can find a case-insensitive match in the index that also
  * matches the stat information, and assume it's that other file!
  */
-static int icase_exists(struct unpack_trees_options *o, struct cache_entry *dst, struct stat *st)
+static int icase_exists(struct unpack_trees_options *o, const char *name, int len, struct stat *st)
 {
        struct cache_entry *src;
 
-       src = index_name_exists(o->src_index, dst->name, ce_namelen(dst), 1);
+       src = index_name_exists(o->src_index, name, len, 1);
        return src && !ie_match_stat(o->src_index, src, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
 }
 
+static int check_ok_to_remove(const char *name, int len, int dtype,
+                             struct cache_entry *ce, struct stat *st,
+                             enum unpack_trees_error_types error_type,
+                             struct unpack_trees_options *o)
+{
+       struct cache_entry *result;
+
+       /*
+        * It may be that the 'lstat()' succeeded even though
+        * target 'ce' was absent, because there is an old
+        * entry that is different only in case..
+        *
+        * Ignore that lstat() if it matches.
+        */
+       if (ignore_case && icase_exists(o, name, len, st))
+               return 0;
+
+       if (o->dir && excluded(o->dir, name, &dtype))
+               /*
+                * ce->name is explicitly excluded, so it is Ok to
+                * overwrite it.
+                */
+               return 0;
+       if (S_ISDIR(st->st_mode)) {
+               /*
+                * We are checking out path "foo" and
+                * found "foo/." in the working tree.
+                * This is tricky -- if we have modified
+                * files that are in "foo/" we would lose
+                * them.
+                */
+               if (verify_clean_subdirectory(ce, error_type, o) < 0)
+                       return -1;
+               return 0;
+       }
+
+       /*
+        * The previous round may already have decided to
+        * delete this path, which is in a subdirectory that
+        * is being replaced with a blob.
+        */
+       result = index_name_exists(&o->result, name, len, 0);
+       if (result) {
+               if (result->ce_flags & CE_REMOVE)
+                       return 0;
+       }
+
+       return o->gently ? -1 :
+               add_rejected_path(o, error_type, name);
+}
+
 /*
  * We do not want to remove or overwrite a working tree file that
  * is not tracked, unless it is ignored.
@@ -1143,63 +1180,31 @@ static int verify_absent_1(struct cache_entry *ce,
                                 enum unpack_trees_error_types error_type,
                                 struct unpack_trees_options *o)
 {
+       int len;
        struct stat st;
 
        if (o->index_only || o->reset || !o->update)
                return 0;
 
-       if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce)))
+       len = check_leading_path(ce->name, ce_namelen(ce));
+       if (!len)
                return 0;
+       else if (len > 0) {
+               char path[PATH_MAX + 1];
+               memcpy(path, ce->name, len);
+               path[len] = 0;
+               lstat(path, &st);
+
+               return check_ok_to_remove(path, len, DT_UNKNOWN, NULL, &st,
+                               error_type, o);
+       } else if (!lstat(ce->name, &st))
+               return check_ok_to_remove(ce->name, ce_namelen(ce),
+                               ce_to_dtype(ce), ce, &st,
+                               error_type, o);
 
-       if (!lstat(ce->name, &st)) {
-               int dtype = ce_to_dtype(ce);
-               struct cache_entry *result;
-
-               /*
-                * It may be that the 'lstat()' succeeded even though
-                * target 'ce' was absent, because there is an old
-                * entry that is different only in case..
-                *
-                * Ignore that lstat() if it matches.
-                */
-               if (ignore_case && icase_exists(o, ce, &st))
-                       return 0;
-
-               if (o->dir && excluded(o->dir, ce->name, &dtype))
-                       /*
-                        * ce->name is explicitly excluded, so it is Ok to
-                        * overwrite it.
-                        */
-                       return 0;
-               if (S_ISDIR(st.st_mode)) {
-                       /*
-                        * We are checking out path "foo" and
-                        * found "foo/." in the working tree.
-                        * This is tricky -- if we have modified
-                        * files that are in "foo/" we would lose
-                        * them.
-                        */
-                       if (verify_clean_subdirectory(ce, error_type, o) < 0)
-                               return -1;
-                       return 0;
-               }
-
-               /*
-                * The previous round may already have decided to
-                * delete this path, which is in a subdirectory that
-                * is being replaced with a blob.
-                */
-               result = index_name_exists(&o->result, ce->name, ce_namelen(ce), 0);
-               if (result) {
-                       if (result->ce_flags & CE_REMOVE)
-                               return 0;
-               }
-
-               return o->gently ? -1 :
-                       add_rejected_path(o, error_type, ce->name);
-       }
        return 0;
 }
+
 static int verify_absent(struct cache_entry *ce,
                         enum unpack_trees_error_types error_type,
                         struct unpack_trees_options *o)
index 7c0187d11adaa5f0b9a8642070d8dfc2db41dfdf..cd11a08365ab3e27b1321b3df87bcab6b9278f90 100644 (file)
@@ -1,6 +1,8 @@
 #ifndef UNPACK_TREES_H
 #define UNPACK_TREES_H
 
+#include "string-list.h"
+
 #define MAX_UNPACK_TREES 8
 
 struct unpack_trees_options;
@@ -29,11 +31,6 @@ enum unpack_trees_error_types {
 void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
                                  const char *cmd);
 
-struct rejected_paths_list {
-       char *path;
-       struct rejected_paths_list *next;
-};
-
 struct unpack_trees_options {
        unsigned int reset,
                     merge,
@@ -59,7 +56,7 @@ struct unpack_trees_options {
         * Store error messages in an array, each case
         * corresponding to a error message type
         */
-       struct rejected_paths_list *unpack_rejects[NB_UNPACK_TREES_ERROR_TYPES];
+       struct string_list unpack_rejects[NB_UNPACK_TREES_ERROR_TYPES];
 
        int head_idx;
        int merge_size;
index fd8ead33ed72c37e690ee1fc5b8568f629c95145..4c1639f1536d259a2b64d574005ac973d042b273 100644 (file)
--- a/wrapper.c
+++ b/wrapper.c
@@ -3,12 +3,11 @@
  */
 #include "cache.h"
 
-static void try_to_free_builtin(size_t size)
+static void do_nothing(size_t size)
 {
-       release_pack_memory(size, -1);
 }
 
-static void (*try_to_free_routine)(size_t size) = try_to_free_builtin;
+static void (*try_to_free_routine)(size_t size) = do_nothing;
 
 try_to_free_t set_try_to_free_routine(try_to_free_t routine)
 {
@@ -108,21 +107,6 @@ void *xcalloc(size_t nmemb, size_t size)
        return ret;
 }
 
-void *xmmap(void *start, size_t length,
-       int prot, int flags, int fd, off_t offset)
-{
-       void *ret = mmap(start, length, prot, flags, fd, offset);
-       if (ret == MAP_FAILED) {
-               if (!length)
-                       return NULL;
-               release_pack_memory(length, fd);
-               ret = mmap(start, length, prot, flags, fd, offset);
-               if (ret == MAP_FAILED)
-                       die_errno("Out of memory? mmap failed");
-       }
-       return ret;
-}
-
 /*
  * xread() is the same a read(), but it automatically restarts read()
  * operations with a recoverable error (EAGAIN and EINTR). xread()
@@ -219,111 +203,127 @@ int xmkstemp(char *template)
        return fd;
 }
 
-int xmkstemp_mode(char *template, int mode)
+/* git_mkstemp() - create tmp file honoring TMPDIR variable */
+int git_mkstemp(char *path, size_t len, const char *template)
 {
-       int fd;
-
-       fd = git_mkstemp_mode(template, mode);
-       if (fd < 0)
-               die_errno("Unable to create temporary file");
-       return fd;
+       const char *tmp;
+       size_t n;
+
+       tmp = getenv("TMPDIR");
+       if (!tmp)
+               tmp = "/tmp";
+       n = snprintf(path, len, "%s/%s", tmp, template);
+       if (len <= n) {
+               errno = ENAMETOOLONG;
+               return -1;
+       }
+       return mkstemp(path);
 }
 
-/*
- * zlib wrappers to make sure we don't silently miss errors
- * at init time.
- */
-void git_inflate_init(z_streamp strm)
+/* git_mkstemps() - create tmp file with suffix honoring TMPDIR variable. */
+int git_mkstemps(char *path, size_t len, const char *template, int suffix_len)
 {
-       const char *err;
-
-       switch (inflateInit(strm)) {
-       case Z_OK:
-               return;
-
-       case Z_MEM_ERROR:
-               err = "out of memory";
-               break;
-       case Z_VERSION_ERROR:
-               err = "wrong version";
-               break;
-       default:
-               err = "error";
+       const char *tmp;
+       size_t n;
+
+       tmp = getenv("TMPDIR");
+       if (!tmp)
+               tmp = "/tmp";
+       n = snprintf(path, len, "%s/%s", tmp, template);
+       if (len <= n) {
+               errno = ENAMETOOLONG;
+               return -1;
        }
-       die("inflateInit: %s (%s)", err, strm->msg ? strm->msg : "no message");
+       return mkstemps(path, suffix_len);
 }
 
-void git_inflate_end(z_streamp strm)
+/* Adapted from libiberty's mkstemp.c. */
+
+#undef TMP_MAX
+#define TMP_MAX 16384
+
+int git_mkstemps_mode(char *pattern, int suffix_len, int mode)
 {
-       if (inflateEnd(strm) != Z_OK)
-               error("inflateEnd: %s", strm->msg ? strm->msg : "failed");
+       static const char letters[] =
+               "abcdefghijklmnopqrstuvwxyz"
+               "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+               "0123456789";
+       static const int num_letters = 62;
+       uint64_t value;
+       struct timeval tv;
+       char *template;
+       size_t len;
+       int fd, count;
+
+       len = strlen(pattern);
+
+       if (len < 6 + suffix_len) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (strncmp(&pattern[len - 6 - suffix_len], "XXXXXX", 6)) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       /*
+        * Replace pattern's XXXXXX characters with randomness.
+        * Try TMP_MAX different filenames.
+        */
+       gettimeofday(&tv, NULL);
+       value = ((size_t)(tv.tv_usec << 16)) ^ tv.tv_sec ^ getpid();
+       template = &pattern[len - 6 - suffix_len];
+       for (count = 0; count < TMP_MAX; ++count) {
+               uint64_t v = value;
+               /* Fill in the random bits. */
+               template[0] = letters[v % num_letters]; v /= num_letters;
+               template[1] = letters[v % num_letters]; v /= num_letters;
+               template[2] = letters[v % num_letters]; v /= num_letters;
+               template[3] = letters[v % num_letters]; v /= num_letters;
+               template[4] = letters[v % num_letters]; v /= num_letters;
+               template[5] = letters[v % num_letters]; v /= num_letters;
+
+               fd = open(pattern, O_CREAT | O_EXCL | O_RDWR, mode);
+               if (fd > 0)
+                       return fd;
+               /*
+                * Fatal error (EPERM, ENOSPC etc).
+                * It doesn't make sense to loop.
+                */
+               if (errno != EEXIST)
+                       break;
+               /*
+                * This is a random value.  It is only necessary that
+                * the next TMP_MAX values generated by adding 7777 to
+                * VALUE are different with (module 2^32).
+                */
+               value += 7777;
+       }
+       /* We return the null string if we can't find a unique file name.  */
+       pattern[0] = '\0';
+       return -1;
 }
 
-int git_inflate(z_streamp strm, int flush)
+int git_mkstemp_mode(char *pattern, int mode)
 {
-       int ret = inflate(strm, flush);
-       const char *err;
-
-       switch (ret) {
-       /* Out of memory is fatal. */
-       case Z_MEM_ERROR:
-               die("inflate: out of memory");
-
-       /* Data corruption errors: we may want to recover from them (fsck) */
-       case Z_NEED_DICT:
-               err = "needs dictionary"; break;
-       case Z_DATA_ERROR:
-               err = "data stream error"; break;
-       case Z_STREAM_ERROR:
-               err = "stream consistency error"; break;
-       default:
-               err = "unknown error"; break;
-
-       /* Z_BUF_ERROR: normal, needs more space in the output buffer */
-       case Z_BUF_ERROR:
-       case Z_OK:
-       case Z_STREAM_END:
-               return ret;
-       }
-       error("inflate: %s (%s)", err, strm->msg ? strm->msg : "no message");
-       return ret;
+       /* mkstemp is just mkstemps with no suffix */
+       return git_mkstemps_mode(pattern, 0, mode);
 }
 
-int odb_mkstemp(char *template, size_t limit, const char *pattern)
+int gitmkstemps(char *pattern, int suffix_len)
 {
-       int fd;
-       /*
-        * we let the umask do its job, don't try to be more
-        * restrictive except to remove write permission.
-        */
-       int mode = 0444;
-       snprintf(template, limit, "%s/%s",
-                get_object_directory(), pattern);
-       fd = git_mkstemp_mode(template, mode);
-       if (0 <= fd)
-               return fd;
-
-       /* slow path */
-       /* some mkstemp implementations erase template on failure */
-       snprintf(template, limit, "%s/%s",
-                get_object_directory(), pattern);
-       safe_create_leading_directories(template);
-       return xmkstemp_mode(template, mode);
+       return git_mkstemps_mode(pattern, suffix_len, 0600);
 }
 
-int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1)
+int xmkstemp_mode(char *template, int mode)
 {
        int fd;
 
-       snprintf(name, namesz, "%s/pack/pack-%s.keep",
-                get_object_directory(), sha1_to_hex(sha1));
-       fd = open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
-       if (0 <= fd)
-               return fd;
-
-       /* slow path */
-       safe_create_leading_directories(name);
-       return open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
+       fd = git_mkstemp_mode(template, mode);
+       if (fd < 0)
+               die_errno("Unable to create temporary file");
+       return fd;
 }
 
 static int warn_if_unremovable(const char *op, const char *file, int rc)
index d9f3d9fe9369ceaac07f5b405e25cf94d824e102..06ae161c6707e893ab723812b268016a05b950fe 100644 (file)
@@ -744,10 +744,20 @@ static void wt_shortstatus_status(int null_termination, struct string_list_item
                const char *one;
                if (d->head_path) {
                        one = quote_path(d->head_path, -1, &onebuf, s->prefix);
+                       if (*one != '"' && strchr(one, ' ') != NULL) {
+                               putchar('"');
+                               strbuf_addch(&onebuf, '"');
+                               one = onebuf.buf;
+                       }
                        printf("%s -> ", one);
                        strbuf_release(&onebuf);
                }
                one = quote_path(it->string, -1, &onebuf, s->prefix);
+               if (*one != '"' && strchr(one, ' ') != NULL) {
+                       putchar('"');
+                       strbuf_addch(&onebuf, '"');
+                       one = onebuf.buf;
+               }
                printf("%s\n", one);
                strbuf_release(&onebuf);
        }
diff --git a/zlib.c b/zlib.c
new file mode 100644 (file)
index 0000000..c4d58da
--- /dev/null
+++ b/zlib.c
@@ -0,0 +1,61 @@
+/*
+ * zlib wrappers to make sure we don't silently miss errors
+ * at init time.
+ */
+#include "cache.h"
+
+void git_inflate_init(z_streamp strm)
+{
+       const char *err;
+
+       switch (inflateInit(strm)) {
+       case Z_OK:
+               return;
+
+       case Z_MEM_ERROR:
+               err = "out of memory";
+               break;
+       case Z_VERSION_ERROR:
+               err = "wrong version";
+               break;
+       default:
+               err = "error";
+       }
+       die("inflateInit: %s (%s)", err, strm->msg ? strm->msg : "no message");
+}
+
+void git_inflate_end(z_streamp strm)
+{
+       if (inflateEnd(strm) != Z_OK)
+               error("inflateEnd: %s", strm->msg ? strm->msg : "failed");
+}
+
+int git_inflate(z_streamp strm, int flush)
+{
+       int ret = inflate(strm, flush);
+       const char *err;
+
+       switch (ret) {
+       /* Out of memory is fatal. */
+       case Z_MEM_ERROR:
+               die("inflate: out of memory");
+
+       /* Data corruption errors: we may want to recover from them (fsck) */
+       case Z_NEED_DICT:
+               err = "needs dictionary"; break;
+       case Z_DATA_ERROR:
+               err = "data stream error"; break;
+       case Z_STREAM_ERROR:
+               err = "stream consistency error"; break;
+       default:
+               err = "unknown error"; break;
+
+       /* Z_BUF_ERROR: normal, needs more space in the output buffer */
+       case Z_BUF_ERROR:
+       case Z_OK:
+       case Z_STREAM_END:
+               return ret;
+       }
+       error("inflate: %s (%s)", err, strm->msg ? strm->msg : "no message");
+       return ret;
+}