Code

Merge branch 'kb/diff-C-M-synonym'
authorJunio C Hamano <gitster@pobox.com>
Thu, 16 Dec 2010 20:58:59 +0000 (12:58 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 16 Dec 2010 20:58:59 +0000 (12:58 -0800)
* kb/diff-C-M-synonym:
  diff: use "find" instead of "detect" as prefix for long forms of -M and -C
  diff: add --detect-copies-harder as a synonym for --find-copies-harder

220 files changed:
.gitignore
Documentation/CodingGuidelines
Documentation/Makefile
Documentation/RelNotes/1.6.4.5.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.5.9.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.6.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.0.8.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.0.9.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.1.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.1.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.2.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.2.5.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/fetch-options.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-fast-import.txt
Documentation/git-merge.txt
Documentation/git-notes.txt
Documentation/git-pull.txt
Documentation/git-rebase.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-web--browse.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/gitignore.txt
Documentation/gitmodules.txt
Documentation/revisions.txt
INSTALL
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/describe.c
builtin/diff.c
builtin/fetch.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/shortlog.c
builtin/tag.c
builtin/update-index.c
builtin/verify-tag.c
cache.h
color.c
color.h
commit.c
commit.h
compat/mingw.c
compat/mingw.h
compat/msvc.c
compat/vcbuild/include/dirent.h [deleted file]
compat/vcbuild/include/unistd.h
compat/win32/dirent.c [new file with mode: 0644]
compat/win32/dirent.h [new file with mode: 0644]
compat/win32/sys/poll.c
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
entry.c
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-submodule.sh
git-svn.perl
git-web--browse.sh
git.c
gitk-git/gitk
gitk-git/po/pt_br.po [new file with mode: 0644]
gitk-git/po/sv.po
gitweb/README
gitweb/gitweb.perl
gitweb/static/gitweb.css
help.c
http-backend.c
http-fetch.c
http-push.c
http.c
http.h
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
parse-options.c
parse-options.h
path.c
pretty.c
read-cache.c
reflog-walk.c
revision.c
setup.c
sha1_file.c
sha1_name.c
strbuf.c
submodule.c
submodule.h
symlinks.c
t/Makefile
t/annotate-tests.sh
t/lib-httpd.sh
t/lib-httpd/apache.conf
t/lib-httpd/passwd [new file with mode: 0644]
t/t1412-reflog-loop.sh [new file with mode: 0755]
t/t1506-rev-parse-diagnosis.sh
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/t3407-rebase-abort.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/t4014-format-patch.sh
t/t4015-diff-whitespace.sh
t/t4019-diff-wserror.sh
t/t4120-apply-popt.sh
t/t4124-apply-ws-rule.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/t5520-pull.sh
t/t5526-fetch-submodules.sh [new file with mode: 0755]
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/t7400-submodule-basic.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-co-error-msgs.sh
t/t7611-merge-abort.sh [new file with mode: 0755]
t/t8002-blame.sh
t/t8003-blame-corner-cases.sh [new file with mode: 0755]
t/t8003-blame.sh [deleted file]
t/t8004-blame-with-conflicts.sh [new file with mode: 0755]
t/t8004-blame.sh [deleted file]
t/t9010-svn-fe.sh
t/t9119-git-svn-info.sh
t/t9143-git-svn-gc.sh
t/t9158-git-svn-mergeinfo.sh [new file with mode: 0755]
t/t9300-fast-import.sh
t/t9301-fast-import-notes.sh
tag.c
tag.h
test-treap.c
thread-utils.h
transport-helper.c
transport.h
unpack-trees.c
unpack-trees.h
url.c
url.h
vcs-svn/repo_tree.c
vcs-svn/trp.h
vcs-svn/trp.txt
wrapper.c
ws.c
wt-status.c
wt-status.h
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.
index e117bc4315f6e288e89cf83fb80e876bee7a7dc2..36989b7f6541cb8444385b64d2c955ae2a1f6d1b 100644 (file)
@@ -63,35 +63,28 @@ endif
 
 #
 # For asciidoc ...
-#      -7.1.2, no extra settings are needed.
-#      8.0-,   set ASCIIDOC8.
+#      -7.1.2, set ASCIIDOC7
+#      8.0-,   no extra settings are needed
 #
 
 #
 # For docbook-xsl ...
-#      -1.68.1,        set ASCIIDOC_NO_ROFF? (based on changelog from 1.73.0)
-#      1.69.0,         no extra settings are needed?
+#      -1.68.1,        no extra settings are needed?
+#      1.69.0,         set ASCIIDOC_ROFF?
 #      1.69.1-1.71.0,  set DOCBOOK_SUPPRESS_SP?
-#      1.71.1,         no extra settings are needed?
+#      1.71.1,         set ASCIIDOC_ROFF?
 #      1.72.0,         set DOCBOOK_XSL_172.
-#      1.73.0-,        set ASCIIDOC_NO_ROFF
+#      1.73.0-,        no extra settings are needed
 #
 
-#
-# If you had been using DOCBOOK_XSL_172 in an attempt to get rid
-# of 'the ".ft C" problem' in your generated manpages, and you
-# instead ended up with weird characters around callouts, try
-# using ASCIIDOC_NO_ROFF instead (it works fine with ASCIIDOC8).
-#
-
-ifdef ASCIIDOC8
+ifndef ASCIIDOC7
 ASCIIDOC_EXTRA += -a asciidoc7compatible -a no-inline-literal
 endif
 ifdef DOCBOOK_XSL_172
 ASCIIDOC_EXTRA += -a git-asciidoc-no-roff
 MANPAGE_XSL = manpage-1.72.xsl
 else
-       ifdef ASCIIDOC_NO_ROFF
+       ifndef ASCIIDOC_ROFF
        # docbook-xsl after 1.72 needs the regular XSL, but will not
        # pass-thru raw roff codes from asciidoc.conf, so turn them off.
        ASCIIDOC_EXTRA += -a git-asciidoc-no-roff
diff --git a/Documentation/RelNotes/1.6.4.5.txt b/Documentation/RelNotes/1.6.4.5.txt
new file mode 100644 (file)
index 0000000..eb6307d
--- /dev/null
@@ -0,0 +1,20 @@
+Git v1.6.4.5 Release Notes
+==========================
+
+Fixes since v1.6.4.4
+--------------------
+
+ * Simplified base85 implementation.
+
+ * An overlong line after ".gitdir: " in a git file caused out of bounds
+   access to an array on the stack.
+
+ * "git count-objects" did not handle packs larger than 4G.
+
+ * "git rev-parse --parseopt --stop-at-non-option" did not stop at non option
+   when --keep-dashdash was in effect.
+
+ * "gitweb" can sometimes be tricked into parrotting a filename argument
+   given in a request without properly quoting.
+
+Other minor fixes and documentation updates are included.
diff --git a/Documentation/RelNotes/1.6.5.9.txt b/Documentation/RelNotes/1.6.5.9.txt
new file mode 100644 (file)
index 0000000..bb469dd
--- /dev/null
@@ -0,0 +1,18 @@
+Git v1.6.5.9 Release Notes
+==========================
+
+Fixes since v1.6.5.8
+--------------------
+
+ * An overlong line after ".gitdir: " in a git file caused out of bounds
+   access to an array on the stack.
+
+ * "git blame -L $start,$end" segfaulted when too large $start was given.
+
+ * "git rev-parse --parseopt --stop-at-non-option" did not stop at non option
+   when --keep-dashdash was in effect.
+
+ * "gitweb" can sometimes be tricked into parrotting a filename argument
+   given in a request without properly quoting.
+
+Other minor fixes and documentation updates are included.
diff --git a/Documentation/RelNotes/1.6.6.3.txt b/Documentation/RelNotes/1.6.6.3.txt
new file mode 100644 (file)
index 0000000..11483ac
--- /dev/null
@@ -0,0 +1,23 @@
+Git v1.6.6.3 Release Notes
+==========================
+
+Fixes since v1.6.6.2
+--------------------
+
+ * An overlong line after ".gitdir: " in a git file caused out of bounds
+   access to an array on the stack.
+
+ * "git bisect $path" did not correctly diagnose an error when given a
+   non-existent path.
+
+ * "git blame -L $start,$end" segfaulted when too large $start was given.
+
+ * "git imap-send" did not write draft box with CRLF line endings per RFC.
+
+ * "git rev-parse --parseopt --stop-at-non-option" did not stop at non option
+   when --keep-dashdash was in effect.
+
+ * "gitweb" can sometimes be tricked into parrotting a filename argument
+   given in a request without properly quoting.
+
+Other minor fixes and documentation updates are included.
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.0.9.txt b/Documentation/RelNotes/1.7.0.9.txt
new file mode 100644 (file)
index 0000000..bfb3166
--- /dev/null
@@ -0,0 +1,8 @@
+Git v1.7.0.9 Release Notes
+==========================
+
+Fixes since v1.7.0.8
+--------------------
+
+ * "gitweb" can sometimes be tricked into parrotting a filename argument
+   given in a request without properly quoting.
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.1.4.txt b/Documentation/RelNotes/1.7.1.4.txt
new file mode 100644 (file)
index 0000000..7c734b4
--- /dev/null
@@ -0,0 +1,8 @@
+Git v1.7.1.4 Release Notes
+==========================
+
+Fixes since v1.7.1.3
+--------------------
+
+ * "gitweb" can sometimes be tricked into parrotting a filename argument
+   given in a request without properly quoting.
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.2.5.txt b/Documentation/RelNotes/1.7.2.5.txt
new file mode 100644 (file)
index 0000000..bf976c4
--- /dev/null
@@ -0,0 +1,8 @@
+Git v1.7.2.5 Release Notes
+==========================
+
+Fixes since v1.7.2.4
+--------------------
+
+ * "gitweb" can sometimes be tricked into parrotting a filename argument
+   given in a request without properly quoting.
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..e57f7c1
--- /dev/null
@@ -0,0 +1,45 @@
+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 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 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.
+
+ * "git imap-send" failed to build under NO_OPENSSL.
+
+ * Setting log.decorate configuration variable to "0" or "1" to mean
+   "false" or "true" did not work.
+
+ * "git push" over dumb HTTP protocol did not work against WebDAV
+   servers that did not terminate a collection name with a slash.
+
+ * "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.
+
+ * "gitweb" can sometimes be tricked into parrotting a filename argument
+   given in a request without properly quoting.
+
+Other minor fixes and documentation updates are also included.
index 9f946e218dd67e4943bd8a7164467cad94bab40a..c1ed23a0c6decbfe4b4ab141b00d24d2f399b872 100644 (file)
@@ -4,6 +4,10 @@ Git v1.7.4 Release Notes (draft)
 Updates since v1.7.3
 --------------------
 
+ * The documentation Makefile now assumes by default asciidoc 8 and
+   docbook-xsl >= 1.73. If you have older versions, you can set
+   ASCIIDOC7 and ASCIIDOC_ROFF, respectively.
+
  * The option parsers of various commands that create new branch (or
    rename existing ones to a new name) were too loose and users were
    allowed to call a branch with a name that begins with a dash by
@@ -15,9 +19,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 +57,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 +96,23 @@ 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 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..bf9479e4eb49c971b9502d072dd9be18c838ab21 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,
@@ -513,6 +522,9 @@ core.whitespace::
   part of the line terminator, i.e. with it, `trailing-space`
   does not trigger if the character before such a carriage-return
   is not a whitespace (not enabled by default).
+* `tabwidth=<n>` tells how many character positions a tab occupies; this
+  is relevant for `indent-with-non-tab` and when git fixes `tab-in-indent`
+  errors. The default tab width is 8. Allowed values are 1 to 63.
 
 core.fsyncobjectfiles::
        This boolean will enable 'fsync()' when writing object files.
@@ -554,9 +566,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.
@@ -774,7 +790,8 @@ color.status.<slot>::
        one of `header` (the header text of the status message),
        `added` or `updated` (files which are added but not committed),
        `changed` (files which are changed but not added in the index),
-       `untracked` (files which are not tracked by git), or
+       `untracked` (files which are not tracked by git),
+       `branch` (the current branch), or
        `nobranch` (the color the 'no branch' warning is shown in, defaulting
        to red). The values of these variables may be specified as in
        color.branch.<slot>.
@@ -880,6 +897,11 @@ diff.wordRegex::
        sequences that match the regular expression are "words", all other
        characters are *ignorable* whitespace.
 
+fetch.recurseSubmodules::
+       A boolean value which changes the behavior for fetch and pull, the
+       default is to not recursively fetch populated sumodules unless
+       configured otherwise.
+
 fetch.unpackLimit::
        If the number of objects fetched over the git native
        transfer is below this
@@ -1532,11 +1554,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
@@ -1792,6 +1816,13 @@ submodule.<name>.update::
        URL and other values found in the `.gitmodules` file.  See
        linkgit:git-submodule[1] and linkgit:gitmodules[5] for details.
 
+submodule.<name>.fetchRecurseSubmodules::
+       This option can be used to enable/disable recursive fetching of this
+       submodule. It can be overriden by using the --[no-]recurse-submodules
+       command line option to "git fetch" and "git pull".
+       This setting will override that from in the linkgit:gitmodules[5]
+       file.
+
 submodule.<name>.ignore::
        Defines under what circumstances "git status" and the diff family show
        a submodule as modified. When set to "all", it will never be considered
index 678675ccdf06dbe0e97d80bb3fbc8985155d2fc0..695696da1b6e78639014388e1d4ac8bf09a0e12d 100644 (file)
@@ -66,6 +66,17 @@ ifndef::git-pull[]
        linkgit:git-config[1].
 endif::git-pull[]
 
+--[no-]recurse-submodules::
+       This option controls if new commits of all populated submodules should
+       be fetched too (see linkgit:git-config[1] and linkgit:gitmodules[5]).
+
+ifndef::git-pull[]
+--submodule-prefix=<path>::
+       Prepend <path> to paths printed in informative messages
+       such as "Fetching submodule foo".  This option is used
+       internally when recursing over submodules.
+endif::git-pull[]
+
 -u::
 --update-head-ok::
        By default 'git fetch' refuses to update the head which
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 5d0c245e38ff751d8c40a6eb6d5b17b80930d526..f56dfcabb96d1510abfd61a13c36135f3824b744 100644 (file)
@@ -92,6 +92,11 @@ OPTIONS
        --(no-)-relative-marks= with the --(import|export)-marks=
        options.
 
+--cat-blob-fd=<fd>::
+       Specify the file descriptor that will be written to
+       when the `cat-blob` command is encountered in the stream.
+       The default behaviour is to write to `stdout`.
+
 --export-pack-edges=<file>::
        After creating a packfile, print a line of data to
        <file> listing the filename of the packfile and the last
@@ -320,6 +325,11 @@ and control the current import process.  More detailed discussion
        standard output.  This command is optional and is not needed
        to perform an import.
 
+`cat-blob`::
+       Causes fast-import to print a blob in 'cat-file --batch'
+       format to the file descriptor set with `--cat-blob-fd` or
+       `stdout` if unspecified.
+
 `feature`::
        Require that fast-import supports the specified feature, or
        abort if it does not.
@@ -879,34 +889,65 @@ Placing a `progress` command immediately after a `checkpoint` will
 inform the reader when the `checkpoint` has been completed and it
 can safely access the refs that fast-import updated.
 
-`feature`
-~~~~~~~~~
-Require that fast-import supports the specified feature, or abort if
-it does not.
+`cat-blob`
+~~~~~~~~~~
+Causes fast-import to print a blob to a file descriptor previously
+arranged with the `--cat-blob-fd` argument.  The command otherwise
+has no impact on the current import; its main purpose is to
+retrieve blobs that may be in fast-import's memory but not
+accessible from the target repository.
 
 ....
-       'feature' SP <feature> LF
+       'cat-blob' SP <dataref> LF
 ....
 
-The <feature> part of the command may be any string matching
-^[a-zA-Z][a-zA-Z-]*$ and should be understood by fast-import.
+The `<dataref>` can be either a mark reference (`:<idnum>`)
+set previously or a full 40-byte SHA-1 of a Git blob, preexisting or
+ready to be written.
 
-Feature work identical as their option counterparts with the
-exception of the import-marks feature, see below.
+output uses the same format as `git cat-file --batch`:
 
-The following features are currently supported:
+====
+       <sha1> SP 'blob' SP <size> LF
+       <contents> LF
+====
 
-* date-format
-* import-marks
-* export-marks
-* relative-marks
-* no-relative-marks
-* force
+This command can be used anywhere in the stream that comments are
+accepted.  In particular, the `cat-blob` command can be used in the
+middle of a commit but not in the middle of a `data` command.
 
-The import-marks behaves differently from when it is specified as
-commandline option in that only one "feature import-marks" is allowed
-per stream. Also, any --import-marks= specified on the commandline
-will override those from the stream (if any).
+`feature`
+~~~~~~~~~
+Require that fast-import supports the specified feature, or abort if
+it does not.
+
+....
+       'feature' SP <feature> ('=' <argument>)? LF
+....
+
+The <feature> part of the command may be any one of the following:
+
+date-format::
+export-marks::
+relative-marks::
+no-relative-marks::
+force::
+       Act as though the corresponding command-line option with
+       a leading '--' was passed on the command line
+       (see OPTIONS, above).
+
+import-marks::
+       Like --import-marks except in two respects: first, only one
+       "feature import-marks" command is allowed per stream;
+       second, an --import-marks= command-line option overrides
+       any "feature import-marks" command in the stream.
+
+cat-blob::
+       Ignored.  Versions of fast-import not supporting the
+       "cat-blob" command will exit with a message indicating so.
+       This lets the import error out early with a clear message,
+       rather than wasting time on the early part of an import
+       before the unsupported command is detected.
 
 `option`
 ~~~~~~~~
@@ -933,6 +974,7 @@ not be passed as option:
 * date-format
 * import-marks
 * export-marks
+* cat-blob-fd
 * force
 
 Crash Reports
@@ -1233,6 +1275,13 @@ and lazy loading of subtrees, allows fast-import to efficiently import
 projects with 2,000+ branches and 45,114+ files in a very limited
 memory footprint (less than 2.7 MiB per active branch).
 
+Signals
+-------
+Sending *SIGUSR1* to the 'git fast-import' process ends the current
+packfile early, simulating a `checkpoint` command.  The impatient
+operator can use this facility to peek at the objects and refs from an
+import in progress, at the cost of some added running time and worse
+compression.
 
 Author
 ------
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..30466917dad52eefd76de95aead3fdc12ad8425d 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
@@ -98,8 +98,9 @@ include::merge-options.txt[]
        fetched, the rebase uses that information to avoid rebasing
        non-local changes.
 +
-See `branch.<name>.rebase` in linkgit:git-config[1] if you want to make
-`git pull` always use `{litdd}rebase` instead of merging.
+See `branch.<name>.rebase` and `branch.autosetuprebase` in
+linkgit:git-config[1] if you want to make `git pull` always use
+`{litdd}rebase` instead of merging.
 +
 [NOTE]
 This is a potentially _dangerous_ mode of operation.
index 30e5c0eb14470a5f72174b1071eac79c55fb0c41..96680c84561c434c4cf3a00cfe958b21f18a3d8d 100644 (file)
@@ -279,6 +279,10 @@ which makes little sense.
 --no-verify::
        This option bypasses the pre-rebase hook.  See also linkgit:githooks[5].
 
+--verify::
+       Allows the pre-rebase hook to run, which is the default.  This option can
+       be used to override --no-verify.  See also linkgit:githooks[5].
+
 -C<n>::
        Ensure at least <n> lines of surrounding context match before
        and after each change.  When fewer lines of surrounding
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 51e8e0af1e7ff37ce4abfe53b6d52e9935b536a8..c0416e5e1a5243ef228173a3a45bb47bf1f3a840 100644 (file)
@@ -20,8 +20,14 @@ The following browsers (or commands) are currently supported:
 
 * firefox (this is the default under X Window when not using KDE)
 * iceweasel
+* seamonkey
+* iceape
+* chromium (also supported as chromium-browser)
+* google-chrome (also supported as chrome)
 * konqueror (this is the default under KDE, see 'Note about konqueror' below)
+* opera
 * w3m (this is the default outside graphical environments)
+* elinks
 * links
 * lynx
 * dillo
index 0c897df6a71ddb5e7f4e960300c2b336ede5aab2..80d795dc567553fe3a89b34e200d881026de1aba 100644 (file)
@@ -44,31 +44,36 @@ 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.4/git.html[documentation for release 1.7.3.4]
 
 * release notes for
+  link:RelNotes/1.7.3.4.txt[1.7.3.4],
+  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 c80ca5da4387e44f42b3bfd54132efc1def881b6..5a7f9364299cb4ed34a7dc35688c9bddcd35ff3d 100644 (file)
@@ -723,6 +723,8 @@ control per path.
 Set::
 
        Notice all types of potential whitespace errors known to git.
+       The tab width is taken from the value of the `core.whitespace`
+       configuration variable.
 
 Unset::
 
@@ -730,13 +732,13 @@ Unset::
 
 Unspecified::
 
-       Use the value of `core.whitespace` configuration variable to
+       Use the value of the `core.whitespace` configuration variable to
        decide what to notice as error.
 
 String::
 
        Specify a comma separate list of common whitespace problems to
-       notice in the same format as `core.whitespace` configuration
+       notice in the same format as the `core.whitespace` configuration
        variable.
 
 
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 bcffd95ada574a8fb3c6e62e539e736a17170414..6c93202e735e7b7caad89120245050e0a06e5537 100644 (file)
@@ -44,6 +44,14 @@ submodule.<name>.update::
        This config option is overridden if 'git submodule update' is given
        the '--merge' or '--rebase' options.
 
+submodule.<name>.fetchRecurseSubmodules::
+       This option can be used to enable/disable recursive fetching of this
+       submodule. If this option is also present in the submodules entry in
+       .git/config of the superproject, the setting there will override the
+       one found in .gitmodules.
+       Both settings can be overriden on the command line by using the
+       "--[no-]recurse-submodules" option to "git fetch" and "git pull"..
+
 submodule.<name>.ignore::
        Defines under what circumstances "git status" and the diff family show
        a submodule as modified. When set to "all", it will never be considered
index 3d4b79c480e2c84150bee96392ee2dd2a4b7d3ea..8b519d74487ce1089462df8556e76d1829fb82d4 100644 (file)
@@ -121,6 +121,10 @@ the `$GIT_DIR/refs` directory or from the `$GIT_DIR/packed-refs` file.
   ':path' (with an empty part before the colon, e.g. `:README`)
   is a special case of the syntax described next: content
   recorded in the index at the given path.
+  A path starting with './' or '../' is relative to current working directory.
+  The given path will be converted to be relative to working tree's root directory.
+  This is most useful to address a blob or tree from a commit or tree that has
+  the same tree structure with the working tree.
 
 * A colon, optionally followed by a stage number (0 to 3) and a
   colon, followed by a path (e.g. `:0:README`); this names a blob object in the
diff --git a/INSTALL b/INSTALL
index 10a1cba643cc9e6cc9a441708b1d43f468fb8383..16e45f114f18d5d6964ab9fa39eee617dfa59bfc 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -122,8 +122,9 @@ Issues of note:
    Building and installing the pdf file additionally requires
    dblatex.  Version 0.2.7 with asciidoc >= 8.2.7 is known to work.
 
-   The documentation is written for AsciiDoc 7, but "make
-   ASCIIDOC8=YesPlease doc" will let you format with AsciiDoc 8.
+   The documentation is written for AsciiDoc 7, but by default
+   uses some compatibility wrappers to work on AsciiDoc 8. If you have
+   AsciiDoc 7, try "make ASCIIDOC7=YesPlease".
 
    Alternatively, pre-formatted documentation is available in
    "html" and "man" branches of the git repository itself.  For
index 919ed2b7ec87cb8399bb7c1119251c1704e2fbe7..57d9c65e03c96d0b337298b32ccaeccaae348c8e 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
@@ -162,13 +167,13 @@ all::
 # Define NO_ST_BLOCKS_IN_STRUCT_STAT if your platform does not have st_blocks
 # field that counts the on-disk footprint in 512-byte blocks.
 #
-# Define ASCIIDOC8 if you want to format documentation with AsciiDoc 8
+# Define ASCIIDOC7 if you want to format documentation with AsciiDoc 7
 #
 # Define DOCBOOK_XSL_172 if you want to format man pages with DocBook XSL v1.72
 # (not v1.73 or v1.71).
 #
-# Define ASCIIDOC_NO_ROFF if your DocBook XSL escapes raw roff directives
-# (versions 1.72 and later and 1.68.1 and earlier).
+# Define ASCIIDOC_ROFF if your DocBook XSL does not escape raw roff directives
+# (versions 1.68.1 through v1.72).
 #
 # Define GNU_ROFF if your target system uses GNU groff.  This forces
 # apostrophes to be ASCII so that cut&pasting examples to the shell
@@ -499,6 +504,7 @@ LIB_H += compat/mingw.h
 LIB_H += compat/win32/pthread.h
 LIB_H += compat/win32/syslog.h
 LIB_H += compat/win32/sys/poll.h
+LIB_H += compat/win32/dirent.h
 LIB_H += csum-file.h
 LIB_H += decorate.h
 LIB_H += delta.h
@@ -520,6 +526,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 +617,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 +673,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 +740,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 +860,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 +1067,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,10 +1097,12 @@ 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/win32/dirent.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
+       EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib
        PTHREAD_LIBS =
        lib =
 ifndef DEBUG
@@ -1099,6 +1114,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 +1144,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,11 +1168,11 @@ 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
+               compat/win32/sys/poll.o compat/win32/dirent.o
        EXTLIBS += -lws2_32
        PTHREAD_LIBS =
        X = .exe
@@ -1268,11 +1303,15 @@ else
        BLK_SHA1 = 1
        OPENSSL_LIBSSL =
 endif
+ifdef NO_OPENSSL
+       LIB_4_CRYPTO =
+else
 ifdef NEEDS_SSL_WITH_CRYPTO
        LIB_4_CRYPTO = $(OPENSSL_LINK) -lcrypto -lssl
 else
        LIB_4_CRYPTO = $(OPENSSL_LINK) -lcrypto
 endif
+endif
 ifdef NEEDS_LIBICONV
        ifdef ICONVDIR
                BASIC_CFLAGS += -I$(ICONVDIR)/include
@@ -1345,6 +1384,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 +1413,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
@@ -1527,8 +1586,8 @@ ifndef V
 endif
 endif
 
-ifdef ASCIIDOC8
-       export ASCIIDOC8
+ifdef ASCIIDOC7
+       export ASCIIDOC7
 endif
 
 # Shell quote (do not use $(call) to accommodate ancient setups);
@@ -1620,6 +1679,8 @@ git$X: git.o $(BUILTIN_OBJS) $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \
                $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS)
 
+help.o: common-cmds.h
+
 builtin/help.o: common-cmds.h
 builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \
        '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
@@ -1775,6 +1836,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,25 +1946,26 @@ 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
+http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h
 
 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..aa30ec52692558b0d8ed3dc3b6d671185682026e 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"),
@@ -2309,8 +2325,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        save_commit_buffer = 0;
        dashdash_pos = 0;
 
-       parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH |
-                           PARSE_OPT_KEEP_ARGV0);
+       parse_options_start(&ctx, argc, argv, prefix, options,
+                           PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);
        for (;;) {
                switch (parse_options_step(&ctx, options, blame_opt_usage)) {
                case PARSE_OPT_HELP:
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..6c09857a602193857cf8907c1c1c2574053e29e1 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)
@@ -984,6 +1011,8 @@ static int parse_status_slot(const char *var, int offset)
 {
        if (!strcasecmp(var+offset, "header"))
                return WT_STATUS_HEADER;
+       if (!strcasecmp(var+offset, "branch"))
+               return WT_STATUS_ONBRANCH;
        if (!strcasecmp(var+offset, "updated")
                || !strcasecmp(var+offset, "added"))
                return WT_STATUS_UPDATED;
@@ -1070,6 +1099,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 +1287,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 43caff2ffe185df6ab224b93b4fd9ce3d82de2d0..a0f52c1b72b2ffd95e781abffb78d7b66ec583c7 100644 (file)
@@ -6,6 +6,7 @@
 #include "exec_cmd.h"
 #include "parse-options.h"
 #include "diff.h"
+#include "hash.h"
 
 #define SEEN           (1u<<0)
 #define MAX_TAGS       (FLAG_BITS - 1)
@@ -22,7 +23,8 @@ static int tags;      /* Allow lightweight tags */
 static int longformat;
 static int abbrev = DEFAULT_ABBREV;
 static int max_candidates = 10;
-static int found_names;
+static struct hash_table names;
+static int have_util;
 static const char *pattern;
 static int always;
 static const char *dirty;
@@ -34,16 +36,44 @@ static const char *diff_index_args[] = {
 
 
 struct commit_name {
+       struct commit_name *next;
+       unsigned char peeled[20];
        struct tag *tag;
        unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */
        unsigned name_checked:1;
        unsigned char sha1[20];
-       char path[FLEX_ARRAY]; /* more */
+       const char *path;
 };
 static const char *prio_names[] = {
        "head", "lightweight", "annotated",
 };
 
+static inline unsigned int hash_sha1(const unsigned char *sha1)
+{
+       unsigned int hash;
+       memcpy(&hash, sha1, sizeof(hash));
+       return hash;
+}
+
+static inline struct commit_name *find_commit_name(const unsigned char *peeled)
+{
+       struct commit_name *n = lookup_hash(hash_sha1(peeled), &names);
+       while (n && !!hashcmp(peeled, n->peeled))
+               n = n->next;
+       return n;
+}
+
+static int set_util(void *chain)
+{
+       struct commit_name *n;
+       for (n = chain; n; n = n->next) {
+               struct commit *c = lookup_commit_reference_gently(n->peeled, 1);
+               if (c)
+                       c->util = n;
+       }
+       return 0;
+}
+
 static int replace_name(struct commit_name *e,
                               int prio,
                               const unsigned char *sha1,
@@ -78,31 +108,36 @@ static int replace_name(struct commit_name *e,
 }
 
 static void add_to_known_names(const char *path,
-                              struct commit *commit,
+                              const unsigned char *peeled,
                               int prio,
                               const unsigned char *sha1)
 {
-       struct commit_name *e = commit->util;
+       struct commit_name *e = find_commit_name(peeled);
        struct tag *tag = NULL;
        if (replace_name(e, prio, sha1, &tag)) {
-               size_t len = strlen(path)+1;
-               free(e);
-               e = xmalloc(sizeof(struct commit_name) + len);
+               if (!e) {
+                       void **pos;
+                       e = xmalloc(sizeof(struct commit_name));
+                       hashcpy(e->peeled, peeled);
+                       pos = insert_hash(hash_sha1(peeled), e, &names);
+                       if (pos) {
+                               e->next = *pos;
+                               *pos = e;
+                       } else {
+                               e->next = NULL;
+                       }
+               }
                e->tag = tag;
                e->prio = prio;
                e->name_checked = 0;
                hashcpy(e->sha1, sha1);
-               memcpy(e->path, path, len);
-               commit->util = e;
+               e->path = path;
        }
-       found_names = 1;
 }
 
 static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)
 {
        int might_be_tag = !prefixcmp(path, "refs/tags/");
-       struct commit *commit;
-       struct object *object;
        unsigned char peeled[20];
        int is_tag, prio;
 
@@ -110,16 +145,10 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void
                return 0;
 
        if (!peel_ref(path, peeled) && !is_null_sha1(peeled)) {
-               commit = lookup_commit_reference_gently(peeled, 1);
-               if (!commit)
-                       return 0;
-               is_tag = !!hashcmp(sha1, commit->object.sha1);
+               is_tag = !!hashcmp(sha1, peeled);
        } else {
-               commit = lookup_commit_reference_gently(sha1, 1);
-               object = parse_object(sha1);
-               if (!commit || !object)
-                       return 0;
-               is_tag = object->type == OBJ_TAG;
+               hashcpy(peeled, sha1);
+               is_tag = 0;
        }
 
        /* If --all, then any refs are used.
@@ -142,7 +171,7 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void
                if (!prio)
                        return 0;
        }
-       add_to_known_names(all ? path + 5 : path + 10, commit, prio, sha1);
+       add_to_known_names(all ? path + 5 : path + 10, peeled, prio, sha1);
        return 0;
 }
 
@@ -240,7 +269,7 @@ static void describe(const char *arg, int last_one)
        if (!cmit)
                die("%s is not a valid '%s' object", arg, commit_type);
 
-       n = cmit->util;
+       n = find_commit_name(cmit->object.sha1);
        if (n && (tags || all || n->prio == 2)) {
                /*
                 * Exact match to an existing ref.
@@ -259,6 +288,11 @@ static void describe(const char *arg, int last_one)
        if (debug)
                fprintf(stderr, "searching to describe %s\n", arg);
 
+       if (!have_util) {
+               for_each_hash(&names, set_util);
+               have_util = 1;
+       }
+
        list = NULL;
        cmit->object.flags = SEEN;
        commit_list_insert(cmit, &list);
@@ -418,8 +452,9 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
                return cmd_name_rev(i + argc, args, prefix);
        }
 
-       for_each_ref(get_name, NULL);
-       if (!found_names && !always)
+       init_hash(&names);
+       for_each_rawref(get_name, NULL);
+       if (!names.nr && !always)
                die("No names found, cannot describe anything.");
 
        if (argc == 0) {
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 6bcce55c0cdb6686e0585bf11107559ba61a5700..357f3cdbbfd601e2ce3f1261a4d6b30c1257cda4 100644 (file)
@@ -12,6 +12,7 @@
 #include "parse-options.h"
 #include "sigchain.h"
 #include "transport.h"
+#include "submodule.h"
 
 static const char * const builtin_fetch_usage[] = {
        "git fetch [<options>] [<repository> [<refspec>...]]",
@@ -27,13 +28,20 @@ enum {
        TAGS_SET = 2
 };
 
+enum {
+       RECURSE_SUBMODULES_OFF = 0,
+       RECURSE_SUBMODULES_DEFAULT = 1,
+       RECURSE_SUBMODULES_ON = 2
+};
+
 static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
-static int progress;
+static int progress, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
 static int tags = TAGS_DEFAULT;
 static const char *depth;
 static const char *upload_pack;
 static struct strbuf default_rla = STRBUF_INIT;
 static struct transport *transport;
+static const char *submodule_prefix = "";
 
 static struct option builtin_fetch_options[] = {
        OPT__VERBOSITY(&verbosity),
@@ -52,6 +60,9 @@ static struct option builtin_fetch_options[] = {
                    "do not fetch all tags (--no-tags)", TAGS_UNSET),
        OPT_BOOLEAN('p', "prune", &prune,
                    "prune remote-tracking branches no longer on remote"),
+       OPT_SET_INT(0, "recurse-submodules", &recurse_submodules,
+                   "control recursive fetching of submodules",
+                   RECURSE_SUBMODULES_ON),
        OPT_BOOLEAN(0, "dry-run", &dry_run,
                    "dry run"),
        OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
@@ -60,6 +71,8 @@ static struct option builtin_fetch_options[] = {
        OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"),
        OPT_STRING(0, "depth", &depth, "DEPTH",
                   "deepen history of shallow clone"),
+       { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, "DIR",
+                  "prepend this to submodule path output", PARSE_OPT_HIDDEN },
        OPT_END()
 };
 
@@ -783,28 +796,36 @@ static int add_remote_or_group(const char *name, struct string_list *list)
        return 1;
 }
 
-static int fetch_multiple(struct string_list *list)
+static void add_options_to_argv(int *argc, const char **argv)
 {
-       int i, result = 0;
-       const char *argv[11] = { "fetch", "--append" };
-       int argc = 2;
-
        if (dry_run)
-               argv[argc++] = "--dry-run";
+               argv[(*argc)++] = "--dry-run";
        if (prune)
-               argv[argc++] = "--prune";
+               argv[(*argc)++] = "--prune";
        if (update_head_ok)
-               argv[argc++] = "--update-head-ok";
+               argv[(*argc)++] = "--update-head-ok";
        if (force)
-               argv[argc++] = "--force";
+               argv[(*argc)++] = "--force";
        if (keep)
-               argv[argc++] = "--keep";
+               argv[(*argc)++] = "--keep";
+       if (recurse_submodules == RECURSE_SUBMODULES_ON)
+               argv[(*argc)++] = "--recurse-submodules";
        if (verbosity >= 2)
-               argv[argc++] = "-v";
+               argv[(*argc)++] = "-v";
        if (verbosity >= 1)
-               argv[argc++] = "-v";
+               argv[(*argc)++] = "-v";
        else if (verbosity < 0)
-               argv[argc++] = "-q";
+               argv[(*argc)++] = "-q";
+
+}
+
+static int fetch_multiple(struct string_list *list)
+{
+       int i, result = 0;
+       const char *argv[12] = { "fetch", "--append" };
+       int argc = 2;
+
+       add_options_to_argv(&argc, argv);
 
        if (!append && !dry_run) {
                int errcode = truncate_fetch_head();
@@ -925,6 +946,21 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
                }
        }
 
+       if (!result && (recurse_submodules != RECURSE_SUBMODULES_OFF)) {
+               const char *options[10];
+               int num_options = 0;
+               /* Set recursion as default when we already are recursing */
+               if (submodule_prefix[0])
+                       set_config_fetch_recurse_submodules(1);
+               gitmodules_config();
+               git_config(submodule_config, NULL);
+               add_options_to_argv(&num_options, options);
+               result = fetch_populated_submodules(num_options, options,
+                                                   submodule_prefix,
+                                                   recurse_submodules == RECURSE_SUBMODULES_ON,
+                                                   verbosity < 0);
+       }
+
        /* All names were strdup()ed or strndup()ed */
        list.strdup_strings = 1;
        string_list_clear(&list, 0);
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..d8c6c28d2fcc3bd0744f071da536215141dc0d0b 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);
 }
@@ -1159,6 +1158,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 
        if (!use_stdout)
                output_directory = set_outdir(prefix, output_directory);
+       else
+               setup_pager();
 
        if (output_directory) {
                if (use_stdout)
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 2135b0dde11faa0c501682ed074e0ba96bf28b42..1a21e4b0538565f7a64488ed7b3a5c317e559699 100644 (file)
@@ -268,8 +268,8 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
        git_config(git_default_config, NULL);
        shortlog_init(&log);
        init_revisions(&rev, prefix);
-       parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH |
-                           PARSE_OPT_KEEP_ARGV0);
+       parse_options_start(&ctx, argc, argv, prefix, options,
+                           PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);
 
        for (;;) {
                switch (parse_options_step(&ctx, options, shortlog_usage)) {
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..56baf27fb7eb18d77e8793d089f2a719d4876689 100644 (file)
@@ -10,6 +10,7 @@
 #include "builtin.h"
 #include "refs.h"
 #include "resolve-undo.h"
+#include "parse-options.h"
 
 /*
  * Default to not allowing changes to the list of files. The
@@ -397,8 +398,10 @@ static void read_index_info(int line_termination)
        strbuf_release(&uq);
 }
 
-static const char update_index_usage[] =
-"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--skip-worktree|--no-skip-worktree] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] [<file>...]";
+static const char * const update_index_usage[] = {
+       "git update-index [options] [--] [<file>...]",
+       NULL
+};
 
 static unsigned char head_sha1[20];
 static unsigned char merge_head_sha1[20];
@@ -578,16 +581,214 @@ static int do_reupdate(int ac, const char **av,
        return 0;
 }
 
+struct refresh_params {
+       unsigned int flags;
+       int *has_errors;
+};
+
+static int refresh(struct refresh_params *o, unsigned int flag)
+{
+       setup_work_tree();
+       *o->has_errors |= refresh_cache(o->flags | flag);
+       return 0;
+}
+
+static int refresh_callback(const struct option *opt,
+                               const char *arg, int unset)
+{
+       return refresh(opt->value, 0);
+}
+
+static int really_refresh_callback(const struct option *opt,
+                               const char *arg, int unset)
+{
+       return refresh(opt->value, REFRESH_REALLY);
+}
+
+static int chmod_callback(const struct option *opt,
+                               const char *arg, int unset)
+{
+       char *flip = opt->value;
+       if ((arg[0] != '-' && arg[0] != '+') || arg[1] != 'x' || arg[2])
+               return error("option 'chmod' expects \"+x\" or \"-x\"");
+       *flip = arg[0];
+       return 0;
+}
+
+static int resolve_undo_clear_callback(const struct option *opt,
+                               const char *arg, int unset)
+{
+       resolve_undo_clear();
+       return 0;
+}
+
+static int cacheinfo_callback(struct parse_opt_ctx_t *ctx,
+                               const struct option *opt, int unset)
+{
+       unsigned char sha1[20];
+       unsigned int mode;
+
+       if (ctx->argc <= 3)
+               return error("option 'cacheinfo' expects three arguments");
+       if (strtoul_ui(*++ctx->argv, 8, &mode) ||
+           get_sha1_hex(*++ctx->argv, sha1) ||
+           add_cacheinfo(mode, sha1, *++ctx->argv, 0))
+               die("git update-index: --cacheinfo cannot add %s", *ctx->argv);
+       ctx->argc -= 3;
+       return 0;
+}
+
+static int stdin_cacheinfo_callback(struct parse_opt_ctx_t *ctx,
+                             const struct option *opt, int unset)
+{
+       int *line_termination = opt->value;
+
+       if (ctx->argc != 1)
+               return error("option '%s' must be the last argument", opt->long_name);
+       allow_add = allow_replace = allow_remove = 1;
+       read_index_info(*line_termination);
+       return 0;
+}
+
+static int stdin_callback(struct parse_opt_ctx_t *ctx,
+                               const struct option *opt, int unset)
+{
+       int *read_from_stdin = opt->value;
+
+       if (ctx->argc != 1)
+               return error("option '%s' must be the last argument", opt->long_name);
+       *read_from_stdin = 1;
+       return 0;
+}
+
+static int unresolve_callback(struct parse_opt_ctx_t *ctx,
+                               const struct option *opt, int flags)
+{
+       int *has_errors = opt->value;
+       const char *prefix = startup_info->prefix;
+
+       /* consume remaining arguments. */
+       *has_errors = do_unresolve(ctx->argc, ctx->argv,
+                               prefix, prefix ? strlen(prefix) : 0);
+       if (*has_errors)
+               active_cache_changed = 0;
+
+       ctx->argv += ctx->argc - 1;
+       ctx->argc = 1;
+       return 0;
+}
+
+static int reupdate_callback(struct parse_opt_ctx_t *ctx,
+                               const struct option *opt, int flags)
+{
+       int *has_errors = opt->value;
+       const char *prefix = startup_info->prefix;
+
+       /* consume remaining arguments. */
+       setup_work_tree();
+       *has_errors = do_reupdate(ctx->argc, ctx->argv,
+                               prefix, prefix ? strlen(prefix) : 0);
+       if (*has_errors)
+               active_cache_changed = 0;
+
+       ctx->argv += ctx->argc - 1;
+       ctx->argc = 1;
+       return 0;
+}
+
 int cmd_update_index(int argc, const char **argv, const char *prefix)
 {
-       int i, newfd, entries, has_errors = 0, line_termination = '\n';
-       int allow_options = 1;
+       int newfd, entries, has_errors = 0, line_termination = '\n';
        int read_from_stdin = 0;
        int prefix_length = prefix ? strlen(prefix) : 0;
        char set_executable_bit = 0;
-       unsigned int refresh_flags = 0;
+       struct refresh_params refresh_args = {0, &has_errors};
        int lock_error = 0;
        struct lock_file *lock_file;
+       struct parse_opt_ctx_t ctx;
+       int parseopt_state = PARSE_OPT_UNKNOWN;
+       struct option options[] = {
+               OPT_BIT('q', NULL, &refresh_args.flags,
+                       "continue refresh even when index needs update",
+                       REFRESH_QUIET),
+               OPT_BIT(0, "ignore-submodules", &refresh_args.flags,
+                       "refresh: ignore submodules",
+                       REFRESH_IGNORE_SUBMODULES),
+               OPT_SET_INT(0, "add", &allow_add,
+                       "do not ignore new files", 1),
+               OPT_SET_INT(0, "replace", &allow_replace,
+                       "let files replace directories and vice-versa", 1),
+               OPT_SET_INT(0, "remove", &allow_remove,
+                       "notice files missing from worktree", 1),
+               OPT_BIT(0, "unmerged", &refresh_args.flags,
+                       "refresh even if index contains unmerged entries",
+                       REFRESH_UNMERGED),
+               {OPTION_CALLBACK, 0, "refresh", &refresh_args, NULL,
+                       "refresh stat information",
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG,
+                       refresh_callback},
+               {OPTION_CALLBACK, 0, "really-refresh", &refresh_args, NULL,
+                       "like --refresh, but ignore assume-unchanged setting",
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG,
+                       really_refresh_callback},
+               {OPTION_LOWLEVEL_CALLBACK, 0, "cacheinfo", NULL,
+                       "<mode> <object> <path>",
+                       "add the specified entry to the index",
+                       PARSE_OPT_NOARG |       /* disallow --cacheinfo=<mode> form */
+                       PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
+                       (parse_opt_cb *) cacheinfo_callback},
+               {OPTION_CALLBACK, 0, "chmod", &set_executable_bit, "(+/-)x",
+                       "override the executable bit of the listed files",
+                       PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
+                       chmod_callback},
+               {OPTION_SET_INT, 0, "assume-unchanged", &mark_valid_only, NULL,
+                       "mark files as \"not changing\"",
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG},
+               {OPTION_SET_INT, 0, "no-assume-unchanged", &mark_valid_only, NULL,
+                       "clear assumed-unchanged bit",
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG},
+               {OPTION_SET_INT, 0, "skip-worktree", &mark_skip_worktree_only, NULL,
+                       "mark files as \"index-only\"",
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG},
+               {OPTION_SET_INT, 0, "no-skip-worktree", &mark_skip_worktree_only, NULL,
+                       "clear skip-worktree bit",
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG},
+               OPT_SET_INT(0, "info-only", &info_only,
+                       "add to index only; do not add content to object database", 1),
+               OPT_SET_INT(0, "force-remove", &force_remove,
+                       "remove named paths even if present in worktree", 1),
+               OPT_SET_INT('z', NULL, &line_termination,
+                       "with --stdin: input lines are terminated by null bytes", '\0'),
+               {OPTION_LOWLEVEL_CALLBACK, 0, "stdin", &read_from_stdin, NULL,
+                       "read list of paths to be updated from standard input",
+                       PARSE_OPT_NONEG | PARSE_OPT_NOARG,
+                       (parse_opt_cb *) stdin_callback},
+               {OPTION_LOWLEVEL_CALLBACK, 0, "index-info", &line_termination, NULL,
+                       "add entries from standard input to the index",
+                       PARSE_OPT_NONEG | PARSE_OPT_NOARG,
+                       (parse_opt_cb *) stdin_cacheinfo_callback},
+               {OPTION_LOWLEVEL_CALLBACK, 0, "unresolve", &has_errors, NULL,
+                       "repopulate stages #2 and #3 for the listed paths",
+                       PARSE_OPT_NONEG | PARSE_OPT_NOARG,
+                       (parse_opt_cb *) unresolve_callback},
+               {OPTION_LOWLEVEL_CALLBACK, 'g', "again", &has_errors, NULL,
+                       "only update entries that differ from HEAD",
+                       PARSE_OPT_NONEG | PARSE_OPT_NOARG,
+                       (parse_opt_cb *) reupdate_callback},
+               OPT_BIT(0, "ignore-missing", &refresh_args.flags,
+                       "ignore files missing from worktree",
+                       REFRESH_IGNORE_MISSING),
+               OPT_SET_INT(0, "verbose", &verbose,
+                       "report actions to standard output", 1),
+               {OPTION_CALLBACK, 0, "clear-resolve-undo", NULL, NULL,
+                       "(for porcelains) forget saved unresolved conflicts",
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG,
+                       resolve_undo_clear_callback},
+               OPT_END()
+       };
+
+       if (argc == 2 && !strcmp(argv[1], "-h"))
+               usage(update_index_usage[0]);
 
        git_config(git_default_config, NULL);
 
@@ -602,151 +803,48 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
        if (entries < 0)
                die("cache corrupted");
 
-       for (i = 1 ; i < argc; i++) {
-               const char *path = argv[i];
-               const char *p;
+       /*
+        * Custom copy of parse_options() because we want to handle
+        * filename arguments as they come.
+        */
+       parse_options_start(&ctx, argc, argv, prefix,
+                           options, PARSE_OPT_STOP_AT_NON_OPTION);
+       while (ctx.argc) {
+               if (parseopt_state != PARSE_OPT_DONE)
+                       parseopt_state = parse_options_step(&ctx, options,
+                                                           update_index_usage);
+               if (!ctx.argc)
+                       break;
+               switch (parseopt_state) {
+               case PARSE_OPT_HELP:
+                       exit(129);
+               case PARSE_OPT_NON_OPTION:
+               case PARSE_OPT_DONE:
+               {
+                       const char *path = ctx.argv[0];
+                       const char *p;
 
-               if (allow_options && *path == '-') {
-                       if (!strcmp(path, "--")) {
-                               allow_options = 0;
-                               continue;
-                       }
-                       if (!strcmp(path, "-q")) {
-                               refresh_flags |= REFRESH_QUIET;
-                               continue;
-                       }
-                       if (!strcmp(path, "--ignore-submodules")) {
-                               refresh_flags |= REFRESH_IGNORE_SUBMODULES;
-                               continue;
-                       }
-                       if (!strcmp(path, "--add")) {
-                               allow_add = 1;
-                               continue;
-                       }
-                       if (!strcmp(path, "--replace")) {
-                               allow_replace = 1;
-                               continue;
-                       }
-                       if (!strcmp(path, "--remove")) {
-                               allow_remove = 1;
-                               continue;
-                       }
-                       if (!strcmp(path, "--unmerged")) {
-                               refresh_flags |= REFRESH_UNMERGED;
-                               continue;
-                       }
-                       if (!strcmp(path, "--refresh")) {
-                               setup_work_tree();
-                               has_errors |= refresh_cache(refresh_flags);
-                               continue;
-                       }
-                       if (!strcmp(path, "--really-refresh")) {
-                               setup_work_tree();
-                               has_errors |= refresh_cache(REFRESH_REALLY | refresh_flags);
-                               continue;
-                       }
-                       if (!strcmp(path, "--cacheinfo")) {
-                               unsigned char sha1[20];
-                               unsigned int mode;
-
-                               if (i+3 >= argc)
-                                       die("git update-index: --cacheinfo <mode> <sha1> <path>");
-
-                               if (strtoul_ui(argv[i+1], 8, &mode) ||
-                                   get_sha1_hex(argv[i+2], sha1) ||
-                                   add_cacheinfo(mode, sha1, argv[i+3], 0))
-                                       die("git update-index: --cacheinfo"
-                                           " cannot add %s", argv[i+3]);
-                               i += 3;
-                               continue;
-                       }
-                       if (!strcmp(path, "--chmod=-x") ||
-                           !strcmp(path, "--chmod=+x")) {
-                               if (argc <= i+1)
-                                       die("git update-index: %s <path>", path);
-                               set_executable_bit = path[8];
-                               continue;
-                       }
-                       if (!strcmp(path, "--assume-unchanged")) {
-                               mark_valid_only = MARK_FLAG;
-                               continue;
-                       }
-                       if (!strcmp(path, "--no-assume-unchanged")) {
-                               mark_valid_only = UNMARK_FLAG;
-                               continue;
-                       }
-                       if (!strcmp(path, "--no-skip-worktree")) {
-                               mark_skip_worktree_only = UNMARK_FLAG;
-                               continue;
-                       }
-                       if (!strcmp(path, "--skip-worktree")) {
-                               mark_skip_worktree_only = MARK_FLAG;
-                               continue;
-                       }
-                       if (!strcmp(path, "--info-only")) {
-                               info_only = 1;
-                               continue;
-                       }
-                       if (!strcmp(path, "--force-remove")) {
-                               force_remove = 1;
-                               continue;
-                       }
-                       if (!strcmp(path, "-z")) {
-                               line_termination = 0;
-                               continue;
-                       }
-                       if (!strcmp(path, "--stdin")) {
-                               if (i != argc - 1)
-                                       die("--stdin must be at the end");
-                               read_from_stdin = 1;
-                               break;
-                       }
-                       if (!strcmp(path, "--index-info")) {
-                               if (i != argc - 1)
-                                       die("--index-info must be at the end");
-                               allow_add = allow_replace = allow_remove = 1;
-                               read_index_info(line_termination);
-                               break;
-                       }
-                       if (!strcmp(path, "--unresolve")) {
-                               has_errors = do_unresolve(argc - i, argv + i,
-                                                         prefix, prefix_length);
-                               if (has_errors)
-                                       active_cache_changed = 0;
-                               goto finish;
-                       }
-                       if (!strcmp(path, "--again") || !strcmp(path, "-g")) {
-                               setup_work_tree();
-                               has_errors = do_reupdate(argc - i, argv + i,
-                                                        prefix, prefix_length);
-                               if (has_errors)
-                                       active_cache_changed = 0;
-                               goto finish;
-                       }
-                       if (!strcmp(path, "--ignore-missing")) {
-                               refresh_flags |= REFRESH_IGNORE_MISSING;
-                               continue;
-                       }
-                       if (!strcmp(path, "--verbose")) {
-                               verbose = 1;
-                               continue;
-                       }
-                       if (!strcmp(path, "--clear-resolve-undo")) {
-                               resolve_undo_clear();
-                               continue;
-                       }
-                       if (!strcmp(path, "-h") || !strcmp(path, "--help"))
-                               usage(update_index_usage);
-                       die("unknown option %s", path);
+                       setup_work_tree();
+                       p = prefix_path(prefix, prefix_length, path);
+                       update_one(p, NULL, 0);
+                       if (set_executable_bit)
+                               chmod_path(set_executable_bit, p);
+                       if (p < path || p > path + strlen(path))
+                               free((char *)p);
+                       ctx.argc--;
+                       ctx.argv++;
+                       break;
+               }
+               case PARSE_OPT_UNKNOWN:
+                       if (ctx.argv[0][1] == '-')
+                               error("unknown option '%s'", ctx.argv[0] + 2);
+                       else
+                               error("unknown switch '%c'", *ctx.opt);
+                       usage_with_options(update_index_usage, options);
                }
-               setup_work_tree();
-               p = prefix_path(prefix, prefix_length, path);
-               update_one(p, NULL, 0);
-               if (set_executable_bit)
-                       chmod_path(set_executable_bit, p);
-               if (p < path || p > path + strlen(path))
-                       free((char *)p);
        }
+       argc = parse_options_end(&ctx);
+
        if (read_from_stdin) {
                struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
 
@@ -770,10 +868,9 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
                strbuf_release(&buf);
        }
 
- finish:
        if (active_cache_changed) {
                if (newfd < 0) {
-                       if (refresh_flags & REFRESH_QUIET)
+                       if (refresh_args.flags & REFRESH_QUIET)
                                exit(128);
                        unable_to_lock_index_die(get_index_file(), lock_error);
                }
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..b45525846d04088ad3900fe4986e62259879523d 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -428,7 +428,7 @@ extern const char **get_pathspec(const char *prefix, const char **pathspec);
 extern void setup_work_tree(void);
 extern const char *setup_git_directory_gently(int *);
 extern const char *setup_git_directory(void);
-extern const char *prefix_path(const char *prefix, int len, const char *path);
+extern char *prefix_path(const char *prefix, int len, const char *path);
 extern const char *prefix_filename(const char *prefix, int len, const char *path);
 extern int check_filename(const char *prefix, const char *name);
 extern void verify_filename(const char *prefix, const char *name);
@@ -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)
@@ -1087,15 +1091,17 @@ void shift_tree_by(const unsigned char *, const unsigned char *, unsigned char *
 /*
  * whitespace rules.
  * used by both diff and apply
+ * last two digits are tab width
  */
-#define WS_BLANK_AT_EOL         01
-#define WS_SPACE_BEFORE_TAB    02
-#define WS_INDENT_WITH_NON_TAB 04
-#define WS_CR_AT_EOL           010
-#define WS_BLANK_AT_EOF        020
-#define WS_TAB_IN_INDENT       040
+#define WS_BLANK_AT_EOL         0100
+#define WS_SPACE_BEFORE_TAB     0200
+#define WS_INDENT_WITH_NON_TAB  0400
+#define WS_CR_AT_EOL           01000
+#define WS_BLANK_AT_EOF        02000
+#define WS_TAB_IN_INDENT       04000
 #define WS_TRAILING_SPACE      (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF)
-#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB)
+#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB|8)
+#define WS_TAB_WIDTH_MASK        077
 extern unsigned whitespace_rule_cfg;
 extern unsigned whitespace_rule(const char *);
 extern unsigned parse_whitespace_rule(const char *);
@@ -1104,6 +1110,7 @@ extern void ws_check_emit(const char *line, int len, unsigned ws_rule, FILE *str
 extern char *whitespace_error_string(unsigned ws);
 extern void ws_fix_copy(struct strbuf *, const char *, int, unsigned, int *);
 extern int ws_blank_line(const char *line, int len, unsigned ws_rule);
+#define ws_tab_width(rule)     ((rule) & WS_TAB_WIDTH_MASK)
 
 /* ls-files */
 int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset);
@@ -1117,6 +1124,7 @@ const char *split_cmdline_strerror(int cmdline_errno);
 /* git.c */
 struct startup_info {
        int have_repository;
+       const char *prefix;
 };
 extern struct startup_info *startup_info;
 
diff --git a/color.c b/color.c
index 1b00554dd50d2960b4e66f229b0d2d0da8eb8c28..6a5a54ec668e08ddef0d2f27359f3ebb3272243b 100644 (file)
--- a/color.c
+++ b/color.c
@@ -211,3 +211,8 @@ int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...)
        va_end(args);
        return r;
 }
+
+int color_is_nil(const char *c)
+{
+       return !strcmp(c, "NIL");
+}
diff --git a/color.h b/color.h
index 03ca0647485e8044d4131f2c61c37b4e9408cd2a..170ff4074d220e7e62264c4c63d1419f4a59d664 100644 (file)
--- a/color.h
+++ b/color.h
@@ -43,6 +43,9 @@
 #define GIT_COLOR_BG_MAGENTA   "\033[45m"
 #define GIT_COLOR_BG_CYAN      "\033[46m"
 
+/* A special value meaning "no color selected" */
+#define GIT_COLOR_NIL "NIL"
+
 /*
  * This variable stores the value of color.ui
  */
@@ -62,4 +65,6 @@ int color_fprintf(FILE *fp, const char *color, const char *fmt, ...);
 __attribute__((format (printf, 3, 4)))
 int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...);
 
+int color_is_nil(const char *color);
+
 #endif /* COLOR_H */
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 fdbf093f6eb704ec0ca0e5abf6baf1f94350676b..bee605441910caeddd1e94eb7795f615a3d1348a 100644 (file)
@@ -1566,63 +1566,3 @@ pid_t waitpid(pid_t pid, int *status, unsigned options)
        errno = EINVAL;
        return -1;
 }
-
-#ifndef NO_MINGW_REPLACE_READDIR
-/* MinGW readdir implementation to avoid extra lstats for Git */
-struct mingw_DIR
-{
-       struct _finddata_t      dd_dta;         /* disk transfer area for this dir */
-       struct mingw_dirent     dd_dir;         /* Our own implementation, including d_type */
-       long                    dd_handle;      /* _findnext handle */
-       int                     dd_stat;        /* 0 = next entry to read is first entry, -1 = off the end, positive = 0 based index of next entry */
-       char                    dd_name[1];     /* given path for dir with search pattern (struct is extended) */
-};
-
-struct dirent *mingw_readdir(DIR *dir)
-{
-       WIN32_FIND_DATAA buf;
-       HANDLE handle;
-       struct mingw_DIR *mdir = (struct mingw_DIR*)dir;
-
-       if (!dir->dd_handle) {
-               errno = EBADF; /* No set_errno for mingw */
-               return NULL;
-       }
-
-       if (dir->dd_handle == (long)INVALID_HANDLE_VALUE && dir->dd_stat == 0)
-       {
-               DWORD lasterr;
-               handle = FindFirstFileA(dir->dd_name, &buf);
-               lasterr = GetLastError();
-               dir->dd_handle = (long)handle;
-               if (handle == INVALID_HANDLE_VALUE && (lasterr != ERROR_NO_MORE_FILES)) {
-                       errno = err_win_to_posix(lasterr);
-                       return NULL;
-               }
-       } else if (dir->dd_handle == (long)INVALID_HANDLE_VALUE) {
-               return NULL;
-       } else if (!FindNextFileA((HANDLE)dir->dd_handle, &buf)) {
-               DWORD lasterr = GetLastError();
-               FindClose((HANDLE)dir->dd_handle);
-               dir->dd_handle = (long)INVALID_HANDLE_VALUE;
-               /* POSIX says you shouldn't set errno when readdir can't
-                  find any more files; so, if another error we leave it set. */
-               if (lasterr != ERROR_NO_MORE_FILES)
-                       errno = err_win_to_posix(lasterr);
-               return NULL;
-       }
-
-       /* We get here if `buf' contains valid data.  */
-       strcpy(dir->dd_dir.d_name, buf.cFileName);
-       ++dir->dd_stat;
-
-       /* Set file type, based on WIN32_FIND_DATA */
-       mdir->dd_dir.d_type = 0;
-       if (buf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
-               mdir->dd_dir.d_type |= DT_DIR;
-       else
-               mdir->dd_dir.d_type |= DT_REG;
-
-       return (struct dirent*)&dir->dd_dir;
-}
-#endif // !NO_MINGW_REPLACE_READDIR
index 99a746703f3c9fb19e043109e4a044efe151e0e7..cafc1eb08a71dd60566a7389ed606e777cf17fc8 100644 (file)
@@ -14,12 +14,6 @@ typedef int socklen_t;
 #define S_ISLNK(x) (((x) & S_IFMT) == S_IFLNK)
 #define S_ISSOCK(x) 0
 
-#ifndef _STAT_H_
-#define S_IRUSR 0
-#define S_IWUSR 0
-#define S_IXUSR 0
-#define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR)
-#endif
 #define S_IRGRP 0
 #define S_IWGRP 0
 #define S_IXGRP 0
@@ -37,6 +31,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
@@ -319,35 +316,6 @@ int main(int argc, const char **argv) \
 } \
 static int mingw_main(c,v)
 
-#ifndef NO_MINGW_REPLACE_READDIR
-/*
- * A replacement of readdir, to ensure that it reads the file type at
- * the same time. This avoid extra unneeded lstats in git on MinGW
- */
-#undef DT_UNKNOWN
-#undef DT_DIR
-#undef DT_REG
-#undef DT_LNK
-#define DT_UNKNOWN     0
-#define DT_DIR         1
-#define DT_REG         2
-#define DT_LNK         3
-
-struct mingw_dirent
-{
-       long            d_ino;                  /* Always zero. */
-       union {
-               unsigned short  d_reclen;       /* Always zero. */
-               unsigned char   d_type;         /* Reimplementation adds this */
-       };
-       unsigned short  d_namlen;               /* Length of name in d_name. */
-       char            d_name[FILENAME_MAX];   /* File name. */
-};
-#define dirent mingw_dirent
-#define readdir(x) mingw_readdir(x)
-struct dirent *mingw_readdir(DIR *dir);
-#endif // !NO_MINGW_REPLACE_READDIR
-
 /*
  * Used by Pthread API implementation for Windows
  */
index ac04a4ccbdd8319349aa2647374d78fd6639f425..71843d7eefafcea2ddf71b9fbff519790f661481 100644 (file)
@@ -3,33 +3,4 @@
 #include <conio.h>
 #include "../strbuf.h"
 
-DIR *opendir(const char *name)
-{
-       int len;
-       DIR *p;
-       p = (DIR*)malloc(sizeof(DIR));
-       memset(p, 0, sizeof(DIR));
-       strncpy(p->dd_name, name, PATH_MAX);
-       len = strlen(p->dd_name);
-       p->dd_name[len] = '/';
-       p->dd_name[len+1] = '*';
-
-       if (p == NULL)
-               return NULL;
-
-       p->dd_handle = _findfirst(p->dd_name, &p->dd_dta);
-
-       if (p->dd_handle == -1) {
-               free(p);
-               return NULL;
-       }
-       return p;
-}
-int closedir(DIR *dir)
-{
-       _findclose(dir->dd_handle);
-       free(dir);
-       return 0;
-}
-
 #include "mingw.c"
diff --git a/compat/vcbuild/include/dirent.h b/compat/vcbuild/include/dirent.h
deleted file mode 100644 (file)
index 440618d..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * DIRENT.H (formerly DIRLIB.H)
- * This file has no copyright assigned and is placed in the Public Domain.
- * This file is a part of the mingw-runtime package.
- *
- * The mingw-runtime package and its code is distributed in the hope that it
- * will be useful but WITHOUT ANY WARRANTY.  ALL WARRANTIES, EXPRESSED OR
- * IMPLIED ARE HEREBY DISCLAIMED.  This includes but is not limited to
- * warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- *
- * You are free to use this package and its code without limitation.
- */
-#ifndef _DIRENT_H_
-#define _DIRENT_H_
-#include <io.h>
-
-#define PATH_MAX 512
-
-#define __MINGW_NOTHROW
-
-#ifndef RC_INVOKED
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct dirent
-{
-       long            d_ino;          /* Always zero. */
-       unsigned short  d_reclen;       /* Always zero. */
-       unsigned short  d_namlen;       /* Length of name in d_name. */
-       char            d_name[FILENAME_MAX]; /* File name. */
-};
-
-/*
- * This is an internal data structure. Good programmers will not use it
- * except as an argument to one of the functions below.
- * dd_stat field is now int (was short in older versions).
- */
-typedef struct
-{
-       /* disk transfer area for this dir */
-       struct _finddata_t      dd_dta;
-
-       /* dirent struct to return from dir (NOTE: this makes this thread
-        * safe as long as only one thread uses a particular DIR struct at
-        * a time) */
-       struct dirent           dd_dir;
-
-       /* _findnext handle */
-       long                    dd_handle;
-
-       /*
-        * Status of search:
-        *   0 = not started yet (next entry to read is first entry)
-        *  -1 = off the end
-        *   positive = 0 based index of next entry
-        */
-       int                     dd_stat;
-
-       /* given path for dir with search pattern (struct is extended) */
-       char                    dd_name[PATH_MAX+3];
-} DIR;
-
-DIR* __cdecl __MINGW_NOTHROW opendir (const char*);
-struct dirent* __cdecl __MINGW_NOTHROW readdir (DIR*);
-int __cdecl __MINGW_NOTHROW closedir (DIR*);
-void __cdecl __MINGW_NOTHROW rewinddir (DIR*);
-long __cdecl __MINGW_NOTHROW telldir (DIR*);
-void __cdecl __MINGW_NOTHROW seekdir (DIR*, long);
-
-
-/* wide char versions */
-
-struct _wdirent
-{
-       long            d_ino;          /* Always zero. */
-       unsigned short  d_reclen;       /* Always zero. */
-       unsigned short  d_namlen;       /* Length of name in d_name. */
-       wchar_t         d_name[FILENAME_MAX]; /* File name. */
-};
-
-/*
- * This is an internal data structure. Good programmers will not use it
- * except as an argument to one of the functions below.
- */
-typedef struct
-{
-       /* disk transfer area for this dir */
-       //struct _wfinddata_t   dd_dta;
-
-       /* dirent struct to return from dir (NOTE: this makes this thread
-        * safe as long as only one thread uses a particular DIR struct at
-        * a time) */
-       struct _wdirent         dd_dir;
-
-       /* _findnext handle */
-       long                    dd_handle;
-
-       /*
-        * Status of search:
-        *   0 = not started yet (next entry to read is first entry)
-        *  -1 = off the end
-        *   positive = 0 based index of next entry
-        */
-       int                     dd_stat;
-
-       /* given path for dir with search pattern (struct is extended) */
-       wchar_t                 dd_name[1];
-} _WDIR;
-
-
-
-_WDIR* __cdecl __MINGW_NOTHROW _wopendir (const wchar_t*);
-struct _wdirent*  __cdecl __MINGW_NOTHROW _wreaddir (_WDIR*);
-int __cdecl __MINGW_NOTHROW _wclosedir (_WDIR*);
-void __cdecl __MINGW_NOTHROW _wrewinddir (_WDIR*);
-long __cdecl __MINGW_NOTHROW _wtelldir (_WDIR*);
-void __cdecl __MINGW_NOTHROW _wseekdir (_WDIR*, long);
-
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* Not RC_INVOKED */
-
-#endif /* Not _DIRENT_H_ */
index 2a4f276869c507c49c3f1d066fa648c6ea78890d..b14fcf94da405c6f3267437e59936d598168e00f 100644 (file)
@@ -45,6 +45,10 @@ typedef unsigned long long uintmax_t;
 
 typedef int64_t off64_t;
 
+#define INTMAX_MIN  _I64_MIN
+#define INTMAX_MAX  _I64_MAX
+#define UINTMAX_MAX _UI64_MAX
+
 #define STDOUT_FILENO 1
 #define STDERR_FILENO 2
 
diff --git a/compat/win32/dirent.c b/compat/win32/dirent.c
new file mode 100644 (file)
index 0000000..7a0debe
--- /dev/null
@@ -0,0 +1,108 @@
+#include "../git-compat-util.h"
+#include "dirent.h"
+
+struct DIR {
+       struct dirent dd_dir; /* includes d_type */
+       HANDLE dd_handle;     /* FindFirstFile handle */
+       int dd_stat;          /* 0-based index */
+       char dd_name[1];      /* extend struct */
+};
+
+DIR *opendir(const char *name)
+{
+       DWORD attrs = GetFileAttributesA(name);
+       int len;
+       DIR *p;
+
+       /* check for valid path */
+       if (attrs == INVALID_FILE_ATTRIBUTES) {
+               errno = ENOENT;
+               return NULL;
+       }
+
+       /* check if it's a directory */
+       if (!(attrs & FILE_ATTRIBUTE_DIRECTORY)) {
+               errno = ENOTDIR;
+               return NULL;
+       }
+
+       /* check that the pattern won't be too long for FindFirstFileA */
+       len = strlen(name);
+       if (is_dir_sep(name[len - 1]))
+               len--;
+       if (len + 2 >= MAX_PATH) {
+               errno = ENAMETOOLONG;
+               return NULL;
+       }
+
+       p = malloc(sizeof(DIR) + len + 2);
+       if (!p)
+               return NULL;
+
+       memset(p, 0, sizeof(DIR) + len + 2);
+       strcpy(p->dd_name, name);
+       p->dd_name[len] = '/';
+       p->dd_name[len+1] = '*';
+
+       p->dd_handle = INVALID_HANDLE_VALUE;
+       return p;
+}
+
+struct dirent *readdir(DIR *dir)
+{
+       WIN32_FIND_DATAA buf;
+       HANDLE handle;
+
+       if (!dir || !dir->dd_handle) {
+               errno = EBADF; /* No set_errno for mingw */
+               return NULL;
+       }
+
+       if (dir->dd_handle == INVALID_HANDLE_VALUE && dir->dd_stat == 0) {
+               DWORD lasterr;
+               handle = FindFirstFileA(dir->dd_name, &buf);
+               lasterr = GetLastError();
+               dir->dd_handle = handle;
+               if (handle == INVALID_HANDLE_VALUE && (lasterr != ERROR_NO_MORE_FILES)) {
+                       errno = err_win_to_posix(lasterr);
+                       return NULL;
+               }
+       } else if (dir->dd_handle == INVALID_HANDLE_VALUE) {
+               return NULL;
+       } else if (!FindNextFileA(dir->dd_handle, &buf)) {
+               DWORD lasterr = GetLastError();
+               FindClose(dir->dd_handle);
+               dir->dd_handle = INVALID_HANDLE_VALUE;
+               /* POSIX says you shouldn't set errno when readdir can't
+                  find any more files; so, if another error we leave it set. */
+               if (lasterr != ERROR_NO_MORE_FILES)
+                       errno = err_win_to_posix(lasterr);
+               return NULL;
+       }
+
+       /* We get here if `buf' contains valid data.  */
+       strcpy(dir->dd_dir.d_name, buf.cFileName);
+       ++dir->dd_stat;
+
+       /* Set file type, based on WIN32_FIND_DATA */
+       dir->dd_dir.d_type = 0;
+       if (buf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+               dir->dd_dir.d_type |= DT_DIR;
+       else
+               dir->dd_dir.d_type |= DT_REG;
+
+       return &dir->dd_dir;
+}
+
+int closedir(DIR *dir)
+{
+       if (!dir) {
+               errno = EBADF;
+               return -1;
+       }
+
+       if (dir->dd_handle != INVALID_HANDLE_VALUE)
+               FindClose(dir->dd_handle);
+       free(dir);
+       return 0;
+}
diff --git a/compat/win32/dirent.h b/compat/win32/dirent.h
new file mode 100644 (file)
index 0000000..927a25c
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef DIRENT_H
+#define DIRENT_H
+
+typedef struct DIR DIR;
+
+#define DT_UNKNOWN 0
+#define DT_DIR     1
+#define DT_REG     2
+#define DT_LNK     3
+
+struct dirent {
+       long d_ino;                      /* Always zero. */
+       char d_name[FILENAME_MAX];       /* File name. */
+       union {
+               unsigned short d_reclen; /* Always zero. */
+               unsigned char  d_type;   /* Reimplementation adds this */
+       };
+};
+
+DIR *opendir(const char *dirname);
+struct dirent *readdir(DIR *dir);
+int closedir(DIR *dir);
+
+#endif /* DIRENT_H */
index 7e74ebe59a5ea2e31a9100659805ada0f5d8e278..708a6c9bec5c04cd95861b77475e54445c0d24fc 100644 (file)
@@ -34,6 +34,9 @@
 
 #if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
 # define WIN32_NATIVE
+# if defined (_MSC_VER)
+#  define _WIN32_WINNT 0x0502
+# endif
 # include <winsock2.h>
 # include <windows.h>
 # include <io.h>
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..961497305777fcbd67001fdae1335fcfe2910e88 100644 (file)
@@ -27,7 +27,7 @@ VPATH = @srcdir@
 export exec_prefix mandir
 export srcdir VPATH
 
-ASCIIDOC8=@ASCIIDOC8@
+ASCIIDOC7=@ASCIIDOC7@
 NEEDS_SSL_WITH_CRYPTO=@NEEDS_SSL_WITH_CRYPTO@
 NO_OPENSSL=@NO_OPENSSL@
 NO_CURL=@NO_CURL@
@@ -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..5792425a4948148b5a110346f5b830063dd78914 100644 (file)
@@ -398,21 +398,21 @@ if test -n "$ASCIIDOC"; then
        AC_MSG_CHECKING([for asciidoc version])
        asciidoc_version=`$ASCIIDOC --version 2>/dev/null`
        case "${asciidoc_version}" in
-       asciidoc' '8*)
-               ASCIIDOC8=YesPlease
+       asciidoc' '7*)
+               ASCIIDOC7=YesPlease
                AC_MSG_RESULT([${asciidoc_version} > 7])
                ;;
-       asciidoc' '7*)
-               ASCIIDOC8=
+       asciidoc' '8*)
+               ASCIIDOC7=
                AC_MSG_RESULT([${asciidoc_version}])
                ;;
        *)
-               ASCIIDOC8=
+               ASCIIDOC7=
                AC_MSG_RESULT([${asciidoc_version} (unknown)])
                ;;
        esac
 fi
-AC_SUBST(ASCIIDOC8)
+AC_SUBST(ASCIIDOC7)
 
 
 ## Checks for libraries.
@@ -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 b5ef1ec55e2d6a97ab00d5692a93affb66803a31..0a438694351e555f66cbb3d24759d3a10a46ff20 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, "--find-renames=") ||
                 !strcmp(arg, "--find-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, "--find-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..38f3e3eb9770a9f3d85f175881bd2dd4ced6548b 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) {
@@ -965,6 +1033,12 @@ char *get_relative_cwd(char *buffer, int size, const char *dir)
        case '/':
                return cwd + 1;
        default:
+               /*
+                * dir can end with a path separator when it's root
+                * directory. Return proper prefix in that case.
+                */
+               if (dir[-1] == '/')
+                       return cwd;
                return NULL;
        }
 }
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
diff --git a/entry.c b/entry.c
index 004182c99d27a6a5825d2429f9104fc7a8f1dc80..b017167f2015623fb9c721e91d0a940abd1d5196 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -106,14 +106,14 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
        case S_IFLNK:
                new = read_blob_entry(ce, &size);
                if (!new)
-                       return error("git checkout-index: unable to read sha1 file of %s (%s)",
+                       return error("unable to read sha1 file of %s (%s)",
                                path, sha1_to_hex(ce->sha1));
 
                if (ce_mode_s_ifmt == S_IFLNK && has_symlinks && !to_tempfile) {
                        ret = symlink(new, path);
                        free(new);
                        if (ret)
-                               return error("git checkout-index: unable to create symlink %s (%s)",
+                               return error("unable to create symlink %s (%s)",
                                             path, strerror(errno));
                        break;
                }
@@ -141,7 +141,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
                }
                if (fd < 0) {
                        free(new);
-                       return error("git checkout-index: unable to create file %s (%s)",
+                       return error("unable to create file %s (%s)",
                                path, strerror(errno));
                }
 
@@ -155,16 +155,16 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
                close(fd);
                free(new);
                if (wrote != size)
-                       return error("git checkout-index: unable to write file %s", path);
+                       return error("unable to write file %s", path);
                break;
        case S_IFGITLINK:
                if (to_tempfile)
-                       return error("git checkout-index: cannot create temporary subproject %s", path);
+                       return error("cannot create temporary subproject %s", path);
                if (mkdir(path, 0777) < 0)
-                       return error("git checkout-index: cannot create subproject directory %s", path);
+                       return error("cannot create subproject directory %s", path);
                break;
        default:
-               return error("git checkout-index: unknown file mode for %s", path);
+               return error("unknown file mode for %s in index", path);
        }
 
        if (state->refresh_cache) {
@@ -211,7 +211,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t
                        return 0;
                if (!state->force) {
                        if (!state->quiet)
-                               fprintf(stderr, "git-checkout-index: %s already exists\n", path);
+                               fprintf(stderr, "%s already exists, no checkout\n", path);
                        return -1;
                }
 
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..785776086ccc22dcd5488e3ac0bad3318f57043e 100644 (file)
@@ -132,14 +132,17 @@ Format of STDIN stream:
   ts    ::= # time since the epoch in seconds, ascii base10 notation;
   tz    ::= # GIT style timezone;
 
-     # note: comments may appear anywhere in the input, except
-     # within a data command.  Any form of the data command
-     # always escapes the related input from comment processing.
+     # note: comments and cat requests may appear anywhere
+     # in the input, except within a data command.  Any form
+     # of the data command always escapes the related input
+     # from comment processing.
      #
      # In case it is not clear, the '#' that starts the comment
      # must be the first character on that line (an lf
      # preceded it).
      #
+  cat_blob ::= 'cat-blob' sp (hexsha1 | idnum) lf;
+
   comment ::= '#' not_lf* lf;
   not_lf  ::= # Any byte that is not ASCII newline (LF);
 */
@@ -156,6 +159,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)
@@ -361,7 +365,14 @@ static uintmax_t next_mark;
 static struct strbuf new_data = STRBUF_INIT;
 static int seen_data_command;
 
+/* Signal handling */
+static volatile sig_atomic_t checkpoint_requested;
+
+/* Where to write output of cat-blob commands */
+static int cat_blob_fd = STDOUT_FILENO;
+
 static void parse_argv(void);
+static void parse_cat_blob(void);
 
 static void write_branch_report(FILE *rpt, struct branch *b)
 {
@@ -500,6 +511,32 @@ static NORETURN void die_nicely(const char *err, va_list params)
        exit(128);
 }
 
+#ifndef SIGUSR1        /* Windows, for example */
+
+static void set_checkpoint_signal(void)
+{
+}
+
+#else
+
+static void checkpoint_signal(int signo)
+{
+       checkpoint_requested = 1;
+}
+
+static void set_checkpoint_signal(void)
+{
+       struct sigaction sa;
+
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_handler = checkpoint_signal;
+       sigemptyset(&sa.sa_mask);
+       sa.sa_flags = SA_RESTART;
+       sigaction(SIGUSR1, &sa, NULL);
+}
+
+#endif
+
 static void alloc_objects(unsigned int cnt)
 {
        struct object_entry_pool *b;
@@ -539,22 +576,17 @@ static struct object_entry *insert_object(unsigned char *sha1)
 {
        unsigned int h = sha1[0] << 8 | sha1[1];
        struct object_entry *e = object_table[h];
-       struct object_entry *p = NULL;
 
        while (e) {
                if (!hashcmp(sha1, e->idx.sha1))
                        return e;
-               p = e;
                e = e->next;
        }
 
        e = new_object(sha1);
-       e->next = NULL;
+       e->next = object_table[h];
        e->idx.offset = 0;
-       if (p)
-               p->next = e;
-       else
-               object_table[h] = e;
+       object_table[h] = e;
        return e;
 }
 
@@ -1437,6 +1469,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 +1490,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 +1500,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 +1563,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 +1574,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 +1624,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 +1635,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))
@@ -1790,7 +1836,7 @@ static int read_next_command(void)
                return EOF;
        }
 
-       do {
+       for (;;) {
                if (unread_command_buf) {
                        unread_command_buf = 0;
                } else {
@@ -1823,9 +1869,14 @@ static int read_next_command(void)
                        rc->prev->next = rc;
                        cmd_tail = rc;
                }
-       } while (command_buf.buf[0] == '#');
-
-       return 0;
+               if (!prefixcmp(command_buf.buf, "cat-blob ")) {
+                       parse_cat_blob();
+                       continue;
+               }
+               if (command_buf.buf[0] == '#')
+                       continue;
+               return 0;
+       }
 }
 
 static void skip_optional_lf(void)
@@ -2218,6 +2269,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 +2331,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,
@@ -2689,14 +2751,95 @@ static void parse_reset_branch(void)
                unread_command_buf = 1;
 }
 
-static void parse_checkpoint(void)
+static void cat_blob_write(const char *buf, unsigned long size)
 {
+       if (write_in_full(cat_blob_fd, buf, size) != size)
+               die_errno("Write to frontend failed");
+}
+
+static void cat_blob(struct object_entry *oe, unsigned char sha1[20])
+{
+       struct strbuf line = STRBUF_INIT;
+       unsigned long size;
+       enum object_type type = 0;
+       char *buf;
+
+       if (!oe || oe->pack_id == MAX_PACK_ID) {
+               buf = read_sha1_file(sha1, &type, &size);
+       } else {
+               type = oe->type;
+               buf = gfi_unpack_entry(oe, &size);
+       }
+
+       /*
+        * Output based on batch_one_object() from cat-file.c.
+        */
+       if (type <= 0) {
+               strbuf_reset(&line);
+               strbuf_addf(&line, "%s missing\n", sha1_to_hex(sha1));
+               cat_blob_write(line.buf, line.len);
+               strbuf_release(&line);
+               free(buf);
+               return;
+       }
+       if (!buf)
+               die("Can't read object %s", sha1_to_hex(sha1));
+       if (type != OBJ_BLOB)
+               die("Object %s is a %s but a blob was expected.",
+                   sha1_to_hex(sha1), typename(type));
+       strbuf_reset(&line);
+       strbuf_addf(&line, "%s %s %lu\n", sha1_to_hex(sha1),
+                                               typename(type), size);
+       cat_blob_write(line.buf, line.len);
+       strbuf_release(&line);
+       cat_blob_write(buf, size);
+       cat_blob_write("\n", 1);
+       free(buf);
+}
+
+static void parse_cat_blob(void)
+{
+       const char *p;
+       struct object_entry *oe = oe;
+       unsigned char sha1[20];
+
+       /* cat-blob SP <object> LF */
+       p = command_buf.buf + strlen("cat-blob ");
+       if (*p == ':') {
+               char *x;
+               oe = find_mark(strtoumax(p + 1, &x, 10));
+               if (x == p + 1)
+                       die("Invalid mark: %s", command_buf.buf);
+               if (!oe)
+                       die("Unknown mark: %s", command_buf.buf);
+               if (*x)
+                       die("Garbage after mark: %s", command_buf.buf);
+               hashcpy(sha1, oe->idx.sha1);
+       } else {
+               if (get_sha1_hex(p, sha1))
+                       die("Invalid SHA1: %s", command_buf.buf);
+               if (p[40])
+                       die("Garbage after SHA1: %s", command_buf.buf);
+               oe = find_object(sha1);
+       }
+
+       cat_blob(oe, sha1);
+}
+
+static void checkpoint(void)
+{
+       checkpoint_requested = 0;
        if (object_count) {
                cycle_packfile();
                dump_branches();
                dump_tags();
                dump_marks();
        }
+}
+
+static void parse_checkpoint(void)
+{
+       checkpoint_requested = 1;
        skip_optional_lf();
 }
 
@@ -2746,16 +2889,25 @@ static void option_date_format(const char *fmt)
                die("unknown --date-format argument %s", fmt);
 }
 
+static unsigned long ulong_arg(const char *option, const char *arg)
+{
+       char *endptr;
+       unsigned long rv = strtoul(arg, &endptr, 0);
+       if (strchr(arg, '-') || endptr == arg || *endptr)
+               die("%s: argument must be a non-negative integer", option);
+       return rv;
+}
+
 static void option_depth(const char *depth)
 {
-       max_depth = strtoul(depth, NULL, 0);
+       max_depth = ulong_arg("--depth", depth);
        if (max_depth > MAX_DEPTH)
                die("--depth cannot exceed %u", MAX_DEPTH);
 }
 
 static void option_active_branches(const char *branches)
 {
-       max_active_branches = strtoul(branches, NULL, 0);
+       max_active_branches = ulong_arg("--active-branches", branches);
 }
 
 static void option_export_marks(const char *marks)
@@ -2764,6 +2916,14 @@ static void option_export_marks(const char *marks)
        safe_create_leading_directories_const(export_marks_file);
 }
 
+static void option_cat_blob_fd(const char *fd)
+{
+       unsigned long n = ulong_arg("--cat-blob-fd", fd);
+       if (n > (unsigned long) INT_MAX)
+               die("--cat-blob-fd cannot exceed %d", INT_MAX);
+       cat_blob_fd = (int) n;
+}
+
 static void option_export_pack_edges(const char *edges)
 {
        if (pack_edges)
@@ -2817,6 +2977,8 @@ static int parse_one_feature(const char *feature, int from_stream)
                option_import_marks(feature + 13, from_stream);
        } else if (!prefixcmp(feature, "export-marks=")) {
                option_export_marks(feature + 13);
+       } else if (!strcmp(feature, "cat-blob")) {
+               ; /* Don't die - this feature is supported */
        } else if (!prefixcmp(feature, "relative-marks")) {
                relative_marks_paths = 1;
        } else if (!prefixcmp(feature, "no-relative-marks")) {
@@ -2911,6 +3073,11 @@ static void parse_argv(void)
                if (parse_one_feature(a + 2, 0))
                        continue;
 
+               if (!prefixcmp(a + 2, "cat-blob-fd=")) {
+                       option_cat_blob_fd(a + 2 + strlen("cat-blob-fd="));
+                       continue;
+               }
+
                die("unknown option %s", a);
        }
        if (i != global_argc)
@@ -2953,6 +3120,7 @@ int main(int argc, const char **argv)
        prepare_packed_git();
        start_packfile();
        set_die_routine(die_nicely);
+       set_checkpoint_signal();
        while (read_next_command() != EOF) {
                if (!strcmp("blob", command_buf.buf))
                        parse_new_blob();
@@ -2974,6 +3142,9 @@ int main(int argc, const char **argv)
                        /* ignore non-git options*/;
                else
                        die("Unsupported command: %s", command_buf.buf);
+
+               if (checkpoint_requested)
+                       checkpoint();
        }
 
        /* argv hasn't been parsed yet, do so */
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 5f47b18141a0758c3d0dae2cfe764495c92139c5..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)
            ;;
@@ -89,7 +89,13 @@ get_remote_merge_branch () {
            refs/heads/*) remote=${remote#refs/heads/} ;;
            refs/* | tags/* | remotes/* ) remote=
            esac
-
-           [ -n "$remote" ] && echo "refs/remotes/$repo/$remote"
+           [ -n "$remote" ] && case "$repo" in
+               .)
+                   echo "refs/heads/$remote"
+                   ;;
+               *)
+                   echo "refs/remotes/$repo/$remote"
+                   ;;
+           esac
        esac
 }
index 8eb74d45debe7ab2bd2cc9b5ed57b1ba49d94bdb..eb87f49062b70d707adebca4013809822123608b 100755 (executable)
@@ -38,7 +38,7 @@ test -z "$(git ls-files -u)" || die_conflict
 test -f "$GIT_DIR/MERGE_HEAD" && die_merge
 
 strategy_args= diffstat= no_commit= squash= no_ff= ff_only=
-log_arg= verbosity= progress=
+log_arg= verbosity= progress= recurse_submodules=
 merge_args=
 curr_branch=$(git symbolic-ref -q HEAD)
 curr_branch_short="${curr_branch#refs/heads/}"
@@ -105,6 +105,12 @@ do
        --no-r|--no-re|--no-reb|--no-reba|--no-rebas|--no-rebase)
                rebase=false
                ;;
+       --recurse-submodules)
+               recurse_submodules=--recurse-submodules
+               ;;
+       --no-recurse-submodules)
+               recurse_submodules=--no-recurse-submodules
+               ;;
        --d|--dr|--dry|--dry-|--dry-r|--dry-ru|--dry-run)
                dry_run=--dry-run
                ;;
@@ -201,10 +207,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 &&
@@ -220,7 +223,7 @@ test true = "$rebase" && {
        done
 }
 orig_head=$(git rev-parse -q --verify HEAD)
-git fetch $verbosity $progress $dry_run --update-head-ok "$@" || exit 1
+git fetch $verbosity $progress $dry_run $recurse_submodules --update-head-ok "$@" || exit 1
 test -z "$dry_run" || exit 0
 
 curr_head=$(git rev-parse -q --verify HEAD)
index c2383bfed58f0d28d35b0a657a02b273d06d8b27..a5ffd9a31eea0f361774d77867c83f444847279b 100755 (executable)
@@ -28,6 +28,7 @@ continue           continue rebasing process
 abort              abort rebasing process and restore original branch
 skip               skip current patch and continue rebasing process
 no-verify          override pre-rebase hook from stopping the operation
+verify             allow pre-rebase hook to run
 root               rebase all reachable commmits up to the root(s)
 autosquash         move commits that begin with squash!/fixup! under -i
 "
@@ -153,14 +154,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 +550,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
@@ -757,6 +750,7 @@ do
                OK_TO_SKIP_PRE_REBASE=yes
                ;;
        --verify)
+               OK_TO_SKIP_PRE_REBASE=
                ;;
        --continue)
                is_standalone "$@" || usage
@@ -798,7 +792,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 +890,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..d8e190302668ca352fd58cd052a677347a29cdcd 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=
@@ -205,6 +206,9 @@ do
        --no-verify)
                OK_TO_SKIP_PRE_REBASE=yes
                ;;
+       --verify)
+               OK_TO_SKIP_PRE_REBASE=
+               ;;
        --continue)
                test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply ||
                        die "No rebase in progress?"
@@ -274,15 +278,16 @@ do
                        die "No rebase in progress?"
 
                git rerere clear
-               if test -d "$dotest"
-               then
-                       GIT_QUIET=$(cat "$dotest/quiet")
-                       move_to_original_branch
-               else
-                       dotest="$GIT_DIR"/rebase-apply
-                       GIT_QUIET=$(cat "$dotest/quiet")
-                       move_to_original_branch
-               fi
+
+               test -d "$dotest" || dotest="$GIT_DIR"/rebase-apply
+
+               head_name="$(cat "$dotest"/head-name)" &&
+               case "$head_name" in
+               refs/*)
+                       git symbolic-ref HEAD $head_name ||
+                       die "Could not move back to $head_name"
+                       ;;
+               esac
                git reset --hard $(cat "$dotest/orig-head")
                rm -r "$dotest"
                exit
@@ -412,19 +417,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 33bc41f069d9807285db6630132e14f8c518ce47..c21b77aee54cd045b7bb64ae7337387569bbf65a 100755 (executable)
@@ -93,20 +93,6 @@ module_clone()
        url=$2
        reference="$3"
 
-       # If there already is a directory at the submodule path,
-       # expect it to be empty (since that is the default checkout
-       # action) and try to remove it.
-       # Note: if $path is a symlink to a directory the test will
-       # succeed but the rmdir will fail. We might want to fix this.
-       if test -d "$path"
-       then
-               rmdir "$path" 2>/dev/null ||
-               die "Directory '$path' exists, but is neither empty nor a git repository"
-       fi
-
-       test -e "$path" &&
-       die "A file already exist at path '$path'"
-
        if test -n "$reference"
        then
                git-clone "$reference" -n "$url" "$path"
@@ -241,7 +227,7 @@ cmd_add()
                        # ash fails to wordsplit ${branch:+-b "$branch"...}
                        case "$branch" in
                        '') git checkout -f -q ;;
-                       ?*) git checkout -f -q -b "$branch" "origin/$branch" ;;
+                       ?*) git checkout -f -q -B "$branch" "origin/$branch" ;;
                        esac
                ) || die "Unable to checkout submodule '$path'"
        fi
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;
index 3fc4166b25714911b6b1294c7439da1e237e4918..e9de241dd004a9f5d6d6d8e6a300a1fa5949a46d 100755 (executable)
@@ -31,154 +31,161 @@ valid_custom_tool()
 
 valid_tool() {
        case "$1" in
-               firefox | iceweasel | chrome | google-chrome | chromium | konqueror | w3m | links | lynx | dillo | open | start)
-                       ;; # happy
-               *)
-                       valid_custom_tool "$1" || return 1
-                       ;;
+       firefox | iceweasel | seamonkey | iceape | \
+       chrome | google-chrome | chromium | chromium-browser |\
+       konqueror | opera | w3m | elinks | links | lynx | dillo | open | start)
+               ;; # happy
+       *)
+               valid_custom_tool "$1" || return 1
+               ;;
        esac
 }
 
 init_browser_path() {
        browser_path=$(git config "browser.$1.path")
-       test -z "$browser_path" && browser_path="$1"
+       if test -z "$browser_path" &&
+          test "$1" = chromium &&
+          type chromium-browser >/dev/null 2>&1
+       then
+               browser_path=chromium-browser
+       fi
+       : ${browser_path:="$1"}
 }
 
 while test $# != 0
 do
-    case "$1" in
+       case "$1" in
        -b|--browser*|-t|--tool*)
-           case "$#,$1" in
+               case "$#,$1" in
                *,*=*)
-                   browser=`expr "z$1" : 'z-[^=]*=\(.*\)'`
-                   ;;
+                       browser=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+                       ;;
                1,*)
-                   usage ;;
+                       usage ;;
                *)
-                   browser="$2"
-                   shift ;;
-           esac
-           ;;
+                       browser="$2"
+                       shift ;;
+               esac
+               ;;
        -c|--config*)
-           case "$#,$1" in
+               case "$#,$1" in
                *,*=*)
-                   conf=`expr "z$1" : 'z-[^=]*=\(.*\)'`
-                   ;;
+                       conf=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+                       ;;
                1,*)
-                   usage ;;
+                       usage ;;
                *)
-                   conf="$2"
-                   shift ;;
-           esac
-           ;;
+                       conf="$2"
+                       shift ;;
+               esac
+               ;;
        --)
-           break
-           ;;
+               break
+               ;;
        -*)
-           usage
-           ;;
+               usage
+               ;;
        *)
-           break
-           ;;
-    esac
-    shift
+               break
+               ;;
+       esac
+       shift
 done
 
 test $# = 0 && usage
 
 if test -z "$browser"
 then
-    for opt in "$conf" "web.browser"
-    do
-       test -z "$opt" && continue
-       browser="`git config $opt`"
-       test -z "$browser" || break
-    done
-    if test -n "$browser" && ! valid_tool "$browser"; then
-       echo >&2 "git config option $opt set to unknown browser: $browser"
-       echo >&2 "Resetting to default..."
-       unset browser
-    fi
+       for opt in "$conf" "web.browser"
+       do
+               test -z "$opt" && continue
+               browser="`git config $opt`"
+               test -z "$browser" || break
+       done
+       if test -n "$browser" && ! valid_tool "$browser"; then
+               echo >&2 "git config option $opt set to unknown browser: $browser"
+               echo >&2 "Resetting to default..."
+               unset browser
+       fi
 fi
 
 if test -z "$browser" ; then
-    if test -n "$DISPLAY"; then
-       browser_candidates="firefox iceweasel google-chrome chrome chromium konqueror w3m links lynx dillo"
-       if test "$KDE_FULL_SESSION" = "true"; then
-           browser_candidates="konqueror $browser_candidates"
+       if test -n "$DISPLAY"; then
+               browser_candidates="firefox iceweasel google-chrome chrome chromium chromium-browser konqueror opera seamonkey iceape w3m elinks links lynx dillo"
+               if test "$KDE_FULL_SESSION" = "true"; then
+                       browser_candidates="konqueror $browser_candidates"
+               fi
+       else
+               browser_candidates="w3m elinks links lynx"
        fi
-    else
-       browser_candidates="w3m links lynx"
-    fi
-    # SECURITYSESSIONID indicates an OS X GUI login session
-    if test -n "$SECURITYSESSIONID" \
-           -o "$TERM_PROGRAM" = "Apple_Terminal" ; then
-       browser_candidates="open $browser_candidates"
-    fi
-    # /bin/start indicates MinGW
-    if test -x /bin/start; then
-       browser_candidates="start $browser_candidates"
-    fi
-
-    for i in $browser_candidates; do
-       init_browser_path $i
-       if type "$browser_path" > /dev/null 2>&1; then
-           browser=$i
-           break
+       # SECURITYSESSIONID indicates an OS X GUI login session
+       if test -n "$SECURITYSESSIONID" \
+               -o "$TERM_PROGRAM" = "Apple_Terminal" ; then
+               browser_candidates="open $browser_candidates"
        fi
-    done
-    test -z "$browser" && die "No known browser available."
+       # /bin/start indicates MinGW
+       if test -x /bin/start; then
+               browser_candidates="start $browser_candidates"
+       fi
+
+       for i in $browser_candidates; do
+               init_browser_path $i
+               if type "$browser_path" > /dev/null 2>&1; then
+                       browser=$i
+                       break
+               fi
+       done
+       test -z "$browser" && die "No known browser available."
 else
-    valid_tool "$browser" || die "Unknown browser '$browser'."
+       valid_tool "$browser" || die "Unknown browser '$browser'."
 
-    init_browser_path "$browser"
+       init_browser_path "$browser"
 
-    if test -z "$browser_cmd" && ! type "$browser_path" > /dev/null 2>&1; then
-       die "The browser $browser is not available as '$browser_path'."
-    fi
+       if test -z "$browser_cmd" && ! type "$browser_path" > /dev/null 2>&1; then
+               die "The browser $browser is not available as '$browser_path'."
+       fi
 fi
 
 case "$browser" in
-    firefox|iceweasel)
+firefox|iceweasel|seamonkey|iceape)
        # Check version because firefox < 2.0 does not support "-new-tab".
        vers=$(expr "$($browser_path -version)" : '.* \([0-9][0-9]*\)\..*')
        NEWTAB='-new-tab'
        test "$vers" -lt 2 && NEWTAB=''
        "$browser_path" $NEWTAB "$@" &
        ;;
-    google-chrome|chrome|chromium)
-       # Actual command for chromium is chromium-browser.
+google-chrome|chrome|chromium|chromium-browser)
        # No need to specify newTab. It's default in chromium
        eval "$browser_path" "$@" &
        ;;
-    konqueror)
+konqueror)
        case "$(basename "$browser_path")" in
-           konqueror)
+       konqueror)
                # It's simpler to use kfmclient to open a new tab in konqueror.
                browser_path="$(echo "$browser_path" | sed -e 's/konqueror$/kfmclient/')"
                type "$browser_path" > /dev/null 2>&1 || die "No '$browser_path' found."
                eval "$browser_path" newTab "$@"
                ;;
-           kfmclient)
+       kfmclient)
                eval "$browser_path" newTab "$@"
                ;;
-           *)
+       *)
                "$browser_path" "$@" &
                ;;
        esac
        ;;
-    w3m|links|lynx|open)
+w3m|elinks|links|lynx|open)
        eval "$browser_path" "$@"
        ;;
-    start)
-        exec "$browser_path" '"web-browse"' "$@"
-        ;;
-    dillo)
+start)
+       exec "$browser_path" '"web-browse"' "$@"
+       ;;
+opera|dillo)
        "$browser_path" "$@" &
        ;;
-    *)
+*)
        if test -n "$browser_cmd"; then
-           ( eval $browser_cmd "$@" )
+               ( eval $browser_cmd "$@" )
        fi
        ;;
 esac
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 1b0e09a561e01c51bdc6dbced052e0c753924618..e82c6bfedea5fb9a3d768c0fa1759346c6e5e603 100644 (file)
@@ -131,6 +131,7 @@ proc unmerged_files {files} {
 
 proc parseviewargs {n arglist} {
     global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs env
+    global worddiff git_version
 
     set vdatemode($n) 0
     set vmergeonly($n) 0
@@ -168,7 +169,7 @@ proc parseviewargs {n arglist} {
                lappend diffargs $arg
            }
            "--raw" - "--patch-with-raw" - "--patch-with-stat" -
-           "--name-only" - "--name-status" - "--color" - "--color-words" -
+           "--name-only" - "--name-status" - "--color" -
            "--log-size" - "--pretty=*" - "--decorate" - "--abbrev-commit" -
            "--cc" - "-z" - "--header" - "--parents" - "--boundary" -
            "--no-color" - "-g" - "--walk-reflogs" - "--no-walk" -
@@ -177,6 +178,18 @@ proc parseviewargs {n arglist} {
                # These cause our parsing of git log's output to fail, or else
                # they're options we want to set ourselves, so ignore them.
            }
+           "--color-words*" - "--word-diff=color" {
+               # These trigger a word diff in the console interface,
+               # so help the user by enabling our own support
+               if {[package vcompare $git_version "1.7.2"] >= 0} {
+                   set worddiff [mc "Color words"]
+               }
+           }
+           "--word-diff*" {
+               if {[package vcompare $git_version "1.7.2"] >= 0} {
+                   set worddiff [mc "Markup words"]
+               }
+           }
            "--stat=*" - "--numstat" - "--shortstat" - "--summary" -
            "--check" - "--exit-code" - "--quiet" - "--topo-order" -
            "--full-history" - "--dense" - "--sparse" -
@@ -313,6 +326,7 @@ proc start_rev_list {view} {
     global viewactive viewinstances vmergeonly
     global mainheadid viewmainheadid viewmainheadid_orig
     global vcanopt vflags vrevs vorigargs
+    global show_notes
 
     set startmsecs [clock clicks -milliseconds]
     set commitidx($view) 0
@@ -361,8 +375,8 @@ proc start_rev_list {view} {
     }
 
     if {[catch {
-       set fd [open [concat | git log --no-color -z --pretty=raw --parents \
-                        --boundary $args "--" $files] r]
+       set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \
+                       --parents --boundary $args "--" $files] r]
     } err]} {
        error_popup "[mc "Error executing git log:"] $err"
        return 0
@@ -456,6 +470,7 @@ proc updatecommits {} {
     global mainheadid viewmainheadid viewmainheadid_orig pending_select
     global isworktree
     global varcid vposids vnegids vflags vrevs
+    global show_notes
 
     set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
     rereadrefs
@@ -508,8 +523,8 @@ proc updatecommits {} {
        set args $vorigargs($view)
     }
     if {[catch {
-       set fd [open [concat | git log --no-color -z --pretty=raw --parents \
-                         --boundary $args "--" $vfilelimit($view)] r]
+       set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \
+                       --parents --boundary $args "--" $vfilelimit($view)] r]
     } err]} {
        error_popup "[mc "Error executing git log:"] $err"
        return
@@ -1970,6 +1985,8 @@ proc makewindow {} {
     global fprogitem fprogcoord lastprogupdate progupdatepending
     global rprogitem rprogcoord rownumsel numcommits
     global have_tk85 use_ttk NS
+    global git_version
+    global worddiff
 
     # The "mc" arguments here are purely so that xgettext
     # sees the following string as needing to be translated
@@ -2243,6 +2260,15 @@ proc makewindow {} {
     ${NS}::checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
        -command changeignorespace -variable ignorespace
     pack .bleft.mid.ignspace -side left -padx 5
+
+    set worddiff [mc "Line diff"]
+    if {[package vcompare $git_version "1.7.2"] >= 0} {
+       makedroplist .bleft.mid.worddiff worddiff [mc "Line diff"] \
+           [mc "Markup words"] [mc "Color words"]
+       trace add variable worddiff write changeworddiff
+       pack .bleft.mid.worddiff -side left -padx 5
+    }
+
     set ctext .bleft.bottom.ctext
     text $ctext -background $bgcolor -foreground $fgcolor \
        -state disabled -font textfont \
@@ -2451,6 +2477,7 @@ proc makewindow {} {
     global ctxbut
     bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y}
     bind $ctext $ctxbut {pop_diff_menu %W %X %Y %x %y}
+    bind $ctext <Button-1> {focus %W}
 
     set maincursor [. cget -cursor]
     set textcursor [$ctext cget -cursor]
@@ -7301,6 +7328,7 @@ proc getblobline {bf id} {
                            [lindex [split $commentend .] 0]}]
            mark_ctext_line $lnum
        }
+       $ctext config -state disabled
        return 0
     }
     $ctext config -state disabled
@@ -7502,11 +7530,16 @@ proc changeignorespace {} {
     reselectline
 }
 
+proc changeworddiff {name ix op} {
+    reselectline
+}
+
 proc getblobdiffs {ids} {
     global blobdifffd diffids env
     global diffinhdr treediffs
     global diffcontext
     global ignorespace
+    global worddiff
     global limitdiffs vfilelimit curview
     global diffencoding targetline diffnparents
     global git_version currdiffsubmod
@@ -7523,6 +7556,9 @@ proc getblobdiffs {ids} {
     if {$ignorespace} {
        append cmd " -w"
     }
+    if {$worddiff ne [mc "Line diff"]} {
+       append cmd " --word-diff=porcelain"
+    }
     if {$limitdiffs && $vfilelimit($curview) ne {}} {
        set cmd [concat $cmd -- $vfilelimit($curview)]
     }
@@ -7608,6 +7644,7 @@ proc getblobdiffline {bdf ids} {
     global ctext_file_names ctext_file_lines
     global diffinhdr treediffs mergemax diffnparents
     global diffencoding jump_to_here targetline diffline currdiffsubmod
+    global worddiff
 
     set nr 0
     $ctext conf -state normal
@@ -7747,15 +7784,28 @@ proc getblobdiffline {bdf ids} {
            # parse the prefix - one ' ', '-' or '+' for each parent
            set prefix [string range $line 0 [expr {$diffnparents - 1}]]
            set tag [expr {$diffnparents > 1? "m": "d"}]
+           set dowords [expr {$worddiff ne [mc "Line diff"] && $diffnparents == 1}]
+           set words_pre_markup ""
+           set words_post_markup ""
            if {[string trim $prefix " -+"] eq {}} {
                # prefix only has " ", "-" and "+" in it: normal diff line
                set num [string first "-" $prefix]
+               if {$dowords} {
+                   set line [string range $line 1 end]
+               }
                if {$num >= 0} {
                    # removed line, first parent with line is $num
                    if {$num >= $mergemax} {
                        set num "max"
                    }
-                   $ctext insert end "$line\n" $tag$num
+                   if {$dowords && $worddiff eq [mc "Markup words"]} {
+                       $ctext insert end "\[-$line-\]" $tag$num
+                   } else {
+                       $ctext insert end "$line" $tag$num
+                   }
+                   if {!$dowords} {
+                       $ctext insert end "\n" $tag$num
+                   }
                } else {
                    set tags {}
                    if {[string first "+" $prefix] >= 0} {
@@ -7770,6 +7820,8 @@ proc getblobdiffline {bdf ids} {
                                lappend tags m$num
                            }
                        }
+                       set words_pre_markup "{+"
+                       set words_post_markup "+}"
                    }
                    if {$targetline ne {}} {
                        if {$diffline == $targetline} {
@@ -7779,8 +7831,17 @@ proc getblobdiffline {bdf ids} {
                            incr diffline
                        }
                    }
-                   $ctext insert end "$line\n" $tags
+                   if {$dowords && $worddiff eq [mc "Markup words"]} {
+                       $ctext insert end "$words_pre_markup$line$words_post_markup" $tags
+                   } else {
+                       $ctext insert end "$line" $tags
+                   }
+                   if {!$dowords} {
+                       $ctext insert end "\n" $tags
+                   }
                }
+           } elseif {$dowords && $prefix eq "~"} {
+               $ctext insert end "\n" {}
            } else {
                # "\ No newline at end of file",
                # or something else we don't recognize
@@ -11391,6 +11452,7 @@ if {[tk windowingsystem] eq "win32"} {
 set diffcolors {red "#00a000" blue}
 set diffcontext 3
 set ignorespace 0
+set worddiff ""
 set markbgcolor "#e0e0ff"
 
 set circlecolors {white blue gray blue blue}
@@ -11521,6 +11583,11 @@ set NS [expr {$use_ttk ? "ttk" : ""}]
 
 set git_version [join [lrange [split [lindex [exec git version] end] .] 0 2] .]
 
+set show_notes {}
+if {[package vcompare $git_version "1.6.6.2"] >= 0} {
+    set show_notes "--show-notes"
+}
+
 set runq {}
 set history {}
 set historyindex 0
diff --git a/gitk-git/po/pt_br.po b/gitk-git/po/pt_br.po
new file mode 100644 (file)
index 0000000..1486e32
--- /dev/null
@@ -0,0 +1,1277 @@
+# Translation of gitk to Brazilian Portuguese.
+# Copyright (C) 2007 Paul Mackerras, et al.
+# This file is distributed under the same license as the gitk package.
+#
+# Alexandre Erwin Ittner <alexandre@ittner.com.br>, 2010.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: gitk\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-01-26 15:47-0800\n"
+"PO-Revision-Date: 2010-12-06 23:39-0200\n"
+"Last-Translator: Alexandre Erwin Ittner <alexandre@ittner.com.br>\n"
+"Language-Team: Brazilian Portuguese <>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: gitk:115
+msgid "Couldn't get list of unmerged files:"
+msgstr "Não foi possível obter a lista dos arquivos não mesclados:"
+
+#: gitk:274
+msgid "Error parsing revisions:"
+msgstr "Erro ao interpretar revisões:"
+
+#: gitk:330
+msgid "Error executing --argscmd command:"
+msgstr "Erro ao executar o comando--argscmd:"
+
+#: gitk:343
+msgid "No files selected: --merge specified but no files are unmerged."
+msgstr ""
+"Nenhum arquivo foi selecionado: --merge especificado mas não há arquivos não-"
+"mesclados."
+
+#: gitk:346
+msgid ""
+"No files selected: --merge specified but no unmerged files are within file "
+"limit."
+msgstr ""
+"Nenhum arquivo foi selecionado: --merge especificado mas não há arquivos não-"
+"mesclados dentro dos limites."
+
+#: gitk:368 gitk:516
+msgid "Error executing git log:"
+msgstr "Erro ao executar git log:"
+
+#: gitk:386 gitk:532
+msgid "Reading"
+msgstr "Lendo"
+
+#: gitk:446 gitk:4271
+msgid "Reading commits..."
+msgstr "Lendo revisões..."
+
+#: gitk:449 gitk:1580 gitk:4274
+msgid "No commits selected"
+msgstr "Nenhuma revisão foi selecionada"
+
+#: gitk:1456
+msgid "Can't parse git log output:"
+msgstr "Não foi possível interpretar a saída do \"git log\":"
+
+#: gitk:1676
+msgid "No commit information available"
+msgstr "Não há informações disponíveis sobre a revisão"
+
+#: gitk:1818
+msgid "mc"
+msgstr "mc"
+
+#: gitk:1853 gitk:4064 gitk:9067 gitk:10607 gitk:10817
+msgid "OK"
+msgstr "Ok"
+
+#: gitk:1855 gitk:4066 gitk:8657 gitk:8736 gitk:8851 gitk:8900 gitk:9069
+#: gitk:10608 gitk:10818
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: gitk:1980
+msgid "Update"
+msgstr "Atualizar"
+
+#: gitk:1981
+msgid "Reload"
+msgstr "Recarregar"
+
+#: gitk:1982
+msgid "Reread references"
+msgstr "Ler as referências novamente"
+
+#: gitk:1983
+msgid "List references"
+msgstr "Listar referências"
+
+#: gitk:1985
+msgid "Start git gui"
+msgstr "Iniciar Git GUI"
+
+#: gitk:1987
+msgid "Quit"
+msgstr "Sair"
+
+#: gitk:1979
+msgid "File"
+msgstr "Arquivo"
+
+#: gitk:1991
+msgid "Preferences"
+msgstr "Preferências"
+
+#: gitk:1990
+msgid "Edit"
+msgstr "Editar"
+
+#: gitk:1995
+msgid "New view..."
+msgstr "Nova vista..."
+
+#: gitk:1996
+msgid "Edit view..."
+msgstr "Editar vista..."
+
+#: gitk:1997
+msgid "Delete view"
+msgstr "Apagar vista"
+
+#: gitk:1999
+msgid "All files"
+msgstr "Todos os arquivos"
+
+#: gitk:1994 gitk:3817
+msgid "View"
+msgstr "Exibir"
+
+#: gitk:2004 gitk:2014 gitk:2787
+msgid "About gitk"
+msgstr "Sobre o gitk"
+
+#: gitk:2005 gitk:2019
+msgid "Key bindings"
+msgstr "Atalhos de teclado"
+
+#: gitk:2003 gitk:2018
+msgid "Help"
+msgstr "Ajuda"
+
+#: gitk:2096 gitk:8132
+msgid "SHA1 ID:"
+msgstr "SHA1 ID:"
+
+#: gitk:2127
+msgid "Row"
+msgstr "Linha"
+
+#: gitk:2165
+msgid "Find"
+msgstr "Encontrar"
+
+#: gitk:2166
+msgid "next"
+msgstr "Próximo"
+
+#: gitk:2167
+msgid "prev"
+msgstr "Anterior"
+
+#: gitk:2168
+msgid "commit"
+msgstr "Revisão"
+
+#: gitk:2171 gitk:2173 gitk:4432 gitk:4455 gitk:4479 gitk:6420 gitk:6492
+#: gitk:6576
+msgid "containing:"
+msgstr "contendo:"
+
+#: gitk:2174 gitk:3298 gitk:3303 gitk:4507
+msgid "touching paths:"
+msgstr "envolvendo os caminhos:"
+
+#: gitk:2175 gitk:4512
+msgid "adding/removing string:"
+msgstr "Adicionando/removendo texto:"
+
+#: gitk:2184 gitk:2186
+msgid "Exact"
+msgstr "Exatamente"
+
+#: gitk:2186 gitk:4587 gitk:6388
+msgid "IgnCase"
+msgstr "Ignorar maiúsculas/minúsculas"
+
+#: gitk:2186 gitk:4481 gitk:4585 gitk:6384
+msgid "Regexp"
+msgstr "Expressão regular"
+
+#: gitk:2188 gitk:2189 gitk:4606 gitk:4636 gitk:4643 gitk:6512 gitk:6580
+msgid "All fields"
+msgstr "Todos os campos"
+
+#: gitk:2189 gitk:4604 gitk:4636 gitk:6451
+msgid "Headline"
+msgstr "Assunto"
+
+#: gitk:2190 gitk:4604 gitk:6451 gitk:6580 gitk:7013
+msgid "Comments"
+msgstr "Descrição da revisão"
+
+#: gitk:2190 gitk:4604 gitk:4608 gitk:4643 gitk:6451 gitk:6948 gitk:8307
+#: gitk:8322
+msgid "Author"
+msgstr "Autor"
+
+#: gitk:2190 gitk:4604 gitk:6451 gitk:6950
+msgid "Committer"
+msgstr "Revisor"
+
+#: gitk:2221
+msgid "Search"
+msgstr "Buscar"
+
+#: gitk:2229
+msgid "Diff"
+msgstr "Diferenças"
+
+#: gitk:2231
+msgid "Old version"
+msgstr "Versão antiga"
+
+#: gitk:2233
+msgid "New version"
+msgstr "Versão nova"
+
+#: gitk:2235
+msgid "Lines of context"
+msgstr "Número de linhas de contexto"
+
+#: gitk:2245
+msgid "Ignore space change"
+msgstr "Ignorar mudanças de caixa"
+
+#: gitk:2304
+msgid "Patch"
+msgstr "Diferenças"
+
+#: gitk:2306
+msgid "Tree"
+msgstr "Árvore"
+
+#: gitk:2463 gitk:2480
+msgid "Diff this -> selected"
+msgstr "Comparar esta revisão com a selecionada"
+
+#: gitk:2464 gitk:2481
+msgid "Diff selected -> this"
+msgstr "Comparar a revisão selecionada com esta"
+
+#: gitk:2465 gitk:2482
+msgid "Make patch"
+msgstr "Criar patch"
+
+#: gitk:2466 gitk:8715
+msgid "Create tag"
+msgstr "Criar etiqueta"
+
+#: gitk:2467 gitk:8831
+msgid "Write commit to file"
+msgstr "Salvar revisão para um arquivo"
+
+#: gitk:2468 gitk:8888
+msgid "Create new branch"
+msgstr "Criar novo ramo"
+
+#: gitk:2469
+msgid "Cherry-pick this commit"
+msgstr "Fazer cherry-pick desta revisão"
+
+#: gitk:2470
+msgid "Reset HEAD branch to here"
+msgstr "Redefinir HEAD para cá"
+
+#: gitk:2471
+msgid "Mark this commit"
+msgstr "Marcar esta revisão"
+
+#: gitk:2472
+msgid "Return to mark"
+msgstr "Voltar à marca"
+
+#: gitk:2473
+msgid "Find descendant of this and mark"
+msgstr "Encontrar descendente e marcar"
+
+#: gitk:2474
+msgid "Compare with marked commit"
+msgstr "Comparar com a revisão marcada"
+
+#: gitk:2488
+msgid "Check out this branch"
+msgstr "Efetuar checkout deste ramo"
+
+#: gitk:2489
+msgid "Remove this branch"
+msgstr "Excluir este ramo"
+
+#: gitk:2496
+msgid "Highlight this too"
+msgstr "Marcar este também"
+
+#: gitk:2497
+msgid "Highlight this only"
+msgstr "Marcar apenas este"
+
+#: gitk:2498
+msgid "External diff"
+msgstr "Diff externo"
+
+#: gitk:2499
+msgid "Blame parent commit"
+msgstr "Anotar revisão anterior"
+
+#: gitk:2506
+msgid "Show origin of this line"
+msgstr "Exibir origem desta linha"
+
+#: gitk:2507
+msgid "Run git gui blame on this line"
+msgstr "Executar 'git blame' nesta linha"
+
+#: gitk:2789
+msgid "\n"
+"Gitk - a commit viewer for git\n"
+"\n"
+"Copyright ©9 2005-2010 Paul Mackerras\n"
+"\n"
+"Use and redistribute under the terms of the GNU General Public License"
+msgstr "\n"
+"Gitk - um visualizador de revisões para o git \n"
+"\n"
+"Copyright ©9 2005-2010 Paul Mackerras\n"
+"\n"
+"Uso e distribuição segundo os termos da Licença Pública Geral GNU"
+
+#: gitk:2797 gitk:2862 gitk:9253
+msgid "Close"
+msgstr "Fechar"
+
+#: gitk:2818
+msgid "Gitk key bindings"
+msgstr "Atalhos de teclado"
+
+#: gitk:2821
+msgid "Gitk key bindings:"
+msgstr "Atalhos de teclado:"
+
+#: gitk:2823
+#, tcl-format
+msgid "<%s-Q>\t\tQuit"
+msgstr "<%s-Q>\t\tSair"
+
+#: gitk:2824
+#, tcl-format
+msgid "<%s-W>\t\tClose window"
+msgstr "<%s-W>\t\tFechar janela"
+
+#: gitk:2825
+msgid "<Home>\t\tMove to first commit"
+msgstr "<Home>\t\tIr para a primeira revisão"
+
+#: gitk:2826
+msgid "<End>\t\tMove to last commit"
+msgstr "<End>\t\tIr para a última revisão"
+
+#: gitk:2827
+msgid "<Up>, p, i\tMove up one commit"
+msgstr "<Up>, p, i\tIr para uma revisão acima"
+
+#: gitk:2828
+msgid "<Down>, n, k\tMove down one commit"
+msgstr "<Down>, n, k\tIr para uma revisão abaixo"
+
+#: gitk:2829
+msgid "<Left>, z, j\tGo back in history list"
+msgstr "<Left>, z, j\tVoltar no histórico"
+
+#: gitk:2830
+msgid "<Right>, x, l\tGo forward in history list"
+msgstr "<Right>, x, l\tAvançar no histórico"
+
+#: gitk:2831
+msgid "<PageUp>\tMove up one page in commit list"
+msgstr "<PageUp>\tSubir uma página na lista de revisões"
+
+#: gitk:2832
+msgid "<PageDown>\tMove down one page in commit list"
+msgstr "<PageDown>\tDescer uma página na lista de revisões"
+
+#: gitk:2833
+#, tcl-format
+msgid "<%s-Home>\tScroll to top of commit list"
+msgstr "<%s-Home>\tRolar para o início da lista de revisões"
+
+#: gitk:2834
+#, tcl-format
+msgid "<%s-End>\tScroll to bottom of commit list"
+msgstr "<%s-End>\tRolar para o final da lista de revisões"
+
+#: gitk:2835
+#, tcl-format
+msgid "<%s-Up>\tScroll commit list up one line"
+msgstr "<%s-Up>\tRolar uma linha acima na lista de revisões"
+
+#: gitk:2836
+#, tcl-format
+msgid "<%s-Down>\tScroll commit list down one line"
+msgstr "<%s-Down>\tRolar uma linha abaixo na lista de revisões"
+
+#: gitk:2837
+#, tcl-format
+msgid "<%s-PageUp>\tScroll commit list up one page"
+msgstr "<%s-PageUp>\tRolar uma página acima na lista de revisões"
+
+#: gitk:2838
+#, tcl-format
+msgid "<%s-PageDown>\tScroll commit list down one page"
+msgstr "<%s-PageDown>\tRolar uma página abaixo na lista de revisões"
+
+#: gitk:2839
+msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
+msgstr "<Shift-Up>\tProcurar próxima (revisões mas recentes)"
+
+#: gitk:2840
+msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
+msgstr "<Shift-Down>\tProcurar anterior (revisões mais antigas)"
+
+#: gitk:2841
+msgid "<Delete>, b\tScroll diff view up one page"
+msgstr "<Delete>, b\tRola alterações uma página acima"
+
+#: gitk:2842
+msgid "<Backspace>\tScroll diff view up one page"
+msgstr "<Backspace>\tRolar alterações uma página abaixo"
+
+#: gitk:2843
+msgid "<Space>\t\tScroll diff view down one page"
+msgstr "<Space>\t\tRolar alterações uma página abaixo"
+
+#: gitk:2844
+msgid "u\t\tScroll diff view up 18 lines"
+msgstr "u\t\tRolar alterações 18 linhas acima"
+
+#: gitk:2845
+msgid "d\t\tScroll diff view down 18 lines"
+msgstr "d\t\tRolar alterações 18 linhas abaixo"
+
+#: gitk:2846
+#, tcl-format
+msgid "<%s-F>\t\tFind"
+msgstr "<%s-F>\t\tProcurar"
+
+#: gitk:2847
+#, tcl-format
+msgid "<%s-G>\t\tMove to next find hit"
+msgstr "<%s-G>\t\tIr para a próxima ocorrência"
+
+#: gitk:2848
+msgid "<Return>\tMove to next find hit"
+msgstr "<Return>\tIr para a próxima ocorrência"
+
+#: gitk:2849
+msgid "/\t\tFocus the search box"
+msgstr "/\t\tPor foco na caixa de busca"
+
+#: gitk:2850
+msgid "?\t\tMove to previous find hit"
+msgstr "?\t\tIr para a ocorrência anterior"
+
+#: gitk:2851
+msgid "f\t\tScroll diff view to next file"
+msgstr "f\t\tRolar alterações para o próximo arquivo"
+
+#: gitk:2852
+#, tcl-format
+msgid "<%s-S>\t\tSearch for next hit in diff view"
+msgstr "<%s-S>\t\tProcurar a próxima ocorrência na lista de alterações"
+
+#: gitk:2853
+#, tcl-format
+msgid "<%s-R>\t\tSearch for previous hit in diff view"
+msgstr "<%s-R>\t\tProcurar ocorrência anterior na lista de alterações"
+
+#: gitk:2854
+#, tcl-format
+msgid "<%s-KP+>\tIncrease font size"
+msgstr "<%s-KP+>\tAumentar tamanho da fonte"
+
+#: gitk:2855
+#, tcl-format
+msgid "<%s-plus>\tIncrease font size"
+msgstr "<%s-plus>\tAumentar tamanho da fonte"
+
+#: gitk:2856
+#, tcl-format
+msgid "<%s-KP->\tDecrease font size"
+msgstr "<%s-KP->\tReduzir tamanho da fonte"
+
+#: gitk:2857
+#, tcl-format
+msgid "<%s-minus>\tDecrease font size"
+msgstr "<%s-minus>\tReduzir tamanho da fonte"
+
+#: gitk:2858
+msgid "<F5>\t\tUpdate"
+msgstr "<F5>\t\tAtualizar"
+
+#: gitk:3313 gitk:3322
+#, tcl-format
+msgid "Error creating temporary directory %s:"
+msgstr "Erro ao criar o diretório temporário %s:"
+
+#: gitk:3335
+#, tcl-format
+msgid "Error getting \"%s\" from %s:"
+msgstr "Erro ao ler \"%s\" de %s:"
+
+#: gitk:3398
+msgid "command failed:"
+msgstr "O comando falhou:"
+
+#: gitk:3547
+msgid "No such commit"
+msgstr "Revisão não encontrada"
+
+#: gitk:3561
+msgid "git gui blame: command failed:"
+msgstr "Comando 'git gui blame' falhou:"
+
+#: gitk:3592
+#, tcl-format
+msgid "Couldn't read merge head: %s"
+msgstr "Impossível ler merge head: %s"
+
+#: gitk:3600
+#, tcl-format
+msgid "Error reading index: %s"
+msgstr "Erro ao ler o índice: %s"
+
+#: gitk:3625
+#, tcl-format
+msgid "Couldn't start git blame: %s"
+msgstr "Não foi possível inciar o 'git blame': %s"
+
+#: gitk:3628 gitk:6419
+msgid "Searching"
+msgstr "Procurando"
+
+#: gitk:3660
+#, tcl-format
+msgid "Error running git blame: %s"
+msgstr "Erro ao executar 'git blame': %s"
+
+#: gitk:3688
+#, tcl-format
+msgid "That line comes from commit %s,  which is not in this view"
+msgstr "Esta linha vem da revisão %s, que não está nesta vista"
+
+#: gitk:3702
+msgid "External diff viewer failed:"
+msgstr "Erro do visualizador de alterações externo:"
+
+#: gitk:3820
+msgid "Gitk view definition"
+msgstr "Definir vista"
+
+#: gitk:3824
+msgid "Remember this view"
+msgstr "Lembrar esta vista"
+
+#: gitk:3825
+msgid "References (space separated list):"
+msgstr "Referências (separar a lista com um espaço):"
+
+#: gitk:3826
+msgid "Branches & tags:"
+msgstr "Ramos & etiquetas:"
+
+#: gitk:3827
+msgid "All refs"
+msgstr "Todas as referências"
+
+#: gitk:3828
+msgid "All (local) branches"
+msgstr "Todos os ramos locais"
+
+#: gitk:3829
+msgid "All tags"
+msgstr "Todas as etiquetas"
+
+#: gitk:3830
+msgid "All remote-tracking branches"
+msgstr "Todos os ramos de rastreio"
+
+#: gitk:3831
+msgid "Commit Info (regular expressions):"
+msgstr "Informações da revisão (expressões regulares):"
+
+#: gitk:3832
+msgid "Author:"
+msgstr "Autor:"
+
+#: gitk:3833
+msgid "Committer:"
+msgstr "Revisor:"
+
+#: gitk:3834
+msgid "Commit Message:"
+msgstr "Descrição da revisão:"
+
+#: gitk:3835
+msgid "Matches all Commit Info criteria"
+msgstr "Coincidir todos os critérios de informações da revisão"
+
+#: gitk:3836
+msgid "Changes to Files:"
+msgstr "Mudanças para os arquivos:"
+
+#: gitk:3837
+msgid "Fixed String"
+msgstr "Texto fixo"
+
+#: gitk:3838
+msgid "Regular Expression"
+msgstr "Expressão regular"
+
+#: gitk:3839
+msgid "Search string:"
+msgstr "Texto de busca"
+
+#: gitk:3840
+msgid ""
+"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+msgstr ""
+"Datas de revisão (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+
+#: gitk:3841
+msgid "Since:"
+msgstr "Desde:"
+
+#: gitk:3842
+msgid "Until:"
+msgstr "Até:"
+
+#: gitk:3843
+msgid "Limit and/or skip a number of revisions (positive integer):"
+msgstr "Limitar e/ou ignorar um número de revisões (inteiro positivo):"
+
+#: gitk:3844
+msgid "Number to show:"
+msgstr "Número para mostrar:"
+
+#: gitk:3845
+msgid "Number to skip:"
+msgstr "Número para ignorar:"
+
+#: gitk:3846
+msgid "Miscellaneous options:"
+msgstr "Opções diversas:"
+
+#: gitk:3847
+msgid "Strictly sort by date"
+msgstr "Ordenar estritamente pela data"
+
+#: gitk:3848
+msgid "Mark branch sides"
+msgstr "Marcar os dois lados do ramo"
+
+#: gitk:3849
+msgid "Limit to first parent"
+msgstr "Limitar ao primeiro antecessor"
+
+#: gitk:3850
+msgid "Simple history"
+msgstr "Histórico simplificado"
+
+#: gitk:3851
+msgid "Additional arguments to git log:"
+msgstr "Argumentos adicionais para o 'git log':"
+
+#: gitk:3852
+msgid "Enter files and directories to include, one per line:"
+msgstr "Arquivos e diretórios para incluir, um por linha"
+
+#: gitk:3853
+msgid "Command to generate more commits to include:"
+msgstr "Comando para gerar mais revisões para incluir:"
+
+#: gitk:3977
+msgid "Gitk: edit view"
+msgstr "Gitk: editar vista"
+
+#: gitk:3985
+msgid "-- criteria for selecting revisions"
+msgstr "-- critérios para selecionar revisões"
+
+#: gitk:3990
+msgid "View Name"
+msgstr "Nome da vista"
+
+#: gitk:4065
+msgid "Apply (F5)"
+msgstr "Aplicar (F5)"
+
+#: gitk:4103
+msgid "Error in commit selection arguments:"
+msgstr "Erro nos argumentos de seleção de revisões:"
+
+#: gitk:4156 gitk:4208 gitk:4656 gitk:4670 gitk:5931 gitk:11551 gitk:11552
+msgid "None"
+msgstr "Nenhum"
+
+#: gitk:4604 gitk:6451 gitk:8309 gitk:8324
+msgid "Date"
+msgstr "Data"
+
+#: gitk:4604 gitk:6451
+msgid "CDate"
+msgstr "DataR"
+
+#: gitk:4753 gitk:4758
+msgid "Descendant"
+msgstr "Descendente de"
+
+#: gitk:4754
+msgid "Not descendant"
+msgstr "Não descendente de"
+
+#: gitk:4761 gitk:4766
+msgid "Ancestor"
+msgstr "Antecessor de"
+
+#: gitk:4762
+msgid "Not ancestor"
+msgstr "Não antecessor de"
+
+#: gitk:5052
+msgid "Local changes checked in to index but not committed"
+msgstr "Mudanças locais marcadas, porém não salvas"
+
+#: gitk:5088
+msgid "Local uncommitted changes, not checked in to index"
+msgstr "Mudanças locais não marcadas"
+
+#: gitk:6769
+msgid "many"
+msgstr "muitas"
+
+#: gitk:6952
+msgid "Tags:"
+msgstr "Etiquetas:"
+
+#: gitk:6969 gitk:6975 gitk:8302
+msgid "Parent"
+msgstr "Antecessor"
+
+#: gitk:6980
+msgid "Child"
+msgstr "Descendente"
+
+#: gitk:6989
+msgid "Branch"
+msgstr "Ramo"
+
+#: gitk:6992
+msgid "Follows"
+msgstr "Segue"
+
+#: gitk:6995
+msgid "Precedes"
+msgstr "Precede"
+
+#: gitk:7532
+#, tcl-format
+msgid "Error getting diffs: %s"
+msgstr "Erro ao obter diferenças: %s"
+
+#: gitk:8130
+msgid "Goto:"
+msgstr "Ir para:"
+
+#: gitk:8151
+#, tcl-format
+msgid "Short SHA1 id %s is ambiguous"
+msgstr "O id SHA1 %s é ambíguo"
+
+#: gitk:8158
+#, tcl-format
+msgid "Revision %s is not known"
+msgstr "Revisão %s desconhecida"
+
+#: gitk:8168
+#, tcl-format
+msgid "SHA1 id %s is not known"
+msgstr "Id SHA1 %s desconhecido"
+
+#: gitk:8170
+#, tcl-format
+msgid "Revision %s is not in the current view"
+msgstr "A revisão %s não está na vista atual"
+
+#: gitk:8312
+msgid "Children"
+msgstr "Descendentes"
+
+#: gitk:8370
+#, tcl-format
+msgid "Reset %s branch to here"
+msgstr "Redefinir ramo %s para este ponto"
+
+#: gitk:8372
+msgid "Detached head: can't reset"
+msgstr "Detached head: impossível redefinir"
+
+#: gitk:8481 gitk:8487
+msgid "Skipping merge commit "
+msgstr "Saltando revisão de mesclagem"
+
+#: gitk:8496 gitk:8501
+msgid "Error getting patch ID for "
+msgstr "Erro ao obter patch ID para"
+
+#: gitk:8497 gitk:8502
+msgid " - stopping\n"
+msgstr "- parando\n"
+
+#: gitk:8507 gitk:8510 gitk:8518 gitk:8532 gitk:8541
+msgid "Commit "
+msgstr "Revisão"
+
+#: gitk:8511
+msgid ""
+" is the same patch as\n"
+"       "
+msgstr ""
+"é o mesmo patch que\n"
+"       "
+
+#: gitk:8519
+msgid ""
+" differs from\n"
+"       "
+msgstr "difere de"
+
+#: gitk:8521
+msgid ""
+"Diff of commits:\n"
+"\n"
+msgstr ""
+"Diferença de revisões:\n"
+"\n"
+
+#: gitk:8533 gitk:8542
+#, tcl-format
+msgid " has %s children - stopping\n"
+msgstr "possui %s descendentes - parando\n"
+
+#: gitk:8561
+#, tcl-format
+msgid "Error writing commit to file: %s"
+msgstr "Erro ao salvar revisão para o arquivo: %s"
+
+#: gitk:8567
+#, tcl-format
+msgid "Error diffing commits: %s"
+msgstr "Erro ao comparar revisões: %s"
+
+#: gitk:8598
+msgid "Top"
+msgstr "Início"
+
+#: gitk:8599
+msgid "From"
+msgstr "De"
+
+#: gitk:8604
+msgid "To"
+msgstr "Para"
+
+#: gitk:8628
+msgid "Generate patch"
+msgstr "Gerar patch"
+
+#: gitk:8630
+msgid "From:"
+msgstr "De:"
+
+#: gitk:8639
+msgid "To:"
+msgstr "Para:"
+
+#: gitk:8648
+msgid "Reverse"
+msgstr "Inverter"
+
+#: gitk:8650 gitk:8845
+msgid "Output file:"
+msgstr "Arquivo de saída:"
+
+#: gitk:8656
+msgid "Generate"
+msgstr "Gerar"
+
+#: gitk:8694
+msgid "Error creating patch:"
+msgstr "Erro ao criar patch:"
+
+#: gitk:8717 gitk:8833 gitk:8890
+msgid "ID:"
+msgstr "ID:"
+
+#: gitk:8726
+msgid "Tag name:"
+msgstr "Nome da etiqueta:"
+
+#: gitk:8729
+msgid "Tag message is optional"
+msgstr "A descrição da etiqueta é opcional"
+
+#: gitk:8731
+msgid "Tag message:"
+msgstr "Descrição da etiqueta"
+
+#: gitk:8735 gitk:8899
+msgid "Create"
+msgstr "Criar"
+
+#: gitk:8753
+msgid "No tag name specified"
+msgstr "Nome da etiqueta não indicado"
+
+#: gitk:8757
+#, tcl-format
+msgid "Tag \"%s\" already exists"
+msgstr "Etiqueta \"%s\" já existe"
+
+#: gitk:8767
+msgid "Error creating tag:"
+msgstr "Erro ao criar etiqueta:"
+
+#: gitk:8842
+msgid "Command:"
+msgstr "Comando:"
+
+#: gitk:8850
+msgid "Write"
+msgstr "Exportar"
+
+#: gitk:8868
+msgid "Error writing commit:"
+msgstr "Erro ao exportar revisão"
+
+#: gitk:8895
+msgid "Name:"
+msgstr "Nome:"
+
+#: gitk:8918
+msgid "Please specify a name for the new branch"
+msgstr "Indique um nome para o novo ramo"
+
+#: gitk:8923
+#, tcl-format
+msgid "Branch '%s' already exists. Overwrite?"
+msgstr "O ramo \"%s\" já existe. Sobrescrever?"
+
+#: gitk:8989
+#, tcl-format
+msgid "Commit %s is already included in branch %s -- really re-apply it?"
+msgstr "Revisão %s já inclusa no ramo %s -- você realmente deseja reaplicá-la?"
+
+#: gitk:8994
+msgid "Cherry-picking"
+msgstr "Cherry-picking"
+
+#: gitk:9003
+#, tcl-format
+msgid ""
+"Cherry-pick failed because of local changes to file '%s'.\n"
+"Please commit, reset or stash your changes and try again."
+msgstr ""
+"O cherry-pick falhou porque o arquivo \"%s\" possui mudanças locais.\n"
+"Salve a uma revisão, redefina ou armazene (stash) suas mudanças e tente "
+"novamente."
+
+#: gitk:9009
+msgid ""
+"Cherry-pick failed because of merge conflict.\n"
+"Do you wish to run git citool to resolve it?"
+msgstr ""
+"O cherry-pick falhou porque houve um conflito na mesclagem.\n"
+"Executar o 'git citool' para resolvê-lo?"
+
+#: gitk:9025
+msgid "No changes committed"
+msgstr "Nenhuma revisão foi salva"
+
+#: gitk:9051
+msgid "Confirm reset"
+msgstr "Confirmar redefinição"
+
+#: gitk:9053
+#, tcl-format
+msgid "Reset branch %s to %s?"
+msgstr "Você realmente deseja redefinir o ramo %s para %s?"
+
+#: gitk:9055
+msgid "Reset type:"
+msgstr "Tipo de redefinição"
+
+#: gitk:9058
+msgid "Soft: Leave working tree and index untouched"
+msgstr "Soft: deixa a árvore de trabalho e o índice intocados"
+
+#: gitk:9061
+msgid "Mixed: Leave working tree untouched, reset index"
+msgstr "Misto: Deixa a árvore de trabalho intocada, redefine o índice"
+
+#: gitk:9064
+msgid ""
+"Hard: Reset working tree and index\n"
+"(discard ALL local changes)"
+msgstr ""
+"Hard: Redefine a árvore de trabalho e o índice\n"
+"(descarta TODAS as mudanças locais)"
+
+#: gitk:9081
+msgid "Resetting"
+msgstr "Redefinindo"
+
+#: gitk:9141
+msgid "Checking out"
+msgstr "Abrindo"
+
+#: gitk:9194
+msgid "Cannot delete the currently checked-out branch"
+msgstr "Impossível excluir o ramo atualmente aberto"
+
+#: gitk:9200
+#, tcl-format
+msgid ""
+"The commits on branch %s aren't on any other branch.\n"
+"Really delete branch %s?"
+msgstr ""
+"As revisões do ramo \"%s\" não existem em nenhum outro ramo.\n"
+"Você realmente deseja excluir ramo \"%s\"?"
+
+#: gitk:9231
+#, tcl-format
+msgid "Tags and heads: %s"
+msgstr "Referências: %s"
+
+#: gitk:9246
+msgid "Filter"
+msgstr "Filtro"
+
+#: gitk:9541
+msgid ""
+"Error reading commit topology information; branch and preceding/following "
+"tag information will be incomplete."
+msgstr ""
+"Erro ao ler a topologia das revisões; as informações dos ramos e etiquetas "
+"antecessoras/sucessoras estarão incompletas"
+
+#: gitk:10527
+msgid "Tag"
+msgstr "Etiqueta"
+
+#: gitk:10527
+msgid "Id"
+msgstr "Id"
+
+#: gitk:10576
+msgid "Gitk font chooser"
+msgstr "Selecionar fontes do Gitk"
+
+#: gitk:10593
+msgid "B"
+msgstr "B"
+
+#: gitk:10596
+msgid "I"
+msgstr "I"
+
+#: gitk:10714
+msgid "Gitk preferences"
+msgstr "Preferências do Gitk"
+
+#: gitk:10716
+msgid "Commit list display options"
+msgstr "Opções da lista de revisões"
+
+#: gitk:10719
+msgid "Maximum graph width (lines)"
+msgstr "Largura máxima do grafo (linhas)"
+
+#: gitk:10722
+#, tcl-format
+msgid "Maximum graph width (% of pane)"
+msgstr "Largura máxima do grafo (% do painel)"
+
+#: gitk:10725
+msgid "Show local changes"
+msgstr "Exibir mudanças locais"
+
+#: gitk:10728
+msgid "Auto-select SHA1"
+msgstr "Selecionar o SHA1 automaticamente"
+
+#: gitk:10731
+msgid "Hide remote refs"
+msgstr "Ocultar referências remotas"
+
+#: gitk:10735
+msgid "Diff display options"
+msgstr "Opções de exibição das alterações"
+
+#: gitk:10737
+msgid "Tab spacing"
+msgstr "Espaços por tabulação"
+
+#: gitk:10740
+msgid "Display nearby tags"
+msgstr "Exibir etiquetas próximas"
+
+#: gitk:10743
+msgid "Limit diffs to listed paths"
+msgstr "Limitar diferenças aos caminhos listados"
+
+#: gitk:10746
+msgid "Support per-file encodings"
+msgstr "Usar codificações distintas por arquivo"
+
+#: gitk:10752 gitk:10832
+msgid "External diff tool"
+msgstr "Ferramenta 'diff' externa"
+
+#: gitk:10753
+msgid "Choose..."
+msgstr "Selecionar..."
+
+#: gitk:10758
+msgid "General options"
+msgstr "Opções gerais"
+
+#: gitk:10761
+msgid "Use themed widgets"
+msgstr "Usar temas para as janelas"
+
+#: gitk:10763
+msgid "(change requires restart)"
+msgstr "(exige reinicialização)"
+
+#: gitk:10765
+msgid "(currently unavailable)"
+msgstr "(atualmente indisponível)"
+
+#: gitk:10769
+msgid "Colors: press to choose"
+msgstr "Cores: clique para escolher"
+
+#: gitk:10772
+msgid "Interface"
+msgstr "Interface"
+
+#: gitk:10773
+msgid "interface"
+msgstr "interface"
+
+#: gitk:10776
+msgid "Background"
+msgstr "Segundo plano"
+
+#: gitk:10777 gitk:10807
+msgid "background"
+msgstr "segundo plano"
+
+#: gitk:10780
+msgid "Foreground"
+msgstr "Primeiro plano"
+
+#: gitk:10781
+msgid "foreground"
+msgstr "primeiro plano"
+
+#: gitk:10784
+msgid "Diff: old lines"
+msgstr "Diff: linhas excluídas"
+
+#: gitk:10785
+msgid "diff old lines"
+msgstr "linhas excluídas"
+
+#: gitk:10789
+msgid "Diff: new lines"
+msgstr "Diff: linhas adicionadas"
+
+#: gitk:10790
+msgid "diff new lines"
+msgstr "linhas adicionadas"
+
+#: gitk:10794
+msgid "Diff: hunk header"
+msgstr "Diff: cabeçalho do bloco"
+
+#: gitk:10796
+msgid "diff hunk header"
+msgstr "cabeçalho do bloco"
+
+#: gitk:10800
+msgid "Marked line bg"
+msgstr "2º plano da linha marcada"
+
+#: gitk:10802
+msgid "marked line background"
+msgstr "segundo plano da linha marcada"
+
+#: gitk:10806
+msgid "Select bg"
+msgstr "2º plano da seleção"
+
+#: gitk:10810
+msgid "Fonts: press to choose"
+msgstr "Fontes: clique para escolher"
+
+#: gitk:10812
+msgid "Main font"
+msgstr "Fonte principal"
+
+#: gitk:10813
+msgid "Diff display font"
+msgstr "Fonte da lista de mudanças"
+
+#: gitk:10814
+msgid "User interface font"
+msgstr "Fonte da interface"
+
+#: gitk:10842
+#, tcl-format
+msgid "Gitk: choose color for %s"
+msgstr "Gitk: selecionar cor para %s"
+
+#: gitk:11445
+msgid "Cannot find a git repository here."
+msgstr "Não há nenhum repositório git aqui."
+
+#: gitk:11449
+#, tcl-format
+msgid "Cannot find the git directory \"%s\"."
+msgstr "Impossível encontrar o diretório git \"%s\"."
+
+#: gitk:11496
+#, tcl-format
+msgid "Ambiguous argument '%s': both revision and filename"
+msgstr ""
+"O argumento \"%s\" é ambíguo (especifica tanto uma revisão e um nome de "
+"arquivo)"
+
+#: gitk:11508
+msgid "Bad arguments to gitk:"
+msgstr "Argumentos incorretos para o gitk:"
+
+#: gitk:11604
+msgid "Command line"
+msgstr "Linha de comando"
index 386763ade786d96b657dd0ab737b633eef44b424..2f07a2e2d93d655cfe5cf2d3535e7b86422233c6 100644 (file)
@@ -1,5 +1,5 @@
 # Swedish translation for gitk
-# Copyright (C) 2005-2009 Paul Mackerras
+# Copyright (C) 2005-2010 Paul Mackerras
 # This file is distributed under the same license as the gitk package.
 #
 # Peter Krefting <peter@softwolves.pp.se>, 2008-2010.
@@ -8,8 +8,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: sv\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-01-28 13:16+0100\n"
-"PO-Revision-Date: 2010-01-28 13:48+0100\n"
+"POT-Creation-Date: 2010-09-12 21:14+0100\n"
+"PO-Revision-Date: 2010-09-12 21:16+0100\n"
 "Last-Translator: Peter Krefting <peter@softwolves.pp.se>\n"
 "Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
 "MIME-Version: 1.0\n"
@@ -24,17 +24,17 @@ msgstr "Kunde inte hämta lista över ej sammanslagna filer:"
 msgid "Error parsing revisions:"
 msgstr "Fel vid tolkning av revisioner:"
 
-#: gitk:329
+#: gitk:330
 msgid "Error executing --argscmd command:"
 msgstr "Fel vid körning av --argscmd-kommando:"
 
-#: gitk:342
+#: gitk:343
 msgid "No files selected: --merge specified but no files are unmerged."
 msgstr ""
 "Inga filer valdes: --merge angavs men det finns inga filer som inte har "
 "slagits samman."
 
-#: gitk:345
+#: gitk:346
 msgid ""
 "No files selected: --merge specified but no unmerged files are within file "
 "limit."
@@ -42,600 +42,605 @@ msgstr ""
 "Inga filer valdes: --merge angavs men det finns inga filer inom "
 "filbegränsningen."
 
-#: gitk:367 gitk:514
+#: gitk:368 gitk:516
 msgid "Error executing git log:"
 msgstr "Fel vid körning av git log:"
 
-#: gitk:385 gitk:530
+#: gitk:386 gitk:532
 msgid "Reading"
 msgstr "Läser"
 
-#: gitk:445 gitk:4261
+#: gitk:446 gitk:4271
 msgid "Reading commits..."
 msgstr "Läser incheckningar..."
 
-#: gitk:448 gitk:1578 gitk:4264
+#: gitk:449 gitk:1580 gitk:4274
 msgid "No commits selected"
 msgstr "Inga incheckningar markerade"
 
-#: gitk:1454
+#: gitk:1456
 msgid "Can't parse git log output:"
 msgstr "Kan inte tolka utdata från git log:"
 
-#: gitk:1674
+#: gitk:1676
 msgid "No commit information available"
 msgstr "Ingen incheckningsinformation är tillgänglig"
 
-#: gitk:1816
+#: gitk:1818
 msgid "mc"
 msgstr "mc"
 
-#: gitk:1851 gitk:4054 gitk:9044 gitk:10585 gitk:10804
+#: gitk:1853 gitk:4064 gitk:9067 gitk:10607 gitk:10817
 msgid "OK"
 msgstr "OK"
 
-#: gitk:1853 gitk:4056 gitk:8634 gitk:8713 gitk:8828 gitk:8877 gitk:9046
-#: gitk:10586 gitk:10805
+#: gitk:1855 gitk:4066 gitk:8657 gitk:8736 gitk:8851 gitk:8900 gitk:9069
+#: gitk:10608 gitk:10818
 msgid "Cancel"
 msgstr "Avbryt"
 
-#: gitk:1975
+#: gitk:1980
 msgid "Update"
 msgstr "Uppdatera"
 
-#: gitk:1976
+#: gitk:1981
 msgid "Reload"
 msgstr "Ladda om"
 
-#: gitk:1977
+#: gitk:1982
 msgid "Reread references"
 msgstr "Läs om referenser"
 
-#: gitk:1978
+#: gitk:1983
 msgid "List references"
 msgstr "Visa referenser"
 
-#: gitk:1980
+#: gitk:1985
 msgid "Start git gui"
 msgstr "Starta git gui"
 
-#: gitk:1982
+#: gitk:1987
 msgid "Quit"
 msgstr "Avsluta"
 
-#: gitk:1974
+#: gitk:1979
 msgid "File"
 msgstr "Arkiv"
 
-#: gitk:1986
+#: gitk:1991
 msgid "Preferences"
 msgstr "Inställningar"
 
-#: gitk:1985
+#: gitk:1990
 msgid "Edit"
 msgstr "Redigera"
 
-#: gitk:1990
+#: gitk:1995
 msgid "New view..."
 msgstr "Ny vy..."
 
-#: gitk:1991
+#: gitk:1996
 msgid "Edit view..."
 msgstr "Ändra vy..."
 
-#: gitk:1992
+#: gitk:1997
 msgid "Delete view"
 msgstr "Ta bort vy"
 
-#: gitk:1994
+#: gitk:1999
 msgid "All files"
 msgstr "Alla filer"
 
-#: gitk:1989 gitk:3808
+#: gitk:1994 gitk:3817
 msgid "View"
 msgstr "Visa"
 
-#: gitk:1999 gitk:2009 gitk:2780
+#: gitk:2004 gitk:2014 gitk:2787
 msgid "About gitk"
 msgstr "Om gitk"
 
-#: gitk:2000 gitk:2014
+#: gitk:2005 gitk:2019
 msgid "Key bindings"
 msgstr "Tangentbordsbindningar"
 
-#: gitk:1998 gitk:2013
+#: gitk:2003 gitk:2018
 msgid "Help"
 msgstr "Hjälp"
 
-#: gitk:2091 gitk:8110
+#: gitk:2096 gitk:8132
 msgid "SHA1 ID:"
 msgstr "SHA1-id:"
 
-#: gitk:2122
+#: gitk:2127
 msgid "Row"
 msgstr "Rad"
 
-#: gitk:2160
+#: gitk:2165
 msgid "Find"
 msgstr "Sök"
 
-#: gitk:2161
+#: gitk:2166
 msgid "next"
 msgstr "nästa"
 
-#: gitk:2162
+#: gitk:2167
 msgid "prev"
 msgstr "föreg"
 
-#: gitk:2163
+#: gitk:2168
 msgid "commit"
 msgstr "incheckning"
 
-#: gitk:2166 gitk:2168 gitk:4422 gitk:4445 gitk:4469 gitk:6410 gitk:6482
-#: gitk:6566
+#: gitk:2171 gitk:2173 gitk:4432 gitk:4455 gitk:4479 gitk:6420 gitk:6492
+#: gitk:6576
 msgid "containing:"
 msgstr "som innehåller:"
 
-#: gitk:2169 gitk:3290 gitk:3295 gitk:4497
+#: gitk:2174 gitk:3298 gitk:3303 gitk:4507
 msgid "touching paths:"
 msgstr "som rör sökväg:"
 
-#: gitk:2170 gitk:4502
+#: gitk:2175 gitk:4512
 msgid "adding/removing string:"
 msgstr "som lägger/till tar bort sträng:"
 
-#: gitk:2179 gitk:2181
+#: gitk:2184 gitk:2186
 msgid "Exact"
 msgstr "Exakt"
 
-#: gitk:2181 gitk:4577 gitk:6378
+#: gitk:2186 gitk:4587 gitk:6388
 msgid "IgnCase"
 msgstr "IgnVersaler"
 
-#: gitk:2181 gitk:4471 gitk:4575 gitk:6374
+#: gitk:2186 gitk:4481 gitk:4585 gitk:6384
 msgid "Regexp"
 msgstr "Reg.uttr."
 
-#: gitk:2183 gitk:2184 gitk:4596 gitk:4626 gitk:4633 gitk:6502 gitk:6570
+#: gitk:2188 gitk:2189 gitk:4606 gitk:4636 gitk:4643 gitk:6512 gitk:6580
 msgid "All fields"
 msgstr "Alla fält"
 
-#: gitk:2184 gitk:4594 gitk:4626 gitk:6441
+#: gitk:2189 gitk:4604 gitk:4636 gitk:6451
 msgid "Headline"
 msgstr "Rubrik"
 
-#: gitk:2185 gitk:4594 gitk:6441 gitk:6570 gitk:7003
+#: gitk:2190 gitk:4604 gitk:6451 gitk:6580 gitk:7013
 msgid "Comments"
 msgstr "Kommentarer"
 
-#: gitk:2185 gitk:4594 gitk:4598 gitk:4633 gitk:6441 gitk:6938 gitk:8285
-#: gitk:8300
+#: gitk:2190 gitk:4604 gitk:4608 gitk:4643 gitk:6451 gitk:6948 gitk:8307
+#: gitk:8322
 msgid "Author"
 msgstr "Författare"
 
-#: gitk:2185 gitk:4594 gitk:6441 gitk:6940
+#: gitk:2190 gitk:4604 gitk:6451 gitk:6950
 msgid "Committer"
 msgstr "Incheckare"
 
-#: gitk:2216
+#: gitk:2221
 msgid "Search"
 msgstr "Sök"
 
-#: gitk:2224
+#: gitk:2229
 msgid "Diff"
 msgstr "Diff"
 
-#: gitk:2226
+#: gitk:2231
 msgid "Old version"
 msgstr "Gammal version"
 
-#: gitk:2228
+#: gitk:2233
 msgid "New version"
 msgstr "Ny version"
 
-#: gitk:2230
+#: gitk:2235
 msgid "Lines of context"
 msgstr "Rader sammanhang"
 
-#: gitk:2240
+#: gitk:2245
 msgid "Ignore space change"
 msgstr "Ignorera ändringar i blanksteg"
 
-#: gitk:2299
+#: gitk:2304
 msgid "Patch"
 msgstr "Patch"
 
-#: gitk:2301
+#: gitk:2306
 msgid "Tree"
 msgstr "Träd"
 
-#: gitk:2456 gitk:2473
+#: gitk:2463 gitk:2480
 msgid "Diff this -> selected"
 msgstr "Diff denna -> markerad"
 
-#: gitk:2457 gitk:2474
+#: gitk:2464 gitk:2481
 msgid "Diff selected -> this"
 msgstr "Diff markerad -> denna"
 
-#: gitk:2458 gitk:2475
+#: gitk:2465 gitk:2482
 msgid "Make patch"
 msgstr "Skapa patch"
 
-#: gitk:2459 gitk:8692
+#: gitk:2466 gitk:8715
 msgid "Create tag"
 msgstr "Skapa tagg"
 
-#: gitk:2460 gitk:8808
+#: gitk:2467 gitk:8831
 msgid "Write commit to file"
 msgstr "Skriv incheckning till fil"
 
-#: gitk:2461 gitk:8865
+#: gitk:2468 gitk:8888
 msgid "Create new branch"
 msgstr "Skapa ny gren"
 
-#: gitk:2462
+#: gitk:2469
 msgid "Cherry-pick this commit"
 msgstr "Plocka denna incheckning"
 
-#: gitk:2463
+#: gitk:2470
 msgid "Reset HEAD branch to here"
 msgstr "Återställ HEAD-grenen hit"
 
-#: gitk:2464
+#: gitk:2471
 msgid "Mark this commit"
 msgstr "Markera denna incheckning"
 
-#: gitk:2465
+#: gitk:2472
 msgid "Return to mark"
 msgstr "Återgå till markering"
 
-#: gitk:2466
+#: gitk:2473
 msgid "Find descendant of this and mark"
 msgstr "Hitta efterföljare till denna och markera"
 
-#: gitk:2467
+#: gitk:2474
 msgid "Compare with marked commit"
 msgstr "Jämför med markerad incheckning"
 
-#: gitk:2481
+#: gitk:2488
 msgid "Check out this branch"
 msgstr "Checka ut denna gren"
 
-#: gitk:2482
+#: gitk:2489
 msgid "Remove this branch"
 msgstr "Ta bort denna gren"
 
-#: gitk:2489
+#: gitk:2496
 msgid "Highlight this too"
 msgstr "Markera även detta"
 
-#: gitk:2490
+#: gitk:2497
 msgid "Highlight this only"
 msgstr "Markera bara detta"
 
-#: gitk:2491
+#: gitk:2498
 msgid "External diff"
 msgstr "Extern diff"
 
-#: gitk:2492
+#: gitk:2499
 msgid "Blame parent commit"
 msgstr "Klandra föräldraincheckning"
 
-#: gitk:2499
+#: gitk:2506
 msgid "Show origin of this line"
 msgstr "Visa ursprunget för den här raden"
 
-#: gitk:2500
+#: gitk:2507
 msgid "Run git gui blame on this line"
 msgstr "Kör git gui blame på den här raden"
 
-#: gitk:2782
+#: gitk:2789
 msgid ""
 "\n"
 "Gitk - a commit viewer for git\n"
 "\n"
-"Copyright \\u00a9 2005-2010 Paul Mackerras\n"
+"Copyright ©9 2005-2010 Paul Mackerras\n"
 "\n"
 "Use and redistribute under the terms of the GNU General Public License"
 msgstr ""
 "\n"
 "Gitk - en incheckningsvisare för git\n"
 "\n"
-"Copyright \\u00a9 2005-2010 Paul Mackerras\n"
+"Copyright ©9 2005-2010 Paul Mackerras\n"
 "\n"
 "Använd och vidareförmedla enligt villkoren i GNU General Public License"
 
-#: gitk:2790 gitk:2854 gitk:9230
+#: gitk:2797 gitk:2862 gitk:9253
 msgid "Close"
 msgstr "Stäng"
 
-#: gitk:2811
+#: gitk:2818
 msgid "Gitk key bindings"
 msgstr "Tangentbordsbindningar för Gitk"
 
-#: gitk:2814
+#: gitk:2821
 msgid "Gitk key bindings:"
 msgstr "Tangentbordsbindningar för Gitk:"
 
-#: gitk:2816
+#: gitk:2823
 #, tcl-format
 msgid "<%s-Q>\t\tQuit"
 msgstr "<%s-Q>\t\tAvsluta"
 
-#: gitk:2817
+#: gitk:2824
+#, tcl-format
+msgid "<%s-W>\t\tClose window"
+msgstr "<%s-W>\t\tStäng fönster"
+
+#: gitk:2825
 msgid "<Home>\t\tMove to first commit"
 msgstr "<Home>\t\tGå till första incheckning"
 
-#: gitk:2818
+#: gitk:2826
 msgid "<End>\t\tMove to last commit"
 msgstr "<End>\t\tGå till sista incheckning"
 
-#: gitk:2819
+#: gitk:2827
 msgid "<Up>, p, i\tMove up one commit"
 msgstr "<Upp>, p, i\tGå en incheckning upp"
 
-#: gitk:2820
+#: gitk:2828
 msgid "<Down>, n, k\tMove down one commit"
 msgstr "<Ned>, n, k\tGå en incheckning ned"
 
-#: gitk:2821
+#: gitk:2829
 msgid "<Left>, z, j\tGo back in history list"
 msgstr "<Vänster>, z, j\tGå bakåt i historiken"
 
-#: gitk:2822
+#: gitk:2830
 msgid "<Right>, x, l\tGo forward in history list"
 msgstr "<Höger>, x, l\tGå framåt i historiken"
 
-#: gitk:2823
+#: gitk:2831
 msgid "<PageUp>\tMove up one page in commit list"
 msgstr "<PageUp>\tGå upp en sida i incheckningslistan"
 
-#: gitk:2824
+#: gitk:2832
 msgid "<PageDown>\tMove down one page in commit list"
 msgstr "<PageDown>\tGå ned en sida i incheckningslistan"
 
-#: gitk:2825
+#: gitk:2833
 #, tcl-format
 msgid "<%s-Home>\tScroll to top of commit list"
 msgstr "<%s-Home>\tRulla till början av incheckningslistan"
 
-#: gitk:2826
+#: gitk:2834
 #, tcl-format
 msgid "<%s-End>\tScroll to bottom of commit list"
 msgstr "<%s-End>\tRulla till slutet av incheckningslistan"
 
-#: gitk:2827
+#: gitk:2835
 #, tcl-format
 msgid "<%s-Up>\tScroll commit list up one line"
 msgstr "<%s-Upp>\tRulla incheckningslistan upp ett steg"
 
-#: gitk:2828
+#: gitk:2836
 #, tcl-format
 msgid "<%s-Down>\tScroll commit list down one line"
 msgstr "<%s-Ned>\tRulla incheckningslistan ned ett steg"
 
-#: gitk:2829
+#: gitk:2837
 #, tcl-format
 msgid "<%s-PageUp>\tScroll commit list up one page"
 msgstr "<%s-PageUp>\tRulla incheckningslistan upp en sida"
 
-#: gitk:2830
+#: gitk:2838
 #, tcl-format
 msgid "<%s-PageDown>\tScroll commit list down one page"
 msgstr "<%s-PageDown>\tRulla incheckningslistan ned en sida"
 
-#: gitk:2831
+#: gitk:2839
 msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
 msgstr "<Skift-Upp>\tSök bakåt (uppåt, senare incheckningar)"
 
-#: gitk:2832
+#: gitk:2840
 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
 msgstr "<Skift-Ned>\tSök framåt (nedåt, tidigare incheckningar)"
 
-#: gitk:2833
+#: gitk:2841
 msgid "<Delete>, b\tScroll diff view up one page"
 msgstr "<Delete>, b\tRulla diffvisningen upp en sida"
 
-#: gitk:2834
+#: gitk:2842
 msgid "<Backspace>\tScroll diff view up one page"
 msgstr "<Baksteg>\tRulla diffvisningen upp en sida"
 
-#: gitk:2835
+#: gitk:2843
 msgid "<Space>\t\tScroll diff view down one page"
 msgstr "<Blanksteg>\tRulla diffvisningen ned en sida"
 
-#: gitk:2836
+#: gitk:2844
 msgid "u\t\tScroll diff view up 18 lines"
 msgstr "u\t\tRulla diffvisningen upp 18 rader"
 
-#: gitk:2837
+#: gitk:2845
 msgid "d\t\tScroll diff view down 18 lines"
 msgstr "d\t\tRulla diffvisningen ned 18 rader"
 
-#: gitk:2838
+#: gitk:2846
 #, tcl-format
 msgid "<%s-F>\t\tFind"
 msgstr "<%s-F>\t\tSök"
 
-#: gitk:2839
+#: gitk:2847
 #, tcl-format
 msgid "<%s-G>\t\tMove to next find hit"
 msgstr "<%s-G>\t\tGå till nästa sökträff"
 
-#: gitk:2840
+#: gitk:2848
 msgid "<Return>\tMove to next find hit"
 msgstr "<Return>\t\tGå till nästa sökträff"
 
-#: gitk:2841
+#: gitk:2849
 msgid "/\t\tFocus the search box"
 msgstr "/\t\tFokusera sökrutan"
 
-#: gitk:2842
+#: gitk:2850
 msgid "?\t\tMove to previous find hit"
 msgstr "?\t\tGå till föregående sökträff"
 
-#: gitk:2843
+#: gitk:2851
 msgid "f\t\tScroll diff view to next file"
 msgstr "f\t\tRulla diffvisningen till nästa fil"
 
-#: gitk:2844
+#: gitk:2852
 #, tcl-format
 msgid "<%s-S>\t\tSearch for next hit in diff view"
 msgstr "<%s-S>\t\tGå till nästa sökträff i diffvisningen"
 
-#: gitk:2845
+#: gitk:2853
 #, tcl-format
 msgid "<%s-R>\t\tSearch for previous hit in diff view"
 msgstr "<%s-R>\t\tGå till föregående sökträff i diffvisningen"
 
-#: gitk:2846
+#: gitk:2854
 #, tcl-format
 msgid "<%s-KP+>\tIncrease font size"
 msgstr "<%s-Num+>\tÖka teckenstorlek"
 
-#: gitk:2847
+#: gitk:2855
 #, tcl-format
 msgid "<%s-plus>\tIncrease font size"
 msgstr "<%s-plus>\tÖka teckenstorlek"
 
-#: gitk:2848
+#: gitk:2856
 #, tcl-format
 msgid "<%s-KP->\tDecrease font size"
 msgstr "<%s-Num->\tMinska teckenstorlek"
 
-#: gitk:2849
+#: gitk:2857
 #, tcl-format
 msgid "<%s-minus>\tDecrease font size"
 msgstr "<%s-minus>\tMinska teckenstorlek"
 
-#: gitk:2850
+#: gitk:2858
 msgid "<F5>\t\tUpdate"
 msgstr "<F5>\t\tUppdatera"
 
-#: gitk:3305 gitk:3314
+#: gitk:3313 gitk:3322
 #, tcl-format
 msgid "Error creating temporary directory %s:"
 msgstr "Fel vid skapande av temporär katalog %s:"
 
-#: gitk:3327
+#: gitk:3335
 #, tcl-format
 msgid "Error getting \"%s\" from %s:"
 msgstr "Fel vid hämtning av  \"%s\" från %s:"
 
-#: gitk:3390
+#: gitk:3398
 msgid "command failed:"
 msgstr "kommando misslyckades:"
 
-#: gitk:3539
+#: gitk:3547
 msgid "No such commit"
 msgstr "Incheckning saknas"
 
-#: gitk:3553
+#: gitk:3561
 msgid "git gui blame: command failed:"
 msgstr "git gui blame: kommando misslyckades:"
 
-#: gitk:3584
+#: gitk:3592
 #, tcl-format
 msgid "Couldn't read merge head: %s"
 msgstr "Kunde inte läsa sammanslagningshuvud: %s"
 
-#: gitk:3592
+#: gitk:3600
 #, tcl-format
 msgid "Error reading index: %s"
 msgstr "Fel vid läsning av index: %s"
 
-#: gitk:3617
+#: gitk:3625
 #, tcl-format
 msgid "Couldn't start git blame: %s"
 msgstr "Kunde inte starta git blame: %s"
 
-#: gitk:3620 gitk:6409
+#: gitk:3628 gitk:6419
 msgid "Searching"
 msgstr "Söker"
 
-#: gitk:3652
+#: gitk:3660
 #, tcl-format
 msgid "Error running git blame: %s"
 msgstr "Fel vid körning av git blame: %s"
 
-#: gitk:3680
+#: gitk:3688
 #, tcl-format
 msgid "That line comes from commit %s,  which is not in this view"
 msgstr "Raden kommer från incheckningen %s, som inte finns i denna vy"
 
-#: gitk:3694
+#: gitk:3702
 msgid "External diff viewer failed:"
 msgstr "Externt diff-verktyg misslyckades:"
 
-#: gitk:3812
+#: gitk:3820
 msgid "Gitk view definition"
 msgstr "Definition av Gitk-vy"
 
-#: gitk:3816
+#: gitk:3824
 msgid "Remember this view"
 msgstr "Spara denna vy"
 
-#: gitk:3817
+#: gitk:3825
 msgid "References (space separated list):"
 msgstr "Referenser (blankstegsavdelad lista):"
 
-#: gitk:3818
+#: gitk:3826
 msgid "Branches & tags:"
 msgstr "Grenar & taggar:"
 
-#: gitk:3819
+#: gitk:3827
 msgid "All refs"
 msgstr "Alla referenser"
 
-#: gitk:3820
+#: gitk:3828
 msgid "All (local) branches"
 msgstr "Alla (lokala) grenar"
 
-#: gitk:3821
+#: gitk:3829
 msgid "All tags"
 msgstr "Alla taggar"
 
-#: gitk:3822
+#: gitk:3830
 msgid "All remote-tracking branches"
 msgstr "Alla fjärrspårande grenar"
 
-#: gitk:3823
+#: gitk:3831
 msgid "Commit Info (regular expressions):"
 msgstr "Incheckningsinfo (reguljära uttryck):"
 
-#: gitk:3824
+#: gitk:3832
 msgid "Author:"
 msgstr "Författare:"
 
-#: gitk:3825
+#: gitk:3833
 msgid "Committer:"
 msgstr "Incheckare:"
 
-#: gitk:3826
+#: gitk:3834
 msgid "Commit Message:"
 msgstr "Incheckningsmeddelande:"
 
-#: gitk:3827
+#: gitk:3835
 msgid "Matches all Commit Info criteria"
 msgstr "Motsvarar alla kriterier för incheckningsinfo"
 
-#: gitk:3828
+#: gitk:3836
 msgid "Changes to Files:"
 msgstr "Ändringar av filer:"
 
-#: gitk:3829
+#: gitk:3837
 msgid "Fixed String"
 msgstr "Fast sträng"
 
-#: gitk:3830
+#: gitk:3838
 msgid "Regular Expression"
 msgstr "Reguljärt uttryck"
 
-#: gitk:3831
+#: gitk:3839
 msgid "Search string:"
 msgstr "Söksträng:"
 
-#: gitk:3832
+#: gitk:3840
 msgid ""
 "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
 "15:27:38\"):"
@@ -643,201 +648,201 @@ msgstr ""
 "Incheckingsdatum (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
 "15:27:38\"):"
 
-#: gitk:3833
+#: gitk:3841
 msgid "Since:"
 msgstr "Från:"
 
-#: gitk:3834
+#: gitk:3842
 msgid "Until:"
 msgstr "Till:"
 
-#: gitk:3835
+#: gitk:3843
 msgid "Limit and/or skip a number of revisions (positive integer):"
 msgstr "Begränsa och/eller hoppa över ett antal revisioner (positivt heltal):"
 
-#: gitk:3836
+#: gitk:3844
 msgid "Number to show:"
 msgstr "Antal att visa:"
 
-#: gitk:3837
+#: gitk:3845
 msgid "Number to skip:"
 msgstr "Antal att hoppa över:"
 
-#: gitk:3838
+#: gitk:3846
 msgid "Miscellaneous options:"
 msgstr "Diverse alternativ:"
 
-#: gitk:3839
+#: gitk:3847
 msgid "Strictly sort by date"
 msgstr "Strikt datumsortering"
 
-#: gitk:3840
+#: gitk:3848
 msgid "Mark branch sides"
 msgstr "Markera sidogrenar"
 
-#: gitk:3841
+#: gitk:3849
 msgid "Limit to first parent"
 msgstr "Begränsa till första förälder"
 
-#: gitk:3842
+#: gitk:3850
 msgid "Simple history"
 msgstr "Enkel historik"
 
-#: gitk:3843
+#: gitk:3851
 msgid "Additional arguments to git log:"
 msgstr "Ytterligare argument till git log:"
 
-#: gitk:3844
+#: gitk:3852
 msgid "Enter files and directories to include, one per line:"
 msgstr "Ange filer och kataloger att ta med, en per rad:"
 
-#: gitk:3845
+#: gitk:3853
 msgid "Command to generate more commits to include:"
 msgstr "Kommando för att generera fler incheckningar att ta med:"
 
-#: gitk:3967
+#: gitk:3977
 msgid "Gitk: edit view"
 msgstr "Gitk: redigera vy"
 
-#: gitk:3975
+#: gitk:3985
 msgid "-- criteria for selecting revisions"
 msgstr " - kriterier för val av revisioner"
 
-#: gitk:3980
+#: gitk:3990
 msgid "View Name"
 msgstr "Namn på vy"
 
-#: gitk:4055
+#: gitk:4065
 msgid "Apply (F5)"
 msgstr "Använd (F5)"
 
-#: gitk:4093
+#: gitk:4103
 msgid "Error in commit selection arguments:"
 msgstr "Fel i argument för val av incheckningar:"
 
-#: gitk:4146 gitk:4198 gitk:4646 gitk:4660 gitk:5921 gitk:11534 gitk:11535
+#: gitk:4156 gitk:4208 gitk:4656 gitk:4670 gitk:5931 gitk:11551 gitk:11552
 msgid "None"
 msgstr "Inget"
 
-#: gitk:4594 gitk:6441 gitk:8287 gitk:8302
+#: gitk:4604 gitk:6451 gitk:8309 gitk:8324
 msgid "Date"
 msgstr "Datum"
 
-#: gitk:4594 gitk:6441
+#: gitk:4604 gitk:6451
 msgid "CDate"
 msgstr "Skapat datum"
 
-#: gitk:4743 gitk:4748
+#: gitk:4753 gitk:4758
 msgid "Descendant"
 msgstr "Avkomling"
 
-#: gitk:4744
+#: gitk:4754
 msgid "Not descendant"
 msgstr "Inte avkomling"
 
-#: gitk:4751 gitk:4756
+#: gitk:4761 gitk:4766
 msgid "Ancestor"
 msgstr "Förfader"
 
-#: gitk:4752
+#: gitk:4762
 msgid "Not ancestor"
 msgstr "Inte förfader"
 
-#: gitk:5042
+#: gitk:5052
 msgid "Local changes checked in to index but not committed"
 msgstr "Lokala ändringar sparade i indexet men inte incheckade"
 
-#: gitk:5078
+#: gitk:5088
 msgid "Local uncommitted changes, not checked in to index"
 msgstr "Lokala ändringar, ej sparade i indexet"
 
-#: gitk:6759
+#: gitk:6769
 msgid "many"
 msgstr "många"
 
-#: gitk:6942
+#: gitk:6952
 msgid "Tags:"
 msgstr "Taggar:"
 
-#: gitk:6959 gitk:6965 gitk:8280
+#: gitk:6969 gitk:6975 gitk:8302
 msgid "Parent"
 msgstr "Förälder"
 
-#: gitk:6970
+#: gitk:6980
 msgid "Child"
 msgstr "Barn"
 
-#: gitk:6979
+#: gitk:6989
 msgid "Branch"
 msgstr "Gren"
 
-#: gitk:6982
+#: gitk:6992
 msgid "Follows"
 msgstr "Följer"
 
-#: gitk:6985
+#: gitk:6995
 msgid "Precedes"
 msgstr "Föregår"
 
-#: gitk:7522
+#: gitk:7532
 #, tcl-format
 msgid "Error getting diffs: %s"
 msgstr "Fel vid hämtning av diff: %s"
 
-#: gitk:8108
+#: gitk:8130
 msgid "Goto:"
 msgstr "Gå till:"
 
-#: gitk:8129
+#: gitk:8151
 #, tcl-format
 msgid "Short SHA1 id %s is ambiguous"
 msgstr "Förkortat SHA1-id %s är tvetydigt"
 
-#: gitk:8136
+#: gitk:8158
 #, tcl-format
 msgid "Revision %s is not known"
 msgstr "Revisionen %s är inte känd"
 
-#: gitk:8146
+#: gitk:8168
 #, tcl-format
 msgid "SHA1 id %s is not known"
 msgstr "SHA-id:t %s är inte känt"
 
-#: gitk:8148
+#: gitk:8170
 #, tcl-format
 msgid "Revision %s is not in the current view"
 msgstr "Revisionen %s finns inte i den nuvarande vyn"
 
-#: gitk:8290
+#: gitk:8312
 msgid "Children"
 msgstr "Barn"
 
-#: gitk:8348
+#: gitk:8370
 #, tcl-format
 msgid "Reset %s branch to here"
 msgstr "Återställ grenen %s hit"
 
-#: gitk:8350
+#: gitk:8372
 msgid "Detached head: can't reset"
 msgstr "Frånkopplad head: kan inte återställa"
 
-#: gitk:8459 gitk:8465
+#: gitk:8481 gitk:8487
 msgid "Skipping merge commit "
 msgstr "Hoppar över sammanslagningsincheckning "
 
-#: gitk:8474 gitk:8479
+#: gitk:8496 gitk:8501
 msgid "Error getting patch ID for "
 msgstr "Fel vid hämtning av patch-id för "
 
-#: gitk:8475 gitk:8480
+#: gitk:8497 gitk:8502
 msgid " - stopping\n"
 msgstr " - stannar\n"
 
-#: gitk:8485 gitk:8488 gitk:8496 gitk:8510 gitk:8519
+#: gitk:8507 gitk:8510 gitk:8518 gitk:8532 gitk:8541
 msgid "Commit "
 msgstr "Incheckning "
 
-#: gitk:8489
+#: gitk:8511
 msgid ""
 " is the same patch as\n"
 "       "
@@ -845,7 +850,7 @@ msgstr ""
 " är samma patch som\n"
 "       "
 
-#: gitk:8497
+#: gitk:8519
 msgid ""
 " differs from\n"
 "       "
@@ -853,139 +858,139 @@ msgstr ""
 " skiljer sig från\n"
 "       "
 
-#: gitk:8499
+#: gitk:8521
 msgid ""
 "Diff of commits:\n"
 "\n"
-msgstr "Skillnad mellan incheckningar:\n"
+msgstr ""
+"Skillnad mellan incheckningar:\n"
 "\n"
-""
 
-#: gitk:8511 gitk:8520
+#: gitk:8533 gitk:8542
 #, tcl-format
 msgid " has %s children - stopping\n"
 msgstr " har %s barn - stannar\n"
 
-#: gitk:8539
+#: gitk:8561
 #, tcl-format
 msgid "Error writing commit to file: %s"
 msgstr "Fel vid skrivning av incheckning till fil: %s"
 
-#: gitk:8545
+#: gitk:8567
 #, tcl-format
 msgid "Error diffing commits: %s"
 msgstr "Fel vid jämförelse av incheckningar: %s"
 
-#: gitk:8575
+#: gitk:8598
 msgid "Top"
 msgstr "Topp"
 
-#: gitk:8576
+#: gitk:8599
 msgid "From"
 msgstr "Från"
 
-#: gitk:8581
+#: gitk:8604
 msgid "To"
 msgstr "Till"
 
-#: gitk:8605
+#: gitk:8628
 msgid "Generate patch"
 msgstr "Generera patch"
 
-#: gitk:8607
+#: gitk:8630
 msgid "From:"
 msgstr "Från:"
 
-#: gitk:8616
+#: gitk:8639
 msgid "To:"
 msgstr "Till:"
 
-#: gitk:8625
+#: gitk:8648
 msgid "Reverse"
 msgstr "Vänd"
 
-#: gitk:8627 gitk:8822
+#: gitk:8650 gitk:8845
 msgid "Output file:"
 msgstr "Utdatafil:"
 
-#: gitk:8633
+#: gitk:8656
 msgid "Generate"
 msgstr "Generera"
 
-#: gitk:8671
+#: gitk:8694
 msgid "Error creating patch:"
 msgstr "Fel vid generering av patch:"
 
-#: gitk:8694 gitk:8810 gitk:8867
+#: gitk:8717 gitk:8833 gitk:8890
 msgid "ID:"
 msgstr "Id:"
 
-#: gitk:8703
+#: gitk:8726
 msgid "Tag name:"
 msgstr "Taggnamn:"
 
-#: gitk:8706
+#: gitk:8729
 msgid "Tag message is optional"
 msgstr "Taggmeddelandet är valfritt"
 
-#: gitk:8708
+#: gitk:8731
 msgid "Tag message:"
 msgstr "Taggmeddelande:"
 
-#: gitk:8712 gitk:8876
+#: gitk:8735 gitk:8899
 msgid "Create"
 msgstr "Skapa"
 
-#: gitk:8730
+#: gitk:8753
 msgid "No tag name specified"
 msgstr "Inget taggnamn angavs"
 
-#: gitk:8734
+#: gitk:8757
 #, tcl-format
 msgid "Tag \"%s\" already exists"
 msgstr "Taggen \"%s\" finns redan"
 
-#: gitk:8744
+#: gitk:8767
 msgid "Error creating tag:"
 msgstr "Fel vid skapande av tagg:"
 
-#: gitk:8819
+#: gitk:8842
 msgid "Command:"
 msgstr "Kommando:"
 
-#: gitk:8827
+#: gitk:8850
 msgid "Write"
 msgstr "Skriv"
 
-#: gitk:8845
+#: gitk:8868
 msgid "Error writing commit:"
 msgstr "Fel vid skrivning av incheckning:"
 
-#: gitk:8872
+#: gitk:8895
 msgid "Name:"
 msgstr "Namn:"
 
-#: gitk:8895
+#: gitk:8918
 msgid "Please specify a name for the new branch"
 msgstr "Ange ett namn för den nya grenen"
 
-#: gitk:8900
+#: gitk:8923
 #, tcl-format
 msgid "Branch '%s' already exists. Overwrite?"
 msgstr "Grenen \"%s\" finns redan. Skriva över?"
 
-#: gitk:8966
+#: gitk:8989
 #, tcl-format
 msgid "Commit %s is already included in branch %s -- really re-apply it?"
 msgstr ""
 "Incheckningen %s finns redan på grenen %s -- skall den verkligen appliceras "
 "på nytt?"
 
-#: gitk:8971
+#: gitk:8994
 msgid "Cherry-picking"
 msgstr "Plockar"
 
-#: gitk:8980
+#: gitk:9003
 #, tcl-format
 msgid ""
 "Cherry-pick failed because of local changes to file '%s'.\n"
@@ -995,7 +1000,7 @@ msgstr ""
 "Checka in, återställ eller spara undan (stash) dina ändringar och försök "
 "igen."
 
-#: gitk:8986
+#: gitk:9009
 msgid ""
 "Cherry-pick failed because of merge conflict.\n"
 "Do you wish to run git citool to resolve it?"
@@ -1003,32 +1008,32 @@ msgstr ""
 "Cherry-pick misslyckades på grund av en sammanslagningskonflikt.\n"
 "Vill du köra git citool för att lösa den?"
 
-#: gitk:9002
+#: gitk:9025
 msgid "No changes committed"
 msgstr "Inga ändringar incheckade"
 
-#: gitk:9028
+#: gitk:9051
 msgid "Confirm reset"
 msgstr "Bekräfta återställning"
 
-#: gitk:9030
+#: gitk:9053
 #, tcl-format
 msgid "Reset branch %s to %s?"
 msgstr "Återställa grenen %s till %s?"
 
-#: gitk:9032
+#: gitk:9055
 msgid "Reset type:"
 msgstr "Typ av återställning:"
 
-#: gitk:9035
+#: gitk:9058
 msgid "Soft: Leave working tree and index untouched"
 msgstr "Mjuk: Rör inte utcheckning och index"
 
-#: gitk:9038
+#: gitk:9061
 msgid "Mixed: Leave working tree untouched, reset index"
 msgstr "Blandad: Rör inte utcheckning, återställ index"
 
-#: gitk:9041
+#: gitk:9064
 msgid ""
 "Hard: Reset working tree and index\n"
 "(discard ALL local changes)"
@@ -1036,19 +1041,19 @@ msgstr ""
 "Hård: Återställ utcheckning och index\n"
 "(förkastar ALLA lokala ändringar)"
 
-#: gitk:9058
+#: gitk:9081
 msgid "Resetting"
 msgstr "Återställer"
 
-#: gitk:9118
+#: gitk:9141
 msgid "Checking out"
 msgstr "Checkar ut"
 
-#: gitk:9171
+#: gitk:9194
 msgid "Cannot delete the currently checked-out branch"
 msgstr "Kan inte ta bort den just nu utcheckade grenen"
 
-#: gitk:9177
+#: gitk:9200
 #, tcl-format
 msgid ""
 "The commits on branch %s aren't on any other branch.\n"
@@ -1057,16 +1062,16 @@ msgstr ""
 "Incheckningarna på grenen %s existerar inte på någon annan gren.\n"
 "Vill du verkligen ta bort grenen %s?"
 
-#: gitk:9208
+#: gitk:9231
 #, tcl-format
 msgid "Tags and heads: %s"
 msgstr "Taggar och huvuden: %s"
 
-#: gitk:9223
+#: gitk:9246
 msgid "Filter"
 msgstr "Filter"
 
-#: gitk:9518
+#: gitk:9541
 msgid ""
 "Error reading commit topology information; branch and preceding/following "
 "tag information will be incomplete."
@@ -1074,203 +1079,203 @@ msgstr ""
 "Fel vid läsning av information om incheckningstopologi; information om "
 "grenar och föregående/senare taggar kommer inte vara komplett."
 
-#: gitk:10504
+#: gitk:10527
 msgid "Tag"
 msgstr "Tagg"
 
-#: gitk:10504
+#: gitk:10527
 msgid "Id"
 msgstr "Id"
 
-#: gitk:10554
+#: gitk:10576
 msgid "Gitk font chooser"
 msgstr "Teckensnittsväljare för Gitk"
 
-#: gitk:10571
+#: gitk:10593
 msgid "B"
 msgstr "F"
 
-#: gitk:10574
+#: gitk:10596
 msgid "I"
 msgstr "K"
 
-#: gitk:10692
+#: gitk:10714
 msgid "Gitk preferences"
 msgstr "Inställningar för Gitk"
 
-#: gitk:10694
+#: gitk:10716
 msgid "Commit list display options"
 msgstr "Alternativ för incheckningslistvy"
 
-#: gitk:10697
+#: gitk:10719
 msgid "Maximum graph width (lines)"
 msgstr "Maximal grafbredd (rader)"
 
-#: gitk:10700
+#: gitk:10722
 #, tcl-format
 msgid "Maximum graph width (% of pane)"
 msgstr "Maximal grafbredd (% av ruta)"
 
-#: gitk:10703
+#: gitk:10725
 msgid "Show local changes"
 msgstr "Visa lokala ändringar"
 
-#: gitk:10706
+#: gitk:10728
 msgid "Auto-select SHA1"
 msgstr "Välj SHA1 automatiskt"
 
-#: gitk:10709
+#: gitk:10731
 msgid "Hide remote refs"
 msgstr "Dölj fjärr-referenser"
 
-#: gitk:10713
+#: gitk:10735
 msgid "Diff display options"
 msgstr "Alternativ för diffvy"
 
-#: gitk:10715
+#: gitk:10737
 msgid "Tab spacing"
 msgstr "Blanksteg för tabulatortecken"
 
-#: gitk:10718
+#: gitk:10740
 msgid "Display nearby tags"
 msgstr "Visa närliggande taggar"
 
-#: gitk:10721
+#: gitk:10743
 msgid "Limit diffs to listed paths"
 msgstr "Begränsa diff till listade sökvägar"
 
-#: gitk:10724
+#: gitk:10746
 msgid "Support per-file encodings"
 msgstr "Stöd för filspecifika teckenkodningar"
 
-#: gitk:10730 gitk:10819
+#: gitk:10752 gitk:10832
 msgid "External diff tool"
 msgstr "Externt diff-verktyg"
 
-#: gitk:10731
+#: gitk:10753
 msgid "Choose..."
 msgstr "Välj..."
 
-#: gitk:10736
+#: gitk:10758
 msgid "General options"
 msgstr "Allmänna inställningar"
 
-#: gitk:10739
+#: gitk:10761
 msgid "Use themed widgets"
 msgstr "Använd tema på fönsterelement"
 
-#: gitk:10741
+#: gitk:10763
 msgid "(change requires restart)"
 msgstr "(ändringen kräver omstart)"
 
-#: gitk:10743
+#: gitk:10765
 msgid "(currently unavailable)"
 msgstr "(för närvarande inte tillgängligt)"
 
-#: gitk:10747
+#: gitk:10769
 msgid "Colors: press to choose"
 msgstr "Färger: tryck för att välja"
 
-#: gitk:10750
+#: gitk:10772
 msgid "Interface"
 msgstr "Gränssnitt"
 
-#: gitk:10751
+#: gitk:10773
 msgid "interface"
 msgstr "gränssnitt"
 
-#: gitk:10754
+#: gitk:10776
 msgid "Background"
 msgstr "Bakgrund"
 
-#: gitk:10755 gitk:10785
+#: gitk:10777 gitk:10807
 msgid "background"
 msgstr "bakgrund"
 
-#: gitk:10758
+#: gitk:10780
 msgid "Foreground"
 msgstr "Förgrund"
 
-#: gitk:10759
+#: gitk:10781
 msgid "foreground"
 msgstr "förgrund"
 
-#: gitk:10762
+#: gitk:10784
 msgid "Diff: old lines"
 msgstr "Diff: gamla rader"
 
-#: gitk:10763
+#: gitk:10785
 msgid "diff old lines"
 msgstr "diff gamla rader"
 
-#: gitk:10767
+#: gitk:10789
 msgid "Diff: new lines"
 msgstr "Diff: nya rader"
 
-#: gitk:10768
+#: gitk:10790
 msgid "diff new lines"
 msgstr "diff nya rader"
 
-#: gitk:10772
+#: gitk:10794
 msgid "Diff: hunk header"
 msgstr "Diff: delhuvud"
 
-#: gitk:10774
+#: gitk:10796
 msgid "diff hunk header"
 msgstr "diff delhuvud"
 
-#: gitk:10778
+#: gitk:10800
 msgid "Marked line bg"
 msgstr "Markerad rad bakgrund"
 
-#: gitk:10780
+#: gitk:10802
 msgid "marked line background"
 msgstr "markerad rad bakgrund"
 
-#: gitk:10784
+#: gitk:10806
 msgid "Select bg"
 msgstr "Markerad bakgrund"
 
-#: gitk:10788
+#: gitk:10810
 msgid "Fonts: press to choose"
 msgstr "Teckensnitt: tryck för att välja"
 
-#: gitk:10790
+#: gitk:10812
 msgid "Main font"
 msgstr "Huvudteckensnitt"
 
-#: gitk:10791
+#: gitk:10813
 msgid "Diff display font"
 msgstr "Teckensnitt för diffvisning"
 
-#: gitk:10792
+#: gitk:10814
 msgid "User interface font"
 msgstr "Teckensnitt för användargränssnitt"
 
-#: gitk:10829
+#: gitk:10842
 #, tcl-format
 msgid "Gitk: choose color for %s"
 msgstr "Gitk: välj färg för %s"
 
-#: gitk:11433
+#: gitk:11445
 msgid "Cannot find a git repository here."
-msgstr "Hittar inget gitk-arkiv här."
+msgstr "Hittar inget git-arkiv här."
 
-#: gitk:11437
+#: gitk:11449
 #, tcl-format
 msgid "Cannot find the git directory \"%s\"."
 msgstr "Hittar inte git-katalogen \"%s\"."
 
-#: gitk:11484
+#: gitk:11496
 #, tcl-format
 msgid "Ambiguous argument '%s': both revision and filename"
 msgstr "Tvetydigt argument \"%s\": både revision och filnamn"
 
-#: gitk:11496
+#: gitk:11508
 msgid "Bad arguments to gitk:"
 msgstr "Felaktiga argument till gitk:"
 
-#: gitk:11587
+#: gitk:11604
 msgid "Command line"
 msgstr "Kommandorad"
 
index bf3664f2b7b7b4b5177d172c6b99a84940244469..4a673933acee475dc423007063d1300b44176a61 100644 (file)
@@ -177,13 +177,15 @@ not include variables usually directly set during build):
  * $my_url, $my_uri
    Full URL and absolute URL of gitweb script;
    in earlier versions of gitweb you might have need to set those
-   variables, now there should be no need to do it.
+   variables, now there should be no need to do it.  See
+   $per_request_config if you need to set them still.
  * $base_url
    Base URL for relative URLs in pages generated by gitweb,
    (e.g. $logo, $favicon, @stylesheets if they are relative URLs),
    needed and used only for URLs with nonempty PATH_INFO via
    <base href="$base_url">.  Usually gitweb sets its value correctly,
    and there is no need to set this variable, e.g. to $my_uri or "/".
+   See $per_request_config if you need to set it anyway.
  * $home_link
    Target of the home link on top of all pages (the first part of view
    "breadcrumbs").  By default set to absolute URI of a page ($my_uri).
@@ -246,6 +248,16 @@ not include variables usually directly set during build):
    http://www.andre-simon.de due to assumptions about parameters and output).
    Useful if highlight is not installed on your webserver's PATH.
    [Default: highlight]
+ * $per_request_config
+   If set to code reference, it would be run once per each request.  You can
+   set parts of configuration that change per session, e.g. by setting it to
+     sub { $ENV{GL_USER} = $cgi->remote_user || "gitweb"; }
+   Otherwise it is treated as boolean value: if true gitweb would process
+   config file once per request, if false it would process config file only
+   once.  Note: $my_url, $my_uri, and $base_url are overwritten with
+   their default values before every request, so if you want to change
+   them, be sure to set this variable to true or a code reference effecting
+   the desired changes.  The default is true.
 
 Projects list file format
 ~~~~~~~~~~~~~~~~~~~~~~~~~
index 679f2da3ee0c59413d34bf020e242887bfd4f28c..d521c93761b187a16eb270d7c231c497236377d5 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 {
@@ -601,6 +611,14 @@ sub filter_snapshot_fmts {
                !$known_snapshot_formats{$_}{'disabled'}} @fmts;
 }
 
+# If it is set to code reference, it is code that it is to be run once per
+# request, allowing updating configurations that change with each request,
+# while running other code in config file only once.
+#
+# Otherwise, if it is false then gitweb would process config file only once;
+# if it is true then gitweb config would be run for each request.
+our $per_request_config = 1;
+
 our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM);
 sub evaluate_gitweb_config {
        our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
@@ -707,6 +725,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,17 +1084,27 @@ sub dispatch {
 }
 
 sub reset_timer {
-       our $t0 = [Time::HiRes::gettimeofday()]
+       our $t0 = [ gettimeofday() ]
                if defined $t0;
        our $number_of_git_cmds = 0;
 }
 
+our $first_request = 1;
 sub run_request {
        reset_timer();
 
        evaluate_uri();
-       evaluate_gitweb_config();
-       evaluate_git_version();
+       if ($first_request) {
+               evaluate_gitweb_config();
+               evaluate_git_version();
+       }
+       if ($per_request_config) {
+               if (ref($per_request_config) eq 'CODE') {
+                       $per_request_config->();
+               } elsif (!$first_request) {
+                       evaluate_gitweb_config();
+               }
+       }
        check_loadavg();
 
        # $projectroot and $projects_list might be set in gitweb config file
@@ -1129,6 +1158,7 @@ sub evaluate_argv {
 sub run {
        evaluate_argv();
 
+       $first_request = 1;
        $pre_listen_hook->()
                if $pre_listen_hook;
 
@@ -1141,6 +1171,7 @@ sub run {
 
                $post_dispatch_hook->()
                        if $post_dispatch_hook;
+               $first_request = 0;
 
                last REQUEST if ($is_last_request->());
        }
@@ -1369,6 +1400,13 @@ sub esc_url {
        return $str;
 }
 
+# quote unsafe characters in HTML attributes
+sub esc_attr {
+
+       # for XHTML conformance escaping '"' to '&quot;' is not enough
+       return esc_html(@_);
+}
+
 # replace invalid utf8 character with SUBSTITUTION sequence
 sub esc_html {
        my $str = shift;
@@ -1774,7 +1812,7 @@ sub format_ref_marker {
                                        hash=>$dest
                                )}, $name);
 
-                       $markers .= " <span class=\"$class\" title=\"$ref\">" .
+                       $markers .= " <span class=\"".esc_attr($class)."\" title=\"".esc_attr($ref)."\">" .
                                $link . "</span>";
                }
        }
@@ -1858,7 +1896,7 @@ sub git_get_avatar {
                return $pre_white .
                       "<img width=\"$size\" " .
                            "class=\"avatar\" " .
-                           "src=\"$url\" " .
+                           "src=\"".esc_url($url)."\" " .
                            "alt=\"\" " .
                       "/>" . $post_white;
        } else {
@@ -2569,7 +2607,7 @@ sub git_show_project_tagcloud {
        } else {
                my @tags = sort { $cloud->{$a}->{count} <=> $cloud->{$b}->{count} } keys %$cloud;
                return '<p align="center">' . join (', ', map {
-                       "<a href=\"$home_link?by_tag=$_\">$cloud->{$_}->{topname}</a>"
+                       $cgi->a({-href=>"$home_link?by_tag=$_"}, $cloud->{$_}->{topname})
                } splice(@tags, 0, $count)) . '</p>';
        }
 }
@@ -2759,6 +2797,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 +3233,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 +3252,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;
@@ -3443,11 +3521,11 @@ EOF
        # print out each stylesheet that exist, providing backwards capability
        # for those people who defined $stylesheet in a config file
        if (defined $stylesheet) {
-               print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
+               print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
        } else {
                foreach my $stylesheet (@stylesheets) {
                        next unless $stylesheet;
-                       print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
+                       print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
                }
        }
        if (defined $project) {
@@ -3460,7 +3538,7 @@ EOF
                        my $type = lc($format);
                        my %link_attr = (
                                '-rel' => 'alternate',
-                               '-title' => "$project - $href_params{'-title'} - $format feed",
+                               '-title' => esc_attr("$project - $href_params{'-title'} - $format feed"),
                                '-type' => "application/$type+xml"
                        );
 
@@ -3487,13 +3565,13 @@ EOF
        } else {
                printf('<link rel="alternate" title="%s projects list" '.
                       'href="%s" type="text/plain; charset=utf-8" />'."\n",
-                      $site_name, href(project=>undef, action=>"project_index"));
+                      esc_attr($site_name), href(project=>undef, action=>"project_index"));
                printf('<link rel="alternate" title="%s projects feeds" '.
                       'href="%s" type="text/x-opml" />'."\n",
-                      $site_name, href(project=>undef, action=>"opml"));
+                      esc_attr($site_name), href(project=>undef, action=>"opml"));
        }
        if (defined $favicon) {
-               print qq(<link rel="shortcut icon" href="$favicon" type="image/png" />\n);
+               print qq(<link rel="shortcut icon" href=").esc_url($favicon).qq(" type="image/png" />\n);
        }
 
        print "</head>\n" .
@@ -3506,12 +3584,20 @@ EOF
        print "<div class=\"page_header\">\n" .
              $cgi->a({-href => esc_url($logo_url),
                       -title => $logo_label},
-                     qq(<img src="$logo" width="72" height="27" alt="git" class="logo"/>));
+                     qq(<img src=").esc_url($logo).qq(" width="72" height="27" alt="git" class="logo"/>));
        print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / ";
        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 +3676,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">'.
@@ -3604,7 +3690,7 @@ sub git_footer_html {
                insert_file($site_footer);
        }
 
-       print qq!<script type="text/javascript" src="$javascript"></script>\n!;
+       print qq!<script type="text/javascript" src="!.esc_url($javascript).qq!"></script>\n!;
        if (defined $action &&
            $action eq 'blame_incremental') {
                print qq!<script type="text/javascript">\n!.
@@ -3718,6 +3804,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 +3860,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 +5102,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 +5114,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 +5346,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 +5355,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 +5381,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 +5433,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 +5542,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 +5729,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 +5742,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 +5752,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;
@@ -5624,14 +5901,14 @@ sub git_blob {
        } else {
                print "<div class=\"page_nav\">\n" .
                      "<br/><br/></div>\n" .
-                     "<div class=\"title\">$hash</div>\n";
+                     "<div class=\"title\">".esc_html($hash)."</div>\n";
        }
        git_print_page_path($file_name, "blob", $hash_base);
        print "<div class=\"page_body\">\n";
        if ($mimetype =~ m!^image/!) {
-               print qq!<img type="$mimetype"!;
+               print qq!<img type="!.esc_attr($mimetype).qq!"!;
                if ($file_name) {
-                       print qq! alt="$file_name" title="$file_name"!;
+                       print qq! alt="!.esc_attr($file_name).qq!" title="!.esc_attr($file_name).qq!"!;
                }
                print qq! src="! .
                      href(action=>"blob_plain", hash=>$hash,
@@ -5644,7 +5921,7 @@ sub git_blob {
                        $nr++;
                        $line = untabify($line);
                        printf qq!<div class="pre"><a id="l%i" href="%s#l%i" class="linenr">%4i</a> %s</div>\n!,
-                              $nr, href(-replay => 1), $nr, $nr, $syntax ? $line : esc_html($line, -nbsp=>1);
+                              $nr, esc_attr(href(-replay => 1)), $nr, $nr, $syntax ? $line : esc_html($line, -nbsp=>1);
                }
        }
        close $fd
@@ -5706,7 +5983,7 @@ sub git_tree {
                undef $hash_base;
                print "<div class=\"page_nav\">\n";
                print "<br/><br/></div>\n";
-               print "<div class=\"title\">$hash</div>\n";
+               print "<div class=\"title\">".esc_html($hash)."</div>\n";
        }
        if (defined $file_name) {
                $basedir = $file_name;
@@ -6174,7 +6451,7 @@ sub git_blobdiff {
                        git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
                } else {
                        print "<div class=\"page_nav\"><br/>$formats_nav<br/></div>\n";
-                       print "<div class=\"title\">$hash vs $hash_parent</div>\n";
+                       print "<div class=\"title\">".esc_html("$hash vs $hash_parent")."</div>\n";
                }
                if (defined $file_name) {
                        git_print_page_path($file_name, "blob", $hash_base);
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/help.c b/help.c
index 7f4928e45954465d0401964289952aec4bd59e2a..7654f1bfd608d151a213937614449cc497bb638b 100644 (file)
--- a/help.c
+++ b/help.c
@@ -3,6 +3,7 @@
 #include "exec_cmd.h"
 #include "levenshtein.h"
 #include "help.h"
+#include "common-cmds.h"
 
 /* most GUI terminals set COLUMNS (although some don't export it) */
 static int term_columns(void)
@@ -298,7 +299,8 @@ static void add_cmd_list(struct cmdnames *cmds, struct cmdnames *old)
 }
 
 /* An empirically derived magic number */
-#define SIMILAR_ENOUGH(x) ((x) < 6)
+#define SIMILARITY_FLOOR 7
+#define SIMILAR_ENOUGH(x) ((x) < SIMILARITY_FLOOR)
 
 const char *help_unknown_cmd(const char *cmd)
 {
@@ -319,10 +321,28 @@ const char *help_unknown_cmd(const char *cmd)
              sizeof(main_cmds.names), cmdname_compare);
        uniq(&main_cmds);
 
-       /* This reuses cmdname->len for similarity index */
-       for (i = 0; i < main_cmds.cnt; ++i)
+       /* This abuses cmdname->len for levenshtein distance */
+       for (i = 0, n = 0; i < main_cmds.cnt; i++) {
+               int cmp = 0; /* avoid compiler stupidity */
+               const char *candidate = main_cmds.names[i]->name;
+
+               /* Does the candidate appear in common_cmds list? */
+               while (n < ARRAY_SIZE(common_cmds) &&
+                      (cmp = strcmp(common_cmds[n].name, candidate)) < 0)
+                       n++;
+               if ((n < ARRAY_SIZE(common_cmds)) && !cmp) {
+                       /* Yes, this is one of the common commands */
+                       n++; /* use the entry from common_cmds[] */
+                       if (!prefixcmp(candidate, cmd)) {
+                               /* Give prefix match a very good score */
+                               main_cmds.names[i]->len = 0;
+                               continue;
+                       }
+               }
+
                main_cmds.names[i]->len =
-                       levenshtein(cmd, main_cmds.names[i]->name, 0, 2, 1, 4);
+                       levenshtein(cmd, candidate, 0, 2, 1, 4) + 1;
+       }
 
        qsort(main_cmds.names, main_cmds.cnt,
              sizeof(*main_cmds.names), levenshtein_compare);
@@ -330,10 +350,21 @@ const char *help_unknown_cmd(const char *cmd)
        if (!main_cmds.cnt)
                die ("Uh oh. Your system reports no Git commands at all.");
 
-       best_similarity = main_cmds.names[0]->len;
-       n = 1;
-       while (n < main_cmds.cnt && best_similarity == main_cmds.names[n]->len)
-               ++n;
+       /* skip and count prefix matches */
+       for (n = 0; n < main_cmds.cnt && !main_cmds.names[n]->len; n++)
+               ; /* still counting */
+
+       if (main_cmds.cnt <= n) {
+               /* prefix matches with everything? that is too ambiguous */
+               best_similarity = SIMILARITY_FLOOR + 1;
+       } else {
+               /* count all the most similar ones */
+               for (best_similarity = main_cmds.names[n++]->len;
+                    (n < main_cmds.cnt &&
+                     best_similarity == main_cmds.names[n]->len);
+                    n++)
+                       ; /* still counting */
+       }
        if (autocorrect && n == 1 && SIMILAR_ENOUGH(best_similarity)) {
                const char *assumed = main_cmds.names[0]->name;
                main_cmds.names[0] = NULL;
index 14c90c2e84afd9997e1a6453f0065b3f59b32e57..85015048dd466fb2f2afe12086379ecf579d0a56 100644 (file)
@@ -510,9 +510,7 @@ static char* getdir(void)
                        die("GIT_PROJECT_ROOT is set but PATH_INFO is not");
                if (daemon_avoid_alias(pathinfo))
                        die("'%s': aliased", pathinfo);
-               strbuf_addstr(&buf, root);
-               if (buf.buf[buf.len - 1] != '/')
-                       strbuf_addch(&buf, '/');
+               end_url_with_slash(&buf, root);
                if (pathinfo[0] == '/')
                        pathinfo++;
                strbuf_addstr(&buf, pathinfo);
index 762c750d7af3651287c147034d3dead469453e7c..923904f97f9274d09a0a0b08543492f78a141f79 100644 (file)
@@ -14,8 +14,7 @@ int main(int argc, const char **argv)
        int commits;
        const char **write_ref = NULL;
        char **commit_id;
-       const char *url;
-       char *rewritten_url = NULL;
+       char *url = NULL;
        int arg = 1;
        int rc = 0;
        int get_tree = 0;
@@ -57,19 +56,14 @@ int main(int argc, const char **argv)
                commit_id = (char **) &argv[arg++];
                commits = 1;
        }
-       url = argv[arg];
+
+       if (argv[arg])
+               str_end_url_with_slash(argv[arg], &url);
 
        prefix = setup_git_directory();
 
        git_config(git_default_config, NULL);
 
-       if (url && url[strlen(url)-1] != '/') {
-               rewritten_url = xmalloc(strlen(url)+2);
-               strcpy(rewritten_url, url);
-               strcat(rewritten_url, "/");
-               url = rewritten_url;
-       }
-
        http_init(NULL);
        walker = get_http_walker(url);
        walker->get_tree = get_tree;
@@ -93,7 +87,7 @@ int main(int argc, const char **argv)
        walker_free(walker);
        http_cleanup();
 
-       free(rewritten_url);
+       free(url);
 
        return rc;
 }
index c9bcd116975635cabf30eceb7909abfc7f341948..ff41a0e183dd3a449f5136c6d1488a31b4a8960b 100644 (file)
@@ -1090,6 +1090,10 @@ static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed)
        if (tag_closed) {
                if (!strcmp(ctx->name, DAV_PROPFIND_RESP) && ls->dentry_name) {
                        if (ls->dentry_flags & IS_DIR) {
+
+                               /* ensure collection names end with slash */
+                               str_end_url_with_slash(ls->dentry_name, &ls->dentry_name);
+
                                if (ls->flags & PROCESS_DIRS) {
                                        ls->userFunc(ls);
                                }
@@ -1112,8 +1116,16 @@ static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed)
                                }
                        }
                        if (path) {
-                               path += repo->path_len;
-                               ls->dentry_name = xstrdup(path);
+                               const char *url = repo->url;
+                               if (repo->path)
+                                       url = repo->path;
+                               if (strncmp(path, url, repo->path_len))
+                                       error("Parsed path '%s' does not match url: '%s'\n",
+                                             path, url);
+                               else {
+                                       path += repo->path_len;
+                                       ls->dentry_name = xstrdup(path);
+                               }
                        }
                } else if (!strcmp(ctx->name, DAV_PROPFIND_COLLECTION)) {
                        ls->dentry_flags |= IS_DIR;
@@ -1789,7 +1801,6 @@ int main(int argc, char **argv)
        int new_refs;
        struct ref *ref, *local_refs;
        struct remote *remote;
-       char *rewritten_url = NULL;
 
        git_extract_argv0_path(argv[0]);
 
@@ -1835,8 +1846,8 @@ int main(int argc, char **argv)
                }
                if (!repo->url) {
                        char *path = strstr(arg, "//");
-                       repo->url = arg;
-                       repo->path_len = strlen(arg);
+                       str_end_url_with_slash(arg, &repo->url);
+                       repo->path_len = strlen(repo->url);
                        if (path) {
                                repo->path = strchr(path+2, '/');
                                if (repo->path)
@@ -1872,15 +1883,6 @@ int main(int argc, char **argv)
        remote->url[remote->url_nr++] = repo->url;
        http_init(remote);
 
-       if (repo->url && repo->url[strlen(repo->url)-1] != '/') {
-               rewritten_url = xmalloc(strlen(repo->url)+2);
-               strcpy(rewritten_url, repo->url);
-               strcat(rewritten_url, "/");
-               repo->path = rewritten_url + (repo->path - repo->url);
-               repo->path_len++;
-               repo->url = rewritten_url;
-       }
-
 #ifdef USE_CURL_MULTI
        is_running_queue = 0;
 #endif
@@ -2088,7 +2090,6 @@ int main(int argc, char **argv)
        }
 
  cleanup:
-       free(rewritten_url);
        if (info_ref_lock)
                unlock_remote(info_ref_lock);
        free(repo);
diff --git a/http.c b/http.c
index 0a5011f615bdfade0b69682b177dc97471ac20a3..9e767723ed1045445e91e79489cb8ec8bb861965 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;
        }
 }
 
@@ -728,13 +743,6 @@ static inline int hex(int v)
                return 'A' + v - 10;
 }
 
-void end_url_with_slash(struct strbuf *buf, const char *url)
-{
-       strbuf_addstr(buf, url);
-       if (buf->len && buf->buf[buf->len - 1] != '/')
-               strbuf_addstr(buf, "/");
-}
-
 static char *quote_ref_url(const char *base, const char *ref)
 {
        struct strbuf buf = STRBUF_INIT;
diff --git a/http.h b/http.h
index 173f74c8298c3fa3989f88b06e2085127fe96db8..5c6e243dde3c654a3c0a705146fb15395c27fb95 100644 (file)
--- a/http.h
+++ b/http.h
@@ -8,6 +8,7 @@
 
 #include "strbuf.h"
 #include "remote.h"
+#include "url.h"
 
 /*
  * We detect based on the cURL version if multi-transfer is
@@ -117,7 +118,6 @@ extern void append_remote_object_url(struct strbuf *buf, const char *url,
                                     int only_two_digit_prefix);
 extern char *get_remote_object_url(const char *url, const char *hex,
                                   int only_two_digit_prefix);
-extern void end_url_with_slash(struct strbuf *buf, const char *url);
 
 /* Options for http_request_*() */
 #define HTTP_NO_CACHE          1
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,
index 0fa79bc31d322e2aab8fce738ca6489a35a51ca4..42b51ef14514f3c3df454e4b718718a9b58f1612 100644 (file)
@@ -11,6 +11,13 @@ static int parse_options_usage(struct parse_opt_ctx_t *ctx,
 #define OPT_SHORT 1
 #define OPT_UNSET 2
 
+static int optbug(const struct option *opt, const char *reason)
+{
+       if (opt->long_name)
+               return error("BUG: option '%s' %s", opt->long_name, reason);
+       return error("BUG: switch '%c' %s", opt->short_name, reason);
+}
+
 static int opterror(const struct option *opt, const char *reason, int flags)
 {
        if (flags & OPT_SHORT)
@@ -55,25 +62,13 @@ static int get_value(struct parse_opt_ctx_t *p,
                return opterror(opt, "takes no value", flags);
        if (unset && (opt->flags & PARSE_OPT_NONEG))
                return opterror(opt, "isn't available", flags);
-
-       if (!(flags & OPT_SHORT) && p->opt) {
-               switch (opt->type) {
-               case OPTION_CALLBACK:
-                       if (!(opt->flags & PARSE_OPT_NOARG))
-                               break;
-                       /* FALLTHROUGH */
-               case OPTION_BOOLEAN:
-               case OPTION_BIT:
-               case OPTION_NEGBIT:
-               case OPTION_SET_INT:
-               case OPTION_SET_PTR:
-                       return opterror(opt, "takes no value", flags);
-               default:
-                       break;
-               }
-       }
+       if (!(flags & OPT_SHORT) && p->opt && (opt->flags & PARSE_OPT_NOARG))
+               return opterror(opt, "takes no value", flags);
 
        switch (opt->type) {
+       case OPTION_LOWLEVEL_CALLBACK:
+               return (*(parse_opt_ll_cb *)opt->callback)(p, opt, unset);
+
        case OPTION_BIT:
                if (unset)
                        *(int *)opt->value &= ~opt->defval;
@@ -281,13 +276,6 @@ static int parse_nodash_opt(struct parse_opt_ctx_t *p, const char *arg,
        for (; options->type != OPTION_END; options++) {
                if (!(options->flags & PARSE_OPT_NODASH))
                        continue;
-               if ((options->flags & PARSE_OPT_OPTARG) ||
-                   !(options->flags & PARSE_OPT_NOARG))
-                       die("BUG: dashless options don't support arguments");
-               if (!(options->flags & PARSE_OPT_NONEG))
-                       die("BUG: dashless options don't support negation");
-               if (options->long_name)
-                       die("BUG: dashless options can't be long");
                if (options->short_name == arg[0] && arg[1] == '\0')
                        return get_value(p, options, OPT_SHORT);
        }
@@ -320,25 +308,37 @@ static void parse_options_check(const struct option *opts)
 
        for (; opts->type != OPTION_END; opts++) {
                if ((opts->flags & PARSE_OPT_LASTARG_DEFAULT) &&
-                   (opts->flags & PARSE_OPT_OPTARG)) {
-                       if (opts->long_name) {
-                               error("`--%s` uses incompatible flags "
-                                     "LASTARG_DEFAULT and OPTARG", opts->long_name);
-                       } else {
-                               error("`-%c` uses incompatible flags "
-                                     "LASTARG_DEFAULT and OPTARG", opts->short_name);
-                       }
-                       err |= 1;
+                   (opts->flags & PARSE_OPT_OPTARG))
+                       err |= optbug(opts, "uses incompatible flags "
+                                       "LASTARG_DEFAULT and OPTARG");
+               if (opts->flags & PARSE_OPT_NODASH &&
+                   ((opts->flags & PARSE_OPT_OPTARG) ||
+                    !(opts->flags & PARSE_OPT_NOARG) ||
+                    !(opts->flags & PARSE_OPT_NONEG) ||
+                    opts->long_name))
+                       err |= optbug(opts, "uses feature "
+                                       "not supported for dashless options");
+               switch (opts->type) {
+               case OPTION_BOOLEAN:
+               case OPTION_BIT:
+               case OPTION_NEGBIT:
+               case OPTION_SET_INT:
+               case OPTION_SET_PTR:
+               case OPTION_NUMBER:
+                       if ((opts->flags & PARSE_OPT_OPTARG) ||
+                           !(opts->flags & PARSE_OPT_NOARG))
+                               err |= optbug(opts, "should not accept an argument");
+               default:
+                       ; /* ok. (usually accepts an argument) */
                }
        }
-
        if (err)
-               exit(129);
+               exit(128);
 }
 
 void parse_options_start(struct parse_opt_ctx_t *ctx,
                         int argc, const char **argv, const char *prefix,
-                        int flags)
+                        const struct option *options, int flags)
 {
        memset(ctx, 0, sizeof(*ctx));
        ctx->argc = argc - 1;
@@ -350,6 +350,7 @@ void parse_options_start(struct parse_opt_ctx_t *ctx,
        if ((flags & PARSE_OPT_KEEP_UNKNOWN) &&
            (flags & PARSE_OPT_STOP_AT_NON_OPTION))
                die("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together");
+       parse_options_check(options);
 }
 
 static int usage_with_options_internal(struct parse_opt_ctx_t *,
@@ -362,8 +363,6 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
 {
        int internal_help = !(ctx->flags & PARSE_OPT_NO_INTERNAL_HELP);
 
-       parse_options_check(options);
-
        /* we must reset ->opt, unknown short option leave it dangling */
        ctx->opt = NULL;
 
@@ -374,7 +373,7 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
                        if (parse_nodash_opt(ctx, arg, options) == 0)
                                continue;
                        if (ctx->flags & PARSE_OPT_STOP_AT_NON_OPTION)
-                               break;
+                               return PARSE_OPT_NON_OPTION;
                        ctx->out[ctx->cpidx++] = ctx->argv[0];
                        continue;
                }
@@ -452,10 +451,11 @@ int parse_options(int argc, const char **argv, const char *prefix,
 {
        struct parse_opt_ctx_t ctx;
 
-       parse_options_start(&ctx, argc, argv, prefix, flags);
+       parse_options_start(&ctx, argc, argv, prefix, options, flags);
        switch (parse_options_step(&ctx, options, usagestr)) {
        case PARSE_OPT_HELP:
                exit(129);
+       case PARSE_OPT_NON_OPTION:
        case PARSE_OPT_DONE:
                break;
        default: /* PARSE_OPT_UNKNOWN */
@@ -541,7 +541,8 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,
                if (opts->type == OPTION_NUMBER)
                        pos += fprintf(outfile, "-NUM");
 
-               if (!(opts->flags & PARSE_OPT_NOARG))
+               if ((opts->flags & PARSE_OPT_LITERAL_ARGHELP) ||
+                   !(opts->flags & PARSE_OPT_NOARG))
                        pos += usage_argh(opts, outfile);
 
                if (pos <= USAGE_OPTS_WIDTH)
index ae8647d8932512a49f5a7f9bc9decee34964976b..31ec5d24763b13b3e97c79f7a3edd6e7799bcd91 100644 (file)
@@ -17,6 +17,7 @@ enum parse_opt_type {
        OPTION_STRING,
        OPTION_INTEGER,
        OPTION_CALLBACK,
+       OPTION_LOWLEVEL_CALLBACK,
        OPTION_FILENAME
 };
 
@@ -43,6 +44,10 @@ enum parse_opt_option_flags {
 struct option;
 typedef int parse_opt_cb(const struct option *, const char *arg, int unset);
 
+struct parse_opt_ctx_t;
+typedef int parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
+                               const struct option *opt, int unset);
+
 /*
  * `type`::
  *   holds the type of the option, you must have an OPTION_END last in your
@@ -87,7 +92,8 @@ typedef int parse_opt_cb(const struct option *, const char *arg, int unset);
  *                             useful for users of OPTION_NEGBIT.
  *
  * `callback`::
- *   pointer to the callback to use for OPTION_CALLBACK.
+ *   pointer to the callback to use for OPTION_CALLBACK or
+ *   OPTION_LOWLEVEL_CALLBACK.
  *
  * `defval`::
  *   default value to fill (*->value) with for PARSE_OPT_OPTARG.
@@ -161,6 +167,7 @@ extern NORETURN void usage_msg_opt(const char *msg,
 enum {
        PARSE_OPT_HELP = -1,
        PARSE_OPT_DONE,
+       PARSE_OPT_NON_OPTION,
        PARSE_OPT_UNKNOWN
 };
 
@@ -180,7 +187,7 @@ struct parse_opt_ctx_t {
 
 extern void parse_options_start(struct parse_opt_ctx_t *ctx,
                                int argc, const char **argv, const char *prefix,
-                               int flags);
+                               const struct option *options, int flags);
 
 extern int parse_options_step(struct parse_opt_ctx_t *ctx,
                              const struct option *options,
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 4879615cad7527dc5346cd1a85bd56b9d11e052b..5d81d39a525830f6bacba88143ab6a4552748441 100644 (file)
@@ -239,7 +239,6 @@ void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit)
 
        commit->parents = xcalloc(sizeof(struct commit_list), 1);
        commit->parents->item = commit_info->commit;
-       commit->object.flags &= ~(ADDED | SEEN | SHOWN);
 }
 
 void get_reflog_selector(struct strbuf *sb,
index b1c18906badc5fc4ce3af48daa7c226365e3c08f..ded881263ba0ff703d9fd468e6785270ea68df5f 100644 (file)
@@ -2030,8 +2030,10 @@ static struct commit *get_revision_1(struct rev_info *revs)
                revs->commits = entry->next;
                free(entry);
 
-               if (revs->reflog_info)
+               if (revs->reflog_info) {
                        fake_reflog_parent(revs->reflog_info, commit);
+                       commit->object.flags &= ~(ADDED | SEEN | SHOWN);
+               }
 
                /*
                 * If we haven't done the list limiting, we need to look at
diff --git a/setup.c b/setup.c
index 346ef2eb2d678b742280c0816a09b8ca174ca799..91887a40b79d3bc2c82479bf7f09580c4c468665 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -4,7 +4,7 @@
 static int inside_git_dir = -1;
 static int inside_work_tree = -1;
 
-const char *prefix_path(const char *prefix, int len, const char *path)
+char *prefix_path(const char *prefix, int len, const char *path)
 {
        const char *orig = path;
        char *sanitized = xmalloc(len + strlen(path) + 1);
@@ -512,8 +512,10 @@ const char *setup_git_directory_gently(int *nongit_ok)
        const char *prefix;
 
        prefix = setup_git_directory_gently_1(nongit_ok);
-       if (startup_info)
+       if (startup_info) {
                startup_info->have_repository = !nongit_ok || !*nongit_ok;
+               startup_info->prefix = prefix;
+       }
        return prefix;
 }
 
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..8f49279642bf1aa8ce74fee351772e3fb28a73c4 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"
@@ -1046,6 +1066,23 @@ int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode,
        return ret;
 }
 
+static char *resolve_relative_path(const char *rel)
+{
+       if (prefixcmp(rel, "./") && prefixcmp(rel, "../"))
+               return NULL;
+
+       if (!startup_info)
+               die("BUG: startup_info struct is not initialized.");
+
+       if (!is_inside_work_tree())
+               die("relative path syntax can't be used outside working tree.");
+
+       /* die() inside prefix_path() if resolved path is outside worktree */
+       return prefix_path(startup_info->prefix,
+                          startup_info->prefix ? strlen(startup_info->prefix) : 0,
+                          rel);
+}
+
 int get_sha1_with_context_1(const char *name, unsigned char *sha1,
                            struct object_context *oc,
                            int gently, const char *prefix)
@@ -1060,13 +1097,15 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
        if (!ret)
                return ret;
        /* sha1:path --> object name of path in ent sha1
-        * :path -> object name of path in index
+        * :path -> object name of absolute path in index
+        * :./path -> object name of path relative to cwd in index
         * :[0-3]:path -> object name of path in index at stage
         * :/foo -> recent commit matching foo
         */
        if (name[0] == ':') {
                int stage = 0;
                struct cache_entry *ce;
+               char *new_path = NULL;
                int pos;
                if (namelen > 2 && name[1] == '/')
                        /* don't need mode for commit */
@@ -1079,7 +1118,13 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
                        stage = name[1] - '0';
                        cp = name + 3;
                }
-               namelen = namelen - (cp - name);
+               new_path = resolve_relative_path(cp);
+               if (!new_path) {
+                       namelen = namelen - (cp - name);
+               } else {
+                       cp = new_path;
+                       namelen = strlen(cp);
+               }
 
                strncpy(oc->path, cp,
                        sizeof(oc->path));
@@ -1098,12 +1143,14 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
                        if (ce_stage(ce) == stage) {
                                hashcpy(sha1, ce->sha1);
                                oc->mode = ce->ce_mode;
+                               free(new_path);
                                return 0;
                        }
                        pos++;
                }
                if (!gently)
                        diagnose_invalid_index_path(stage, prefix, cp);
+               free(new_path);
                return -1;
        }
        for (cp = name, bracket_depth = 0; *cp; cp++) {
@@ -1124,6 +1171,11 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
                }
                if (!get_sha1_1(name, cp-name, tree_sha1)) {
                        const char *filename = cp+1;
+                       char *new_filename = NULL;
+
+                       new_filename = resolve_relative_path(filename);
+                       if (new_filename)
+                               filename = new_filename;
                        ret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode);
                        if (!gently) {
                                diagnose_invalid_sha1_path(prefix, filename,
@@ -1135,6 +1187,7 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
                                sizeof(oc->path));
                        oc->path[sizeof(oc->path)-1] = '\0';
 
+                       free(new_filename);
                        return ret;
                } else {
                        if (!gently)
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 91a47587478ae0550be8f41c00cb1749c85834f4..6f1c10722f744f4a27f18dae4867b15ecbf57d49 100644 (file)
@@ -10,7 +10,9 @@
 #include "string-list.h"
 
 struct string_list config_name_for_path;
+struct string_list config_fetch_recurse_submodules_for_name;
 struct string_list config_ignore_for_name;
+static int config_fetch_recurse_submodules;
 
 static int add_submodule_odb(const char *path)
 {
@@ -63,10 +65,14 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
        }
 }
 
-static int submodule_config(const char *var, const char *value, void *cb)
+int submodule_config(const char *var, const char *value, void *cb)
 {
        if (!prefixcmp(var, "submodule."))
                return parse_submodule_config_option(var, value);
+       else if (!strcmp(var, "fetch.recursesubmodules")) {
+               config_fetch_recurse_submodules = git_config_bool(var, value);
+               return 0;
+       }
        return 0;
 }
 
@@ -100,6 +106,14 @@ int parse_submodule_config_option(const char *var, const char *value)
                        config = string_list_append(&config_name_for_path, xstrdup(value));
                config->util = strbuf_detach(&submodname, NULL);
                strbuf_release(&submodname);
+       } else if ((len > 23) && !strcmp(var + len - 23, ".fetchrecursesubmodules")) {
+               strbuf_add(&submodname, var, len - 23);
+               config = unsorted_string_list_lookup(&config_fetch_recurse_submodules_for_name, submodname.buf);
+               if (!config)
+                       config = string_list_append(&config_fetch_recurse_submodules_for_name,
+                                                   strbuf_detach(&submodname, NULL));
+               config->util = git_config_bool(var, value) ? (void *)1 : NULL;
+               strbuf_release(&submodname);
        } else if ((len > 7) && !strcmp(var + len - 7, ".ignore")) {
                if (strcmp(value, "untracked") && strcmp(value, "dirty") &&
                    strcmp(value, "all") && strcmp(value, "none")) {
@@ -229,6 +243,89 @@ void show_submodule_summary(FILE *f, const char *path,
        strbuf_release(&sb);
 }
 
+void set_config_fetch_recurse_submodules(int value)
+{
+       config_fetch_recurse_submodules = value;
+}
+
+int fetch_populated_submodules(int num_options, const char **options,
+                              const char *prefix, int ignore_config,
+                              int quiet)
+{
+       int i, result = 0, argc = 0;
+       struct child_process cp;
+       const char **argv;
+       struct string_list_item *name_for_path;
+       const char *work_tree = get_git_work_tree();
+       if (!work_tree)
+               return 0;
+
+       if (!the_index.initialized)
+               if (read_cache() < 0)
+                       die("index file corrupt");
+
+       /* 4: "fetch" (options) "--submodule-prefix" prefix NULL */
+       argv = xcalloc(num_options + 4, sizeof(const char *));
+       argv[argc++] = "fetch";
+       for (i = 0; i < num_options; i++)
+               argv[argc++] = options[i];
+       argv[argc++] = "--submodule-prefix";
+
+       memset(&cp, 0, sizeof(cp));
+       cp.argv = argv;
+       cp.env = local_repo_env;
+       cp.git_cmd = 1;
+       cp.no_stdin = 1;
+
+       for (i = 0; i < active_nr; i++) {
+               struct strbuf submodule_path = STRBUF_INIT;
+               struct strbuf submodule_git_dir = STRBUF_INIT;
+               struct strbuf submodule_prefix = STRBUF_INIT;
+               struct cache_entry *ce = active_cache[i];
+               const char *git_dir, *name;
+
+               if (!S_ISGITLINK(ce->ce_mode))
+                       continue;
+
+               name = ce->name;
+               name_for_path = unsorted_string_list_lookup(&config_name_for_path, ce->name);
+               if (name_for_path)
+                       name = name_for_path->util;
+
+               if (!ignore_config) {
+                       struct string_list_item *fetch_recurse_submodules_option;
+                       fetch_recurse_submodules_option = unsorted_string_list_lookup(&config_fetch_recurse_submodules_for_name, name);
+                       if (fetch_recurse_submodules_option) {
+                               if (!fetch_recurse_submodules_option->util)
+                                       continue;
+                       } else {
+                               if (!config_fetch_recurse_submodules)
+                                       continue;
+                       }
+               }
+
+               strbuf_addf(&submodule_path, "%s/%s", work_tree, ce->name);
+               strbuf_addf(&submodule_git_dir, "%s/.git", submodule_path.buf);
+               strbuf_addf(&submodule_prefix, "%s%s/", prefix, ce->name);
+               git_dir = read_gitfile_gently(submodule_git_dir.buf);
+               if (!git_dir)
+                       git_dir = submodule_git_dir.buf;
+               if (is_directory(git_dir)) {
+                       if (!quiet)
+                               printf("Fetching submodule %s%s\n", prefix, ce->name);
+                       cp.dir = submodule_path.buf;
+                       argv[argc] = submodule_prefix.buf;
+                       if (run_command(&cp))
+                               result = 1;
+               }
+               strbuf_release(&submodule_path);
+               strbuf_release(&submodule_git_dir);
+               strbuf_release(&submodule_prefix);
+       }
+       free(argv);
+       return result;
+}
+
 unsigned is_submodule_modified(const char *path, int ignore_untracked)
 {
        ssize_t len;
index 386f410a66d9c431a61c0e26c773f1b3452f0d09..4729023aa5bbd7d7c95981b995c379407ba6423d 100644 (file)
@@ -5,6 +5,7 @@ struct diff_options;
 
 void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
                const char *path);
+int submodule_config(const char *var, const char *value, void *cb);
 void gitmodules_config();
 int parse_submodule_config_option(const char *var, const char *value);
 void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *);
@@ -12,6 +13,10 @@ void show_submodule_summary(FILE *f, const char *path,
                unsigned char one[20], unsigned char two[20],
                unsigned dirty_submodule,
                const char *del, const char *add, const char *reset);
+void set_config_fetch_recurse_submodules(int value);
+int fetch_populated_submodules(int num_options, const char **options,
+                              const char *prefix, int ignore_config,
+                              int quiet);
 unsigned is_submodule_modified(const char *path, int ignore_untracked);
 int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],
                    const unsigned char a[20], const unsigned char b[20]);
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 73c6ec473da2d13b9e5bf3d6feb7cfca4937e109..47cbeb6e68ec910a691f2feca18b7b160efd0ced 100644 (file)
@@ -23,10 +23,10 @@ TGITWEB = $(wildcard t95[0-9][0-9]-*.sh)
 
 all: $(DEFAULT_TEST_TARGET)
 
-test: pre-clean
+test: pre-clean $(TEST_LINT)
        $(MAKE) aggregate-results-and-cleanup
 
-prove: pre-clean
+prove: pre-clean $(TEST_LINT)
        @echo "*** prove ***"; GIT_CONFIG=.git/config $(PROVE) --exec '$(SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS)
        $(MAKE) clean
 
@@ -41,6 +41,18 @@ clean:
        $(RM) -r valgrind/bin
        $(RM) .prove
 
+test-lint: test-lint-duplicates test-lint-executable
+
+test-lint-duplicates:
+       @dups=`echo $(T) | tr ' ' '\n' | sed 's/-.*//' | sort | uniq -d` && \
+               test -z "$$dups" || { \
+               echo >&2 "duplicate test numbers:" $$dups; exit 1; }
+
+test-lint-executable:
+       @bad=`for i in $(T); do test -x "$$i" || echo $$i; done` && \
+               test -z "$$bad" || { \
+               echo >&2 "non-executable tests:" $$bad; exit 1; }
+
 aggregate-results-and-cleanup: $(T)
        $(MAKE) aggregate-results
        $(MAKE) clean
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/t1412-reflog-loop.sh b/t/t1412-reflog-loop.sh
new file mode 100755 (executable)
index 0000000..7f519e5
--- /dev/null
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+test_description='reflog walk shows repeated commits again'
+. ./test-lib.sh
+
+test_expect_success 'setup commits' '
+       test_tick &&
+       echo content >file && git add file && git commit -m one &&
+       git tag one &&
+       echo content >>file && git add file && git commit -m two &&
+       git tag two
+'
+
+test_expect_success 'setup reflog with alternating commits' '
+       git checkout -b topic &&
+       git reset one &&
+       git reset two &&
+       git reset one &&
+       git reset two
+'
+
+test_expect_success 'reflog shows all entries' '
+       cat >expect <<-\EOF
+               topic@{0} two: updating HEAD
+               topic@{1} one: updating HEAD
+               topic@{2} two: updating HEAD
+               topic@{3} one: updating HEAD
+               topic@{4} branch: Created from HEAD
+       EOF
+       git log -g --format="%gd %gs" topic >actual &&
+       test_cmp expect actual
+'
+
+test_done
index 0eeeb0e45090661c0576607792ff9b91d096e282..9f8adb1f824a5bafe8829e5ab613e2ab83065f4f 100755 (executable)
@@ -31,6 +31,67 @@ test_expect_success 'correct file objects' '
         test $HASH_file = $(git rev-parse :0:file.txt) )
 '
 
+test_expect_success 'correct relative file objects (0)' '
+       git rev-parse :file.txt >expected &&
+       git rev-parse :./file.txt >result &&
+       test_cmp expected result &&
+       git rev-parse :0:./file.txt >result &&
+       test_cmp expected result
+'
+
+test_expect_success 'correct relative file objects (1)' '
+       git rev-parse HEAD:file.txt >expected &&
+       git rev-parse HEAD:./file.txt >result &&
+       test_cmp expected result
+'
+
+test_expect_success 'correct relative file objects (2)' '
+       (
+               cd subdir &&
+               git rev-parse HEAD:../file.txt >result &&
+               test_cmp ../expected result
+       )
+'
+
+test_expect_success 'correct relative file objects (3)' '
+       (
+               cd subdir &&
+               git rev-parse HEAD:../subdir/../file.txt >result &&
+               test_cmp ../expected result
+       )
+'
+
+test_expect_success 'correct relative file objects (4)' '
+       git rev-parse HEAD:subdir/file.txt >expected &&
+       (
+               cd subdir &&
+               git rev-parse HEAD:./file.txt >result &&
+               test_cmp ../expected result
+       )
+'
+
+test_expect_success 'correct relative file objects (5)' '
+       git rev-parse :subdir/file.txt >expected &&
+       (
+               cd subdir &&
+               git rev-parse :./file.txt >result &&
+               test_cmp ../expected result &&
+               git rev-parse :0:./file.txt >result &&
+               test_cmp ../expected result
+       )
+'
+
+test_expect_success 'correct relative file objects (6)' '
+       git rev-parse :file.txt >expected &&
+       (
+               cd subdir &&
+               git rev-parse :../file.txt >result &&
+               test_cmp ../expected result &&
+               git rev-parse :0:../file.txt >result &&
+               test_cmp ../expected result
+       )
+'
+
 test_expect_success 'incorrect revision id' '
        test_must_fail git rev-parse foobar:file.txt 2>error &&
        grep "Invalid object name '"'"'foobar'"'"'." error &&
@@ -75,4 +136,29 @@ test_expect_success 'invalid @{n} reference' '
        grep "fatal: Log for [^ ]* only has [0-9][0-9]* entries." error
 '
 
+test_expect_success 'relative path not found' '
+       (
+               cd subdir &&
+               test_must_fail git rev-parse HEAD:./nonexistent.txt 2>error &&
+               grep subdir/nonexistent.txt error
+       )
+'
+
+test_expect_success 'relative path outside worktree' '
+       test_must_fail git rev-parse HEAD:../file.txt >output 2>error &&
+       test -z "$(cat output)" &&
+       grep "outside repository" error
+'
+
+test_expect_success 'relative path when cwd is outside worktree' '
+       test_must_fail git --git-dir=.git --work-tree=subdir rev-parse HEAD:./file.txt >output 2>error &&
+       test -z "$(cat output)" &&
+       grep "relative path syntax can.t be used outside working tree." error
+'
+
+test_expect_success 'relative path when startup_info is NULL' '
+       test_must_fail test-match-trees HEAD:./file.txt HEAD:./file.txt 2>error &&
+       grep "BUG: startup_info struct is not initialized." error
+'
+
 test_done
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 fbb3f2e0dfcf1a0673dbd2022a4ed843990fce52..e573dc845b3d72004b2a96f344528c68977c52e1 100755 (executable)
@@ -72,6 +72,18 @@ testrebase() {
                test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) &&
                test ! -d "$dotest"
        '
+
+       test_expect_success "rebase$type --abort does not update reflog" '
+               cd "$work_dir" &&
+               # Clean up the state from the previous one
+               git reset --hard pre-rebase &&
+               git reflog show to-rebase > reflog_before &&
+               test_must_fail git rebase$type master &&
+               git rebase --abort &&
+               git reflog show to-rebase > reflog_after &&
+               test_cmp reflog_before reflog_after &&
+               rm reflog_before reflog_after
+       '
 }
 
 testrebase "" .git/rebase-apply
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 07bf6eb49dd2879758f64a8c09a0ae8d7e54dd45..027c13d52cd701ba28e3c5e29c5431acfccdad73 100755 (executable)
@@ -6,6 +6,7 @@
 test_description='various format-patch tests'
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
 
 test_expect_success setup '
 
@@ -686,4 +687,26 @@ test_expect_success 'format-patch --signature="" supresses signatures' '
        ! grep "^-- \$" output
 '
 
+test_expect_success TTY 'format-patch --stdout paginates' '
+       rm -f pager_used &&
+       (
+               GIT_PAGER="wc >pager_used" &&
+               export GIT_PAGER &&
+               test_terminal git format-patch --stdout --all
+       ) &&
+       test_path_is_file pager_used
+'
+
+ test_expect_success TTY 'format-patch --stdout pagination can be disabled' '
+       rm -f pager_used &&
+       (
+               GIT_PAGER="wc >pager_used" &&
+               export GIT_PAGER &&
+               test_terminal git --no-pager format-patch --stdout --all &&
+               test_terminal git -c "pager.format-patch=false" format-patch --stdout --all
+       ) &&
+       test_path_is_missing pager_used &&
+       test_path_is_missing .git/pager_used
+'
+
 test_done
index 8622eb51c70acf73ea1f2173a946b1a5ade6a221..9059bcd69eec7a718b6017356c82ec163875c042 100755 (executable)
@@ -344,6 +344,13 @@ test_expect_success 'check spaces as indentation (indent-with-non-tab: on)' '
 
 '
 
+test_expect_success 'ditto, but tabwidth=9' '
+
+       git config core.whitespace "indent-with-non-tab,tabwidth=9" &&
+       git diff --check
+
+'
+
 test_expect_success 'check tabs and spaces as indentation (indent-with-non-tab: on)' '
 
        git config core.whitespace "indent-with-non-tab" &&
@@ -352,6 +359,20 @@ test_expect_success 'check tabs and spaces as indentation (indent-with-non-tab:
 
 '
 
+test_expect_success 'ditto, but tabwidth=10' '
+
+       git config core.whitespace "indent-with-non-tab,tabwidth=10" &&
+       test_must_fail git diff --check
+
+'
+
+test_expect_success 'ditto, but tabwidth=20' '
+
+       git config core.whitespace "indent-with-non-tab,tabwidth=20" &&
+       git diff --check
+
+'
+
 test_expect_success 'check tabs as indentation (tab-in-indent: off)' '
 
        git config core.whitespace "-tab-in-indent" &&
@@ -376,6 +397,13 @@ test_expect_success 'check tabs and spaces as indentation (tab-in-indent: on)' '
 
 '
 
+test_expect_success 'ditto, but tabwidth=1 (must be irrelevant)' '
+
+       git config core.whitespace "tab-in-indent,tabwidth=1" &&
+       test_must_fail git diff --check
+
+'
+
 test_expect_success 'check tab-in-indent and indent-with-non-tab conflict' '
 
        git config core.whitespace "tab-in-indent,indent-with-non-tab" &&
index f7c85ec604df5e037990065ab9d3d82d86587076..a5019759bc7593cf8affd8625f61bbd7ab1b9655 100755 (executable)
@@ -51,8 +51,65 @@ test_expect_success default '
 
 '
 
+test_expect_success 'default (attribute)' '
+
+       test_might_fail git config --unset core.whitespace &&
+       echo "F whitespace" >.gitattributes &&
+       prepare_output &&
+
+       grep Eight error >/dev/null &&
+       grep HT error >/dev/null &&
+       grep With error >/dev/null &&
+       grep Return error >/dev/null &&
+       grep No normal >/dev/null
+
+'
+
+test_expect_success 'default, tabwidth=10 (attribute)' '
+
+       git config core.whitespace "tabwidth=10" &&
+       echo "F whitespace" >.gitattributes &&
+       prepare_output &&
+
+       grep Eight normal >/dev/null &&
+       grep HT error >/dev/null &&
+       grep With error >/dev/null &&
+       grep Return error >/dev/null &&
+       grep No normal >/dev/null
+
+'
+
+test_expect_success 'no check (attribute)' '
+
+       test_might_fail git config --unset core.whitespace &&
+       echo "F -whitespace" >.gitattributes &&
+       prepare_output &&
+
+       grep Eight normal >/dev/null &&
+       grep HT normal >/dev/null &&
+       grep With normal >/dev/null &&
+       grep Return normal >/dev/null &&
+       grep No normal >/dev/null
+
+'
+
+test_expect_success 'no check, tabwidth=10 (attribute), must be irrelevant' '
+
+       git config core.whitespace "tabwidth=10" &&
+       echo "F -whitespace" >.gitattributes &&
+       prepare_output &&
+
+       grep Eight normal >/dev/null &&
+       grep HT normal >/dev/null &&
+       grep With normal >/dev/null &&
+       grep Return normal >/dev/null &&
+       grep No normal >/dev/null
+
+'
+
 test_expect_success 'without -trail' '
 
+       rm -f .gitattributes &&
        git config core.whitespace -trail &&
        prepare_output &&
 
@@ -134,6 +191,34 @@ test_expect_success 'with indent-non-tab only (attribute)' '
 
 '
 
+test_expect_success 'with indent-non-tab only, tabwidth=10' '
+
+       rm -f .gitattributes &&
+       git config core.whitespace indent,tabwidth=10,-trailing,-space &&
+       prepare_output &&
+
+       grep Eight normal >/dev/null &&
+       grep HT normal >/dev/null &&
+       grep With normal >/dev/null &&
+       grep Return normal >/dev/null &&
+       grep No normal >/dev/null
+
+'
+
+test_expect_success 'with indent-non-tab only, tabwidth=10 (attribute)' '
+
+       test_might_fail git config --unset core.whitespace &&
+       echo "F whitespace=indent,-trailing,-space,tabwidth=10" >.gitattributes &&
+       prepare_output &&
+
+       grep Eight normal >/dev/null &&
+       grep HT normal >/dev/null &&
+       grep With normal >/dev/null &&
+       grep Return normal >/dev/null &&
+       grep No normal >/dev/null
+
+'
+
 test_expect_success 'with cr-at-eol' '
 
        rm -f .gitattributes &&
@@ -179,6 +264,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 61bfc569c37eefa6dba4dd6f26513c61d71cfb9c..6f6ee88b28bc5417035b45d87aaf4a9c974ab6c5 100755 (executable)
@@ -10,7 +10,8 @@ prepare_test_file () {
        #       X  RULE
        #       !  trailing-space
        #       @  space-before-tab
-       #       #  indent-with-non-tab
+       #       #  indent-with-non-tab (default tab width 8)
+       #       =  indent-with-non-tab,tabwidth=16
        #       %  tab-in-indent
        sed -e "s/_/ /g" -e "s/>/       /" <<-\EOF
                An_SP in an ordinary line>and a HT.
@@ -25,8 +26,8 @@ prepare_test_file () {
                ________>_Eight SP, a HT and a SP (@#%).
                _______________Fifteen SP (#).
                _______________>Fifteen SP and a HT (@#%).
-               ________________Sixteen SP (#).
-               ________________>Sixteen SP and a HT (@#%).
+               ________________Sixteen SP (#=).
+               ________________>Sixteen SP and a HT (@#%=).
                _____a__Five SP, a non WS, two SP.
                A line with a (!) trailing SP_
                A line with a (!) trailing HT>
@@ -121,6 +122,34 @@ test_expect_success 'whitespace=error-all, no rule (attribute)' '
 
 '
 
+test_expect_success 'spaces inserted by tab-in-indent' '
+
+       git config core.whitespace -trailing,-space,-indent,tab &&
+       rm -f .gitattributes &&
+       test_fix % &&
+       sed -e "s/_/ /g" -e "s/>/       /" <<-\EOF >expect &&
+               An_SP in an ordinary line>and a HT.
+               ________A HT (%).
+               ________A SP and a HT (@%).
+               _________A SP, a HT and a SP (@%).
+               _______Seven SP.
+               ________Eight SP (#).
+               ________Seven SP and a HT (@%).
+               ________________Eight SP and a HT (@#%).
+               _________Seven SP, a HT and a SP (@%).
+               _________________Eight SP, a HT and a SP (@#%).
+               _______________Fifteen SP (#).
+               ________________Fifteen SP and a HT (@#%).
+               ________________Sixteen SP (#=).
+               ________________________Sixteen SP and a HT (@#%=).
+               _____a__Five SP, a non WS, two SP.
+               A line with a (!) trailing SP_
+               A line with a (!) trailing HT>
+       EOF
+       test_cmp expect target
+
+'
+
 for t in - ''
 do
        case "$t" in '') tt='!' ;; *) tt= ;; esac
@@ -129,7 +158,7 @@ do
                case "$s" in '') ts='@' ;; *) ts= ;; esac
                for i in - ''
                do
-                       case "$i" in '') ti='#' ;; *) ti= ;; esac
+                       case "$i" in '') ti='#' ti16='=';; *) ti= ti16= ;; esac
                        for h in - ''
                        do
                                [ -z "$h$i" ] && continue
@@ -142,12 +171,22 @@ do
                                        test_fix "$tt$ts$ti$th"
                                '
 
+                               test_expect_success "rule=$rule,tabwidth=16" '
+                                       git config core.whitespace "$rule,tabwidth=16" &&
+                                       test_fix "$tt$ts$ti16$th"
+                               '
+
                                test_expect_success "rule=$rule (attributes)" '
                                        git config --unset core.whitespace &&
                                        echo "target whitespace=$rule" >.gitattributes &&
                                        test_fix "$tt$ts$ti$th"
                                '
 
+                               test_expect_success "rule=$rule,tabwidth=16 (attributes)" '
+                                       echo "target whitespace=$rule,tabwidth=16" >.gitattributes &&
+                                       test_fix "$tt$ts$ti16$th"
+                               '
+
                        done
                done
        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 0b489f5b1227268c050c1256d105d57d871f5698..0470a81be0edaf883788d313778ef6865ed34c6f 100755 (executable)
@@ -222,4 +222,11 @@ test_expect_success 'git pull --rebase does not reapply old patches' '
        )
 '
 
+test_expect_success 'git pull --rebase against local branch' '
+       git checkout -b copy2 to-rebase-orig &&
+       git pull --rebase . to-rebase &&
+       test "conflicting modification" = "$(cat file)" &&
+       test file = "$(cat file2)"
+'
+
 test_done
diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh
new file mode 100755 (executable)
index 0000000..884a5e5
--- /dev/null
@@ -0,0 +1,195 @@
+#!/bin/sh
+# Copyright (c) 2010, Jens Lehmann
+
+test_description='Recursive "git fetch" for submodules'
+
+. ./test-lib.sh
+
+pwd=$(pwd)
+
+add_upstream_commit() {
+       (
+               cd submodule &&
+               head1=$(git rev-parse --short HEAD) &&
+               echo new >> subfile &&
+               test_tick &&
+               git add subfile &&
+               git commit -m new subfile &&
+               head2=$(git rev-parse --short HEAD) &&
+               echo "From $pwd/submodule" > ../expect.err &&
+               echo "   $head1..$head2  master     -> origin/master" >> ../expect.err
+       ) &&
+       (
+               cd deepsubmodule &&
+               head1=$(git rev-parse --short HEAD) &&
+               echo new >> deepsubfile &&
+               test_tick &&
+               git add deepsubfile &&
+               git commit -m new deepsubfile &&
+               head2=$(git rev-parse --short HEAD) &&
+               echo "From $pwd/deepsubmodule" >> ../expect.err &&
+               echo "   $head1..$head2  master     -> origin/master" >> ../expect.err
+       )
+}
+
+test_expect_success setup '
+       mkdir deepsubmodule &&
+       (
+               cd deepsubmodule &&
+               git init &&
+               echo deepsubcontent > deepsubfile &&
+               git add deepsubfile &&
+               git commit -m new deepsubfile
+       ) &&
+       mkdir submodule &&
+       (
+               cd submodule &&
+               git init &&
+               echo subcontent > subfile &&
+               git add subfile &&
+               git submodule add "$pwd/deepsubmodule" deepsubmodule &&
+               git commit -a -m new
+       ) &&
+       git submodule add "$pwd/submodule" submodule &&
+       git commit -am initial &&
+       git clone . downstream &&
+       (
+               cd downstream &&
+               git submodule update --init --recursive
+       ) &&
+       echo "Fetching submodule submodule" > expect.out &&
+       echo "Fetching submodule submodule/deepsubmodule" >> expect.out
+'
+
+test_expect_success "fetch --recurse-submodules recurses into submodules" '
+       add_upstream_commit &&
+       (
+               cd downstream &&
+               git fetch --recurse-submodules >../actual.out 2>../actual.err
+       ) &&
+       test_cmp expect.out actual.out &&
+       test_cmp expect.err actual.err
+'
+
+test_expect_success "fetch alone only fetches superproject" '
+       add_upstream_commit &&
+       (
+               cd downstream &&
+               git fetch >../actual.out 2>../actual.err
+       ) &&
+       ! test -s actual.out &&
+       ! test -s actual.err
+'
+
+test_expect_success "fetch --no-recurse-submodules only fetches superproject" '
+       (
+               cd downstream &&
+               git fetch --no-recurse-submodules >../actual.out 2>../actual.err
+       ) &&
+       ! test -s actual.out &&
+       ! test -s actual.err
+'
+
+test_expect_success "using fetchRecurseSubmodules=true in .gitmodules recurses into submodules" '
+       (
+               cd downstream &&
+               git config -f .gitmodules submodule.submodule.fetchRecurseSubmodules true &&
+               git fetch >../actual.out 2>../actual.err
+       ) &&
+       test_cmp expect.out actual.out &&
+       test_cmp expect.err actual.err
+'
+
+test_expect_success "--no-recurse-submodules overrides .gitmodules config" '
+       add_upstream_commit &&
+       (
+               cd downstream &&
+               git fetch --no-recurse-submodules >../actual.out 2>../actual.err
+       ) &&
+       ! test -s actual.out &&
+       ! test -s actual.err
+'
+
+test_expect_success "using fetchRecurseSubmodules=false in .git/config overrides setting in .gitmodules" '
+       (
+               cd downstream &&
+               git config submodule.submodule.fetchRecurseSubmodules false &&
+               git fetch >../actual.out 2>../actual.err
+       ) &&
+       ! test -s actual.out &&
+       ! test -s actual.err
+'
+
+test_expect_success "--recurse-submodules overrides fetchRecurseSubmodules setting from .git/config" '
+       (
+               cd downstream &&
+               git fetch --recurse-submodules >../actual.out 2>../actual.err &&
+               git config -f --unset .gitmodules submodule.submodule.fetchRecurseSubmodules true &&
+               git config --unset submodule.submodule.fetchRecurseSubmodules
+       ) &&
+       test_cmp expect.out actual.out &&
+       test_cmp expect.err actual.err
+'
+
+test_expect_success "--quiet propagates to submodules" '
+       (
+               cd downstream &&
+               git fetch --recurse-submodules --quiet >../actual.out 2>../actual.err
+       ) &&
+       ! test -s actual.out &&
+       ! test -s actual.err
+'
+
+test_expect_success "--dry-run propagates to submodules" '
+       add_upstream_commit &&
+       (
+               cd downstream &&
+               git fetch --recurse-submodules --dry-run >../actual.out 2>../actual.err
+       ) &&
+       test_cmp expect.out actual.out &&
+       test_cmp expect.err actual.err &&
+       (
+               cd downstream &&
+               git fetch --recurse-submodules >../actual.out 2>../actual.err
+       ) &&
+       test_cmp expect.out actual.out &&
+       test_cmp expect.err actual.err
+'
+
+test_expect_success "recurseSubmodules=true propagates into submodules" '
+       add_upstream_commit &&
+       (
+               cd downstream &&
+               git config fetch.recurseSubmodules true
+               git fetch >../actual.out 2>../actual.err
+       ) &&
+       test_cmp expect.out actual.out &&
+       test_cmp expect.err actual.err
+'
+
+test_expect_success "--recurse-submodules overrides config in submodule" '
+       add_upstream_commit &&
+       (
+               cd downstream &&
+               (
+                       cd submodule &&
+                       git config fetch.recurseSubmodules false
+               ) &&
+               git fetch --recurse-submodules >../actual.out 2>../actual.err
+       ) &&
+       test_cmp expect.out actual.out &&
+       test_cmp expect.err actual.err
+'
+
+test_expect_success "--no-recurse-submodules overrides config setting" '
+       add_upstream_commit &&
+       (
+               cd downstream &&
+               git config fetch.recurseSubmodules true
+               git fetch --no-recurse-submodules >../actual.out 2>../actual.err
+       ) &&
+       ! test -s actual.out &&
+       ! test -s actual.err
+'
+
+test_done
index 2fb48d09edb47129d9cea8b9686e46a6d8f35615..a1883ca6b649a236e5e08a5e4b76d1479353dcac 100755 (executable)
@@ -30,18 +30,37 @@ test_expect_success 'create http-accessible bare repository' '
 '
 
 test_expect_success 'clone http repository' '
-       git clone $HTTPD_URL/dumb/repo.git clone &&
+       git clone $HTTPD_URL/dumb/repo.git clone-tmpl &&
+       cp -R clone-tmpl clone &&
        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 &&
-       git push public
+       git push public &&
        (cd clone && git pull) &&
        test_cmp file clone/file
 '
 
+test_expect_success 'fetch changes via manual http-fetch' '
+       cp -R clone-tmpl clone2 &&
+
+       HEAD=$(git rev-parse --verify HEAD) &&
+       (cd clone2 &&
+        git http-fetch -a -w heads/master-new $HEAD $(git config remote.origin.url) &&
+        git checkout master-new &&
+        test $HEAD = $(git rev-parse --verify HEAD)) &&
+       test_cmp file clone2/file
+'
+
 test_expect_success 'http remote detects correct HEAD' '
        git push public master:other &&
        (cd clone &&
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 782b0a3ece7724bf8df7cf4d405715a42ad21d44..2c49db9f6244225db7f82b574f21f05b58bfdc26 100755 (executable)
@@ -421,11 +421,29 @@ test_expect_success 'add submodules without specifying an explicit path' '
                git commit -m "repo commit 1"
        ) &&
        git clone --bare repo/ bare.git &&
-       cd addtest &&
-       git submodule add "$submodurl/repo" &&
-       git config -f .gitmodules submodule.repo.path repo &&
-       git submodule add "$submodurl/bare.git" &&
-       git config -f .gitmodules submodule.bare.path bare
+       (
+               cd addtest &&
+               git submodule add "$submodurl/repo" &&
+               git config -f .gitmodules submodule.repo.path repo &&
+               git submodule add "$submodurl/bare.git" &&
+               git config -f .gitmodules submodule.bare.path bare
+       )
+'
+
+test_expect_success 'add should fail when path is used by a file' '
+       (
+               cd addtest &&
+               touch file &&
+               test_must_fail  git submodule add "$submodurl/repo" file
+       )
+'
+
+test_expect_success 'add should fail when path is used by an existing directory' '
+       (
+               cd addtest &&
+               mkdir empty-dir &&
+               test_must_fail git submodule add "$submodurl/repo" empty-dir
+       )
 '
 
 test_done
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..f1dc5c3b6a36507e664e624bf9cebb1634bce335 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 &&
@@ -381,12 +405,13 @@ test_expect_success 'status --porcelain ignores relative paths setting' '
 
 test_expect_success 'setup unique colors' '
 
-       git config status.color.untracked blue
+       git config status.color.untracked blue &&
+       git config status.color.branch green
 
 '
 
 cat >expect <<\EOF
-# On branch master
+# On branch <GREEN>master<RESET>
 # Changes to be committed:
 #   (use "git reset HEAD <file>..." to unstage)
 #
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
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
 
diff --git a/t/t7611-merge-abort.sh b/t/t7611-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 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
diff --git a/t/t8003-blame-corner-cases.sh b/t/t8003-blame-corner-cases.sh
new file mode 100755 (executable)
index 0000000..230143c
--- /dev/null
@@ -0,0 +1,188 @@
+#!/bin/sh
+
+test_description='git blame corner cases'
+. ./test-lib.sh
+
+pick_fc='s/^[0-9a-f^]* *\([^ ]*\) *(\([^ ]*\) .*/\1-\2/'
+
+test_expect_success setup '
+
+       echo A A A A A >one &&
+       echo B B B B B >two &&
+       echo C C C C C >tres &&
+       echo ABC >mouse &&
+       for i in 1 2 3 4 5 6 7 8 9
+       do
+               echo $i
+       done >nine_lines &&
+       for i in 1 2 3 4 5 6 7 8 9 a
+       do
+               echo $i
+       done >ten_lines &&
+       git add one two tres mouse nine_lines ten_lines &&
+       test_tick &&
+       GIT_AUTHOR_NAME=Initial git commit -m Initial &&
+
+       cat one >uno &&
+       mv two dos &&
+       cat one >>tres &&
+       echo DEF >>mouse
+       git add uno dos tres mouse &&
+       test_tick &&
+       GIT_AUTHOR_NAME=Second git commit -a -m Second &&
+
+       echo GHIJK >>mouse &&
+       git add mouse &&
+       test_tick &&
+       GIT_AUTHOR_NAME=Third git commit -m Third &&
+
+       cat mouse >cow &&
+       git add cow &&
+       test_tick &&
+       GIT_AUTHOR_NAME=Fourth git commit -m Fourth &&
+
+       {
+               echo ABC
+               echo DEF
+               echo XXXX
+               echo GHIJK
+       } >cow &&
+       git add cow &&
+       test_tick &&
+       GIT_AUTHOR_NAME=Fifth git commit -m Fifth
+'
+
+test_expect_success 'straight copy without -C' '
+
+       git blame uno | grep Second
+
+'
+
+test_expect_success 'straight move without -C' '
+
+       git blame dos | grep Initial
+
+'
+
+test_expect_success 'straight copy with -C' '
+
+       git blame -C1 uno | grep Second
+
+'
+
+test_expect_success 'straight move with -C' '
+
+       git blame -C1 dos | grep Initial
+
+'
+
+test_expect_success 'straight copy with -C -C' '
+
+       git blame -C -C1 uno | grep Initial
+
+'
+
+test_expect_success 'straight move with -C -C' '
+
+       git blame -C -C1 dos | grep Initial
+
+'
+
+test_expect_success 'append without -C' '
+
+       git blame -L2 tres | grep Second
+
+'
+
+test_expect_success 'append with -C' '
+
+       git blame -L2 -C1 tres | grep Second
+
+'
+
+test_expect_success 'append with -C -C' '
+
+       git blame -L2 -C -C1 tres | grep Second
+
+'
+
+test_expect_success 'append with -C -C -C' '
+
+       git blame -L2 -C -C -C1 tres | grep Initial
+
+'
+
+test_expect_success 'blame wholesale copy' '
+
+       git blame -f -C -C1 HEAD^ -- cow | sed -e "$pick_fc" >current &&
+       {
+               echo mouse-Initial
+               echo mouse-Second
+               echo mouse-Third
+       } >expected &&
+       test_cmp expected current
+
+'
+
+test_expect_success 'blame wholesale copy and more' '
+
+       git blame -f -C -C1 HEAD -- cow | sed -e "$pick_fc" >current &&
+       {
+               echo mouse-Initial
+               echo mouse-Second
+               echo cow-Fifth
+               echo mouse-Third
+       } >expected &&
+       test_cmp expected current
+
+'
+
+test_expect_success 'blame path that used to be a directory' '
+       mkdir path &&
+       echo A A A A A >path/file &&
+       echo B B B B B >path/elif &&
+       git add path &&
+       test_tick &&
+       git commit -m "path was a directory" &&
+       rm -fr path &&
+       echo A A A A A >path &&
+       git add path &&
+       test_tick &&
+       git commit -m "path is a regular file" &&
+       git blame HEAD^.. -- path
+'
+
+test_expect_success 'blame to a commit with no author name' '
+  TREE=`git rev-parse HEAD:`
+  cat >badcommit <<EOF
+tree $TREE
+author <noname> 1234567890 +0000
+committer David Reiss <dreiss@facebook.com> 1234567890 +0000
+
+some message
+EOF
+  COMMIT=`git hash-object -t commit -w badcommit`
+  git --no-pager blame $COMMIT -- uno >/dev/null
+'
+
+test_expect_success 'blame -L with invalid start' '
+       test_must_fail git blame -L5 tres 2>errors &&
+       grep "has only 2 lines" errors
+'
+
+test_expect_success 'blame -L with invalid end' '
+       test_must_fail git blame -L1,5 tres 2>errors &&
+       grep "has only 2 lines" errors
+'
+
+test_expect_success 'indent of line numbers, nine lines' '
+       git blame nine_lines >actual &&
+       test $(grep -c "  " actual) = 0
+'
+
+test_expect_success 'indent of line numbers, ten lines' '
+       git blame ten_lines >actual &&
+       test $(grep -c "  " actual) = 9
+'
+
+test_done
diff --git a/t/t8003-blame.sh b/t/t8003-blame.sh
deleted file mode 100755 (executable)
index 230143c..0000000
+++ /dev/null
@@ -1,188 +0,0 @@
-#!/bin/sh
-
-test_description='git blame corner cases'
-. ./test-lib.sh
-
-pick_fc='s/^[0-9a-f^]* *\([^ ]*\) *(\([^ ]*\) .*/\1-\2/'
-
-test_expect_success setup '
-
-       echo A A A A A >one &&
-       echo B B B B B >two &&
-       echo C C C C C >tres &&
-       echo ABC >mouse &&
-       for i in 1 2 3 4 5 6 7 8 9
-       do
-               echo $i
-       done >nine_lines &&
-       for i in 1 2 3 4 5 6 7 8 9 a
-       do
-               echo $i
-       done >ten_lines &&
-       git add one two tres mouse nine_lines ten_lines &&
-       test_tick &&
-       GIT_AUTHOR_NAME=Initial git commit -m Initial &&
-
-       cat one >uno &&
-       mv two dos &&
-       cat one >>tres &&
-       echo DEF >>mouse
-       git add uno dos tres mouse &&
-       test_tick &&
-       GIT_AUTHOR_NAME=Second git commit -a -m Second &&
-
-       echo GHIJK >>mouse &&
-       git add mouse &&
-       test_tick &&
-       GIT_AUTHOR_NAME=Third git commit -m Third &&
-
-       cat mouse >cow &&
-       git add cow &&
-       test_tick &&
-       GIT_AUTHOR_NAME=Fourth git commit -m Fourth &&
-
-       {
-               echo ABC
-               echo DEF
-               echo XXXX
-               echo GHIJK
-       } >cow &&
-       git add cow &&
-       test_tick &&
-       GIT_AUTHOR_NAME=Fifth git commit -m Fifth
-'
-
-test_expect_success 'straight copy without -C' '
-
-       git blame uno | grep Second
-
-'
-
-test_expect_success 'straight move without -C' '
-
-       git blame dos | grep Initial
-
-'
-
-test_expect_success 'straight copy with -C' '
-
-       git blame -C1 uno | grep Second
-
-'
-
-test_expect_success 'straight move with -C' '
-
-       git blame -C1 dos | grep Initial
-
-'
-
-test_expect_success 'straight copy with -C -C' '
-
-       git blame -C -C1 uno | grep Initial
-
-'
-
-test_expect_success 'straight move with -C -C' '
-
-       git blame -C -C1 dos | grep Initial
-
-'
-
-test_expect_success 'append without -C' '
-
-       git blame -L2 tres | grep Second
-
-'
-
-test_expect_success 'append with -C' '
-
-       git blame -L2 -C1 tres | grep Second
-
-'
-
-test_expect_success 'append with -C -C' '
-
-       git blame -L2 -C -C1 tres | grep Second
-
-'
-
-test_expect_success 'append with -C -C -C' '
-
-       git blame -L2 -C -C -C1 tres | grep Initial
-
-'
-
-test_expect_success 'blame wholesale copy' '
-
-       git blame -f -C -C1 HEAD^ -- cow | sed -e "$pick_fc" >current &&
-       {
-               echo mouse-Initial
-               echo mouse-Second
-               echo mouse-Third
-       } >expected &&
-       test_cmp expected current
-
-'
-
-test_expect_success 'blame wholesale copy and more' '
-
-       git blame -f -C -C1 HEAD -- cow | sed -e "$pick_fc" >current &&
-       {
-               echo mouse-Initial
-               echo mouse-Second
-               echo cow-Fifth
-               echo mouse-Third
-       } >expected &&
-       test_cmp expected current
-
-'
-
-test_expect_success 'blame path that used to be a directory' '
-       mkdir path &&
-       echo A A A A A >path/file &&
-       echo B B B B B >path/elif &&
-       git add path &&
-       test_tick &&
-       git commit -m "path was a directory" &&
-       rm -fr path &&
-       echo A A A A A >path &&
-       git add path &&
-       test_tick &&
-       git commit -m "path is a regular file" &&
-       git blame HEAD^.. -- path
-'
-
-test_expect_success 'blame to a commit with no author name' '
-  TREE=`git rev-parse HEAD:`
-  cat >badcommit <<EOF
-tree $TREE
-author <noname> 1234567890 +0000
-committer David Reiss <dreiss@facebook.com> 1234567890 +0000
-
-some message
-EOF
-  COMMIT=`git hash-object -t commit -w badcommit`
-  git --no-pager blame $COMMIT -- uno >/dev/null
-'
-
-test_expect_success 'blame -L with invalid start' '
-       test_must_fail git blame -L5 tres 2>errors &&
-       grep "has only 2 lines" errors
-'
-
-test_expect_success 'blame -L with invalid end' '
-       test_must_fail git blame -L1,5 tres 2>errors &&
-       grep "has only 2 lines" errors
-'
-
-test_expect_success 'indent of line numbers, nine lines' '
-       git blame nine_lines >actual &&
-       test $(grep -c "  " actual) = 0
-'
-
-test_expect_success 'indent of line numbers, ten lines' '
-       git blame ten_lines >actual &&
-       test $(grep -c "  " actual) = 9
-'
-
-test_done
diff --git a/t/t8004-blame-with-conflicts.sh b/t/t8004-blame-with-conflicts.sh
new file mode 100755 (executable)
index 0000000..ba19ac1
--- /dev/null
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+# Based on a test case submitted by Björn Steinbrink.
+
+test_description='git blame on conflicted files'
+. ./test-lib.sh
+
+test_expect_success 'setup first case' '
+       # Create the old file
+       echo "Old line" > file1 &&
+       git add file1 &&
+       git commit --author "Old Line <ol@localhost>" -m file1.a &&
+
+       # Branch
+       git checkout -b foo &&
+
+       # Do an ugly move and change
+       git rm file1 &&
+       echo "New line ..."  > file2 &&
+       echo "... and more" >> file2 &&
+       git add file2 &&
+       git commit --author "U Gly <ug@localhost>" -m ugly &&
+
+       # Back to master and change something
+       git checkout master &&
+       echo "
+
+bla" >> file1 &&
+       git commit --author "Old Line <ol@localhost>" -a -m file1.b &&
+
+       # Back to foo and merge master
+       git checkout foo &&
+       if git merge master; then
+               echo needed conflict here
+               exit 1
+       else
+               echo merge failed - resolving automatically
+       fi &&
+       echo "New line ...
+... and more
+
+bla
+Even more" > file2 &&
+       git rm file1 &&
+       git commit --author "M Result <mr@localhost>" -a -m merged &&
+
+       # Back to master and change file1 again
+       git checkout master &&
+       sed s/bla/foo/ <file1 >X &&
+       rm file1 &&
+       mv X file1 &&
+       git commit --author "No Bla <nb@localhost>" -a -m replace &&
+
+       # Try to merge into foo again
+       git checkout foo &&
+       if git merge master; then
+               echo needed conflict here
+               exit 1
+       else
+               echo merge failed - test is setup
+       fi
+'
+
+test_expect_success \
+       'blame runs on unconflicted file while other file has conflicts' '
+       git blame file2
+'
+
+test_expect_success 'blame runs on conflicted file in stages 1,3' '
+       git blame file1
+'
+
+test_done
diff --git a/t/t8004-blame.sh b/t/t8004-blame.sh
deleted file mode 100755 (executable)
index ba19ac1..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-#!/bin/sh
-
-# Based on a test case submitted by Björn Steinbrink.
-
-test_description='git blame on conflicted files'
-. ./test-lib.sh
-
-test_expect_success 'setup first case' '
-       # Create the old file
-       echo "Old line" > file1 &&
-       git add file1 &&
-       git commit --author "Old Line <ol@localhost>" -m file1.a &&
-
-       # Branch
-       git checkout -b foo &&
-
-       # Do an ugly move and change
-       git rm file1 &&
-       echo "New line ..."  > file2 &&
-       echo "... and more" >> file2 &&
-       git add file2 &&
-       git commit --author "U Gly <ug@localhost>" -m ugly &&
-
-       # Back to master and change something
-       git checkout master &&
-       echo "
-
-bla" >> file1 &&
-       git commit --author "Old Line <ol@localhost>" -a -m file1.b &&
-
-       # Back to foo and merge master
-       git checkout foo &&
-       if git merge master; then
-               echo needed conflict here
-               exit 1
-       else
-               echo merge failed - resolving automatically
-       fi &&
-       echo "New line ...
-... and more
-
-bla
-Even more" > file2 &&
-       git rm file1 &&
-       git commit --author "M Result <mr@localhost>" -a -m merged &&
-
-       # Back to master and change file1 again
-       git checkout master &&
-       sed s/bla/foo/ <file1 >X &&
-       rm file1 &&
-       mv X file1 &&
-       git commit --author "No Bla <nb@localhost>" -a -m replace &&
-
-       # Try to merge into foo again
-       git checkout foo &&
-       if git merge master; then
-               echo needed conflict here
-               exit 1
-       else
-               echo merge failed - test is setup
-       fi
-'
-
-test_expect_success \
-       'blame runs on unconflicted file while other file has conflicts' '
-       git blame file2
-'
-
-test_expect_success 'blame runs on conflicted file in stages 1,3' '
-       git blame file1
-'
-
-test_done
index a713dfc50bdd69878bb837b28e9f6c61c85f366a..faf9092967fff63cb0f496064d237b4c6e900f23 100755 (executable)
@@ -2,9 +2,25 @@
 
 test_description='check svn dumpfile importer'
 
-. ./lib-git-svn.sh
+. ./test-lib.sh
 
-test_dump() {
+if ! svnadmin -h >/dev/null 2>&1
+then
+       skip_all='skipping svn-fe tests, svn not available'
+       test_done
+fi
+
+svnconf=$PWD/svnconf
+export svnconf
+
+svn_cmd () {
+       subcommand=$1 &&
+       shift &&
+       mkdir -p "$svnconf" &&
+       svn "$subcommand" --config-dir "$svnconf" "$@"
+}
+
+test_dump () {
        label=$1
        dump=$2
        test_expect_success "$dump" '
index f3f397cdda4766ce6c02beac13e492d406ca14c4..ff19695e776f803aef03a18e70067c89aa3be218 100755 (executable)
@@ -18,21 +18,14 @@ case $v in
        ;;
 esac
 
-ptouch() {
-       perl -w -e '
-               use strict;
-               use POSIX qw(mktime);
-               die "ptouch requires exactly 2 arguments" if @ARGV != 2;
-               my $text_last_updated = shift @ARGV;
-               my $git_file = shift @ARGV;
-               die "\"$git_file\" does not exist" if ! -e $git_file;
-               if ($text_last_updated
-                   =~ /(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/) {
-                       my $mtime = mktime($6, $5, $4, $3, $2 - 1, $1 - 1900);
-                       my $atime = $mtime;
-                       utime $atime, $mtime, $git_file;
-               }
-       ' "`svn_cmd info $2 | grep '^Text Last Updated:'`" "$1"
+# On the "Text Last Updated" line, "git svn info" does not return the
+# same value as "svn info" (i.e. the commit timestamp that touched the
+# path most recently); do not expect that field to match.
+test_cmp_info () {
+       sed -e '/^Text Last Updated:/d' "$1" >tmp.expect
+       sed -e '/^Text Last Updated:/d' "$2" >tmp.actual
+       test_cmp tmp.expect tmp.actual &&
+       rm -f tmp.expect tmp.actual
 }
 
 quoted_svnrepo="$(echo $svnrepo | sed 's/ /%20/')"
@@ -62,17 +55,13 @@ test_expect_success 'setup repository and import' '
                cd gitwc &&
                git svn init "$svnrepo" &&
                git svn fetch
-       ) &&
-       ptouch gitwc/file svnwc/file &&
-       ptouch gitwc/directory svnwc/directory &&
-       ptouch gitwc/symlink-file svnwc/symlink-file &&
-       ptouch gitwc/symlink-directory svnwc/symlink-directory
+       )
        '
 
 test_expect_success 'info' "
        (cd svnwc; svn info) > expected.info &&
        (cd gitwc; git svn info) > actual.info &&
-       test_cmp expected.info actual.info
+       test_cmp_info expected.info actual.info
        "
 
 test_expect_success 'info --url' '
@@ -82,7 +71,7 @@ test_expect_success 'info --url' '
 test_expect_success 'info .' "
        (cd svnwc; svn info .) > expected.info-dot &&
        (cd gitwc; git svn info .) > actual.info-dot &&
-       test_cmp expected.info-dot actual.info-dot
+       test_cmp_info expected.info-dot actual.info-dot
        "
 
 test_expect_success 'info --url .' '
@@ -92,7 +81,7 @@ test_expect_success 'info --url .' '
 test_expect_success 'info file' "
        (cd svnwc; svn info file) > expected.info-file &&
        (cd gitwc; git svn info file) > actual.info-file &&
-       test_cmp expected.info-file actual.info-file
+       test_cmp_info expected.info-file actual.info-file
        "
 
 test_expect_success 'info --url file' '
@@ -102,13 +91,13 @@ test_expect_success 'info --url file' '
 test_expect_success 'info directory' "
        (cd svnwc; svn info directory) > expected.info-directory &&
        (cd gitwc; git svn info directory) > actual.info-directory &&
-       test_cmp expected.info-directory actual.info-directory
+       test_cmp_info expected.info-directory actual.info-directory
        "
 
 test_expect_success 'info inside directory' "
        (cd svnwc/directory; svn info) > expected.info-inside-directory &&
        (cd gitwc/directory; git svn info) > actual.info-inside-directory &&
-       test_cmp expected.info-inside-directory actual.info-inside-directory
+       test_cmp_info expected.info-inside-directory actual.info-inside-directory
        "
 
 test_expect_success 'info --url directory' '
@@ -118,7 +107,7 @@ test_expect_success 'info --url directory' '
 test_expect_success 'info symlink-file' "
        (cd svnwc; svn info symlink-file) > expected.info-symlink-file &&
        (cd gitwc; git svn info symlink-file) > actual.info-symlink-file &&
-       test_cmp expected.info-symlink-file actual.info-symlink-file
+       test_cmp_info expected.info-symlink-file actual.info-symlink-file
        "
 
 test_expect_success 'info --url symlink-file' '
@@ -131,7 +120,7 @@ test_expect_success 'info symlink-directory' "
                > expected.info-symlink-directory &&
        (cd gitwc; git svn info symlink-directory) \
                > actual.info-symlink-directory &&
-       test_cmp expected.info-symlink-directory actual.info-symlink-directory
+       test_cmp_info expected.info-symlink-directory actual.info-symlink-directory
        "
 
 test_expect_success 'info --url symlink-directory' '
@@ -146,14 +135,13 @@ test_expect_success 'info added-file' "
                git add added-file
        ) &&
        cp gitwc/added-file svnwc/added-file &&
-       ptouch gitwc/added-file svnwc/added-file &&
        (
                cd svnwc &&
                svn_cmd add added-file > /dev/null
        ) &&
        (cd svnwc; svn info added-file) > expected.info-added-file &&
        (cd gitwc; git svn info added-file) > actual.info-added-file &&
-       test_cmp expected.info-added-file actual.info-added-file
+       test_cmp_info expected.info-added-file actual.info-added-file
        "
 
 test_expect_success 'info --url added-file' '
@@ -163,7 +151,6 @@ test_expect_success 'info --url added-file' '
 
 test_expect_success 'info added-directory' "
        mkdir gitwc/added-directory svnwc/added-directory &&
-       ptouch gitwc/added-directory svnwc/added-directory &&
        touch gitwc/added-directory/.placeholder &&
        (
                cd svnwc &&
@@ -177,7 +164,7 @@ test_expect_success 'info added-directory' "
                > expected.info-added-directory &&
        (cd gitwc; git svn info added-directory) \
                > actual.info-added-directory &&
-       test_cmp expected.info-added-directory actual.info-added-directory
+       test_cmp_info expected.info-added-directory actual.info-added-directory
        "
 
 test_expect_success 'info --url added-directory' '
@@ -196,13 +183,12 @@ test_expect_success 'info added-symlink-file' "
                ln -s added-file added-symlink-file &&
                svn_cmd add added-symlink-file > /dev/null
        ) &&
-       ptouch gitwc/added-symlink-file svnwc/added-symlink-file &&
        (cd svnwc; svn info added-symlink-file) \
                > expected.info-added-symlink-file &&
        (cd gitwc; git svn info added-symlink-file) \
                > actual.info-added-symlink-file &&
-       test_cmp expected.info-added-symlink-file \
-                actual.info-added-symlink-file
+       test_cmp_info expected.info-added-symlink-file \
+               actual.info-added-symlink-file
        "
 
 test_expect_success 'info --url added-symlink-file' '
@@ -221,13 +207,12 @@ test_expect_success 'info added-symlink-directory' "
                ln -s added-directory added-symlink-directory &&
                svn_cmd add added-symlink-directory > /dev/null
        ) &&
-       ptouch gitwc/added-symlink-directory svnwc/added-symlink-directory &&
        (cd svnwc; svn info added-symlink-directory) \
                > expected.info-added-symlink-directory &&
        (cd gitwc; git svn info added-symlink-directory) \
                > actual.info-added-symlink-directory &&
-       test_cmp expected.info-added-symlink-directory \
-                actual.info-added-symlink-directory
+       test_cmp_info expected.info-added-symlink-directory \
+               actual.info-added-symlink-directory
        "
 
 test_expect_success 'info --url added-symlink-directory' '
@@ -235,11 +220,6 @@ test_expect_success 'info --url added-symlink-directory' '
             = "$quoted_svnrepo/added-symlink-directory"
        '
 
-# The next few tests replace the "Text Last Updated" value with a
-# placeholder since git doesn't have a way to know the date that a
-# now-deleted file was last checked out locally.  Internally it
-# simply reuses the Last Changed Date.
-
 test_expect_success 'info deleted-file' "
        (
                cd gitwc &&
@@ -249,13 +229,9 @@ test_expect_success 'info deleted-file' "
                cd svnwc &&
                svn_cmd rm --force file > /dev/null
        ) &&
-       (cd svnwc; svn info file) |
-       sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
-               > expected.info-deleted-file &&
-       (cd gitwc; git svn info file) |
-       sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
-               > actual.info-deleted-file &&
-       test_cmp expected.info-deleted-file actual.info-deleted-file
+       (cd svnwc; svn info file) >expected.info-deleted-file &&
+       (cd gitwc; git svn info file) >actual.info-deleted-file &&
+       test_cmp_info expected.info-deleted-file actual.info-deleted-file
        "
 
 test_expect_success 'info --url file (deleted)' '
@@ -272,13 +248,9 @@ test_expect_success 'info deleted-directory' "
                cd svnwc &&
                svn_cmd rm --force directory > /dev/null
        ) &&
-       (cd svnwc; svn info directory) |
-       sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
-               > expected.info-deleted-directory &&
-       (cd gitwc; git svn info directory) |
-       sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
-               > actual.info-deleted-directory &&
-       test_cmp expected.info-deleted-directory actual.info-deleted-directory
+       (cd svnwc; svn info directory) >expected.info-deleted-directory &&
+       (cd gitwc; git svn info directory) >actual.info-deleted-directory &&
+       test_cmp_info expected.info-deleted-directory actual.info-deleted-directory
        "
 
 test_expect_success 'info --url directory (deleted)' '
@@ -295,14 +267,9 @@ test_expect_success 'info deleted-symlink-file' "
                cd svnwc &&
                svn_cmd rm --force symlink-file > /dev/null
        ) &&
-       (cd svnwc; svn info symlink-file) |
-       sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
-               > expected.info-deleted-symlink-file &&
-       (cd gitwc; git svn info symlink-file) |
-       sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
-               > actual.info-deleted-symlink-file &&
-       test_cmp expected.info-deleted-symlink-file \
-                actual.info-deleted-symlink-file
+       (cd svnwc; svn info symlink-file) >expected.info-deleted-symlink-file &&
+       (cd gitwc; git svn info symlink-file) >actual.info-deleted-symlink-file &&
+       test_cmp_info expected.info-deleted-symlink-file actual.info-deleted-symlink-file
        "
 
 test_expect_success 'info --url symlink-file (deleted)' '
@@ -319,14 +286,9 @@ test_expect_success 'info deleted-symlink-directory' "
                cd svnwc &&
                svn_cmd rm --force symlink-directory > /dev/null
        ) &&
-       (cd svnwc; svn info symlink-directory) |
-       sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
-                > expected.info-deleted-symlink-directory &&
-       (cd gitwc; git svn info symlink-directory) |
-       sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
-                > actual.info-deleted-symlink-directory &&
-       test_cmp expected.info-deleted-symlink-directory \
-                actual.info-deleted-symlink-directory
+       (cd svnwc; svn info symlink-directory) >expected.info-deleted-symlink-directory &&
+       (cd gitwc; git svn info symlink-directory) >actual.info-deleted-symlink-directory &&
+       test_cmp_info expected.info-deleted-symlink-directory actual.info-deleted-symlink-directory
        "
 
 test_expect_success 'info --url symlink-directory (deleted)' '
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 100755 (executable)
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..5a1925f690fe309436a06534addf26a30bc10e1f 100755 (executable)
@@ -23,11 +23,18 @@ file5_data='an inline file.
 file6_data='#!/bin/sh
 echo "$@"'
 
+>empty
+
 ###
 ### series A
 ###
 
 test_tick
+
+test_expect_success 'empty stream succeeds' '
+       git fast-import </dev/null
+'
+
 cat >input <<INPUT_END
 blob
 mark :2
@@ -928,6 +935,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
 ###
@@ -1632,6 +1747,256 @@ test_expect_success 'R: feature no-relative-marks should be honoured' '
     test_cmp marks.new non-relative.out
 '
 
+test_expect_success 'R: feature cat-blob supported' '
+       echo "feature cat-blob" |
+       git fast-import
+'
+
+test_expect_success 'R: cat-blob-fd must be a nonnegative integer' '
+       test_must_fail git fast-import --cat-blob-fd=-1 </dev/null
+'
+
+test_expect_success 'R: print old blob' '
+       blob=$(echo "yes it can" | git hash-object -w --stdin) &&
+       cat >expect <<-EOF &&
+       ${blob} blob 11
+       yes it can
+
+       EOF
+       echo "cat-blob $blob" |
+       git fast-import --cat-blob-fd=6 6>actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'R: in-stream cat-blob-fd not respected' '
+       echo hello >greeting &&
+       blob=$(git hash-object -w greeting) &&
+       cat >expect <<-EOF &&
+       ${blob} blob 6
+       hello
+
+       EOF
+       git fast-import --cat-blob-fd=3 3>actual.3 >actual.1 <<-EOF &&
+       cat-blob $blob
+       EOF
+       test_cmp expect actual.3 &&
+       test_cmp empty actual.1 &&
+       git fast-import 3>actual.3 >actual.1 <<-EOF &&
+       option cat-blob-fd=3
+       cat-blob $blob
+       EOF
+       test_cmp empty actual.3 &&
+       test_cmp expect actual.1
+'
+
+test_expect_success 'R: print new blob' '
+       blob=$(echo "yep yep yep" | git hash-object --stdin) &&
+       cat >expect <<-EOF &&
+       ${blob} blob 12
+       yep yep yep
+
+       EOF
+       git fast-import --cat-blob-fd=6 6>actual <<-\EOF &&
+       blob
+       mark :1
+       data <<BLOB_END
+       yep yep yep
+       BLOB_END
+       cat-blob :1
+       EOF
+       test_cmp expect actual
+'
+
+test_expect_success 'R: print new blob by sha1' '
+       blob=$(echo "a new blob named by sha1" | git hash-object --stdin) &&
+       cat >expect <<-EOF &&
+       ${blob} blob 25
+       a new blob named by sha1
+
+       EOF
+       git fast-import --cat-blob-fd=6 6>actual <<-EOF &&
+       blob
+       data <<BLOB_END
+       a new blob named by sha1
+       BLOB_END
+       cat-blob $blob
+       EOF
+       test_cmp expect actual
+'
+
+test_expect_success 'setup: big file' '
+       (
+               echo "the quick brown fox jumps over the lazy dog" >big &&
+               for i in 1 2 3
+               do
+                       cat big big big big >bigger &&
+                       cat bigger bigger bigger bigger >big ||
+                       exit
+               done
+       )
+'
+
+test_expect_success 'R: print two blobs to stdout' '
+       blob1=$(git hash-object big) &&
+       blob1_len=$(wc -c <big) &&
+       blob2=$(echo hello | git hash-object --stdin) &&
+       {
+               echo ${blob1} blob $blob1_len &&
+               cat big &&
+               cat <<-EOF
+
+               ${blob2} blob 6
+               hello
+
+               EOF
+       } >expect &&
+       {
+               cat <<-\END_PART1 &&
+                       blob
+                       mark :1
+                       data <<data_end
+               END_PART1
+               cat big &&
+               cat <<-\EOF
+                       data_end
+                       blob
+                       mark :2
+                       data <<data_end
+                       hello
+                       data_end
+                       cat-blob :1
+                       cat-blob :2
+               EOF
+       } |
+       git fast-import >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'setup: have pipes?' '
+       rm -f frob &&
+       if mkfifo frob
+       then
+               test_set_prereq PIPE
+       fi
+'
+
+test_expect_success PIPE 'R: copy using cat-file' '
+       expect_id=$(git hash-object big) &&
+       expect_len=$(wc -c <big) &&
+       echo $expect_id blob $expect_len >expect.response &&
+
+       rm -f blobs &&
+       cat >frontend <<-\FRONTEND_END &&
+       #!/bin/sh
+       cat <<EOF &&
+       feature cat-blob
+       blob
+       mark :1
+       data <<BLOB
+       EOF
+       cat big
+       cat <<EOF
+       BLOB
+       cat-blob :1
+       EOF
+
+       read blob_id type size <&3 &&
+       echo "$blob_id $type $size" >response &&
+       dd of=blob bs=1 count=$size <&3 &&
+       read newline <&3 &&
+
+       cat <<EOF &&
+       commit refs/heads/copied
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<COMMIT
+       copy big file as file3
+       COMMIT
+       M 644 inline file3
+       data <<BLOB
+       EOF
+       cat blob &&
+       cat <<EOF
+       BLOB
+       EOF
+       FRONTEND_END
+
+       mkfifo blobs &&
+       (
+               export GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL GIT_COMMITTER_DATE &&
+               sh frontend 3<blobs |
+               git fast-import --cat-blob-fd=3 3>blobs
+       ) &&
+       git show copied:file3 >actual &&
+       test_cmp expect.response response &&
+       test_cmp big actual
+'
+
+test_expect_success PIPE 'R: print blob mid-commit' '
+       rm -f blobs &&
+       echo "A blob from _before_ the commit." >expect &&
+       mkfifo blobs &&
+       (
+               exec 3<blobs &&
+               cat <<-EOF &&
+               feature cat-blob
+               blob
+               mark :1
+               data <<BLOB
+               A blob from _before_ the commit.
+               BLOB
+               commit refs/heads/temporary
+               committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+               data <<COMMIT
+               Empty commit
+               COMMIT
+               cat-blob :1
+               EOF
+
+               read blob_id type size <&3 &&
+               dd of=actual bs=1 count=$size <&3 &&
+               read newline <&3 &&
+
+               echo
+       ) |
+       git fast-import --cat-blob-fd=3 3>blobs &&
+       test_cmp expect actual
+'
+
+test_expect_success PIPE 'R: print staged blob within commit' '
+       rm -f blobs &&
+       echo "A blob from _within_ the commit." >expect &&
+       mkfifo blobs &&
+       (
+               exec 3<blobs &&
+               cat <<-EOF &&
+               feature cat-blob
+               commit refs/heads/within
+               committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+               data <<COMMIT
+               Empty commit
+               COMMIT
+               M 644 inline within
+               data <<BLOB
+               A blob from _within_ the commit.
+               BLOB
+               EOF
+
+               to_get=$(
+                       echo "A blob from _within_ the commit." |
+                       git hash-object --stdin
+               ) &&
+               echo "cat-blob $to_get" &&
+
+               read blob_id type size <&3 &&
+               dd of=actual bs=1 count=$size <&3 &&
+               read newline <&3 &&
+
+               echo deleteall
+       ) |
+       git fast-import --cat-blob-fd=3 3>blobs &&
+       test_cmp expect actual
+'
+
 cat >input << EOF
 option git quiet
 blob
@@ -1640,8 +2005,6 @@ hi
 
 EOF
 
-touch empty
-
 test_expect_success 'R: quiet option results in no stats being output' '
     cat input | git fast-import 2> output &&
     test_cmp empty output
@@ -1659,6 +2022,14 @@ test_expect_success 'R: unknown commandline options are rejected' '\
     test_must_fail git fast-import --non-existing-option < /dev/null
 '
 
+test_expect_success 'R: die on invalid option argument' '
+       echo "option git active-branches=-5" |
+       test_must_fail git fast-import &&
+       echo "option git depth=" |
+       test_must_fail git fast-import &&
+       test_must_fail git fast-import --depth="5 elephants" </dev/null
+'
+
 cat >input <<EOF
 option non-existing-vcs non-existing-option
 EOF
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 cdba5111e19cd850f262108a330ce5d4ce63a166..ab8c951c6eb39f54a9885dd15c173f974d7357d5 100644 (file)
@@ -38,9 +38,14 @@ int main(int argc, char *argv[])
                usage("test-treap < ints");
 
        while (strbuf_getline(&sb, stdin, '\n') != EOF) {
-               item = node_alloc(1);
-               strtonode(node_pointer(item), sb.buf);
-               treap_insert(&root, node_pointer(item));
+               struct int_node *node = node_pointer(node_alloc(1));
+
+               item = node_offset(node);
+               strtonode(node, sb.buf);
+               node = treap_insert(&root, node_pointer(item));
+               if (node_offset(node) != item)
+                       die("inserted %"PRIu32" in place of %"PRIu32"",
+                               node_offset(node), item);
        }
 
        item = node_offset(treap_first(&root));
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;
diff --git a/url.c b/url.c
index cd8f74f00c345294f94cb25e4e00849a0618168b..6a5495960f03ded65f0f5f8b8bd5c7cd98c0b05e 100644 (file)
--- a/url.c
+++ b/url.c
@@ -125,3 +125,17 @@ char *url_decode_parameter_value(const char **query)
        struct strbuf out = STRBUF_INIT;
        return url_decode_internal(query, "&", &out, 1);
 }
+
+void end_url_with_slash(struct strbuf *buf, const char *url)
+{
+       strbuf_addstr(buf, url);
+       if (buf->len && buf->buf[buf->len - 1] != '/')
+               strbuf_addstr(buf, "/");
+}
+
+void str_end_url_with_slash(const char *url, char **dest) {
+       struct strbuf buf = STRBUF_INIT;
+       end_url_with_slash(&buf, url);
+       free(*dest);
+       *dest = strbuf_detach(&buf, NULL);
+}
diff --git a/url.h b/url.h
index 15817f8f9385c217b22706df64117fcc53c8a87f..7100e3215a97b234c8ab93b76f7f86518f7c3382 100644 (file)
--- a/url.h
+++ b/url.h
@@ -7,4 +7,7 @@ extern char *url_decode(const char *url);
 extern char *url_decode_parameter_name(const char **query);
 extern char *url_decode_parameter_value(const char **query);
 
+extern void end_url_with_slash(struct strbuf *buf, const char *url);
+extern void str_end_url_with_slash(const char *url, char **dest);
+
 #endif /* URL_H */
index e94d91d12966562e4cd43df9c34990dddc3465c3..e3d1fa35444dd693ab8723a030b9fc6265cad8bc 100644 (file)
@@ -131,7 +131,7 @@ static void repo_write_dirent(uint32_t *path, uint32_t mode,
                if (dent == key) {
                        dent->mode = REPO_MODE_DIR;
                        dent->content_offset = 0;
-                       dent_insert(&dir->entries, dent);
+                       dent = dent_insert(&dir->entries, dent);
                }
 
                if (dent_offset(dent) < dent_pool.committed) {
@@ -142,7 +142,7 @@ static void repo_write_dirent(uint32_t *path, uint32_t mode,
                        dent->name_offset = name;
                        dent->mode = REPO_MODE_DIR;
                        dent->content_offset = dir_o;
-                       dent_insert(&dir->entries, dent);
+                       dent = dent_insert(&dir->entries, dent);
                }
 
                dir = repo_dir_from_dirent(dent);
index ee35c688a0008ac210cdd28a82191c5a7cb7987e..c32b9184e9b8420e4d4e2904806bd2f14814132b 100644 (file)
@@ -188,11 +188,12 @@ a_attr uint32_t MAYBE_UNUSED a_pre##insert_recurse(uint32_t cur_node, uint32_t i
                return ret; \
        } \
 } \
-a_attr void MAYBE_UNUSED a_pre##insert(struct trp_root *treap, a_type *node) \
+a_attr a_type *MAYBE_UNUSED a_pre##insert(struct trp_root *treap, a_type *node) \
 { \
        uint32_t offset = trpn_offset(a_base, node); \
        trp_node_new(a_base, a_field, offset); \
        treap->trp_root = a_pre##insert_recurse(treap->trp_root, offset); \
+       return trpn_pointer(a_base, offset); \
 } \
 a_attr uint32_t MAYBE_UNUSED a_pre##remove_recurse(uint32_t cur_node, uint32_t rem_node) \
 { \
index eb4c191875f4580d18c1db1913cfe99f658a5f83..5ca6b42edb289c5f5714ae2f0a09710d40dd14f3 100644 (file)
@@ -21,7 +21,9 @@ The caller:
 
 . Allocates a `struct trp_root` variable and sets it to {~0}.
 
-. Adds new nodes to the set using `foo_insert`.
+. Adds new nodes to the set using `foo_insert`.  Any pointers
+  to existing nodes cannot be relied upon any more, so the caller
+  might retrieve them anew with `foo_pointer`.
 
 . Can find a specific item in the set using `foo_search`.
 
@@ -73,10 +75,14 @@ int (*cmp)(node_type \*a, node_type \*b)
 and returning a value less than, equal to, or greater than zero
 according to the result of comparison.
 
-void foo_insert(struct trp_root *treap, node_type \*node)::
+node_type {asterisk}foo_insert(struct trp_root *treap, node_type \*node)::
 
        Insert node into treap.  If inserted multiple times,
        a node will appear in the treap multiple times.
++
+The return value is the address of the node within the treap,
+which might differ from `node` if `pool_alloc` had to call
+`realloc` to expand the pool.
 
 void foo_remove(struct trp_root *treap, node_type \*node)::
 
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)
diff --git a/ws.c b/ws.c
index 7302f8f5a2cd450771aefcba0a385a90121ba0cf..9fb9b14760b100972a1adb5198bff9d6a9941808 100644 (file)
--- a/ws.c
+++ b/ws.c
@@ -56,6 +56,16 @@ unsigned parse_whitespace_rule(const char *string)
                                rule |= whitespace_rule_names[i].rule_bits;
                        break;
                }
+               if (strncmp(string, "tabwidth=", 9) == 0) {
+                       unsigned tabwidth = atoi(string + 9);
+                       if (0 < tabwidth && tabwidth < 0100) {
+                               rule &= ~WS_TAB_WIDTH_MASK;
+                               rule |= tabwidth;
+                       }
+                       else
+                               warning("tabwidth %.*s out of range",
+                                       (int)(len - 9), string + 9);
+               }
                string = ep;
        }
 
@@ -84,7 +94,7 @@ unsigned whitespace_rule(const char *pathname)
                value = attr_whitespace_rule.value;
                if (ATTR_TRUE(value)) {
                        /* true (whitespace) */
-                       unsigned all_rule = 0;
+                       unsigned all_rule = ws_tab_width(whitespace_rule_cfg);
                        int i;
                        for (i = 0; i < ARRAY_SIZE(whitespace_rule_names); i++)
                                if (!whitespace_rule_names[i].loosens_error &&
@@ -93,7 +103,7 @@ unsigned whitespace_rule(const char *pathname)
                        return all_rule;
                } else if (ATTR_FALSE(value)) {
                        /* false (-whitespace) */
-                       return 0;
+                       return ws_tab_width(whitespace_rule_cfg);
                } else if (ATTR_UNSET(value)) {
                        /* reset to default (!whitespace) */
                        return whitespace_rule_cfg;
@@ -206,7 +216,7 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,
        }
 
        /* Check for indent using non-tab. */
-       if ((ws_rule & WS_INDENT_WITH_NON_TAB) && i - written >= 8) {
+       if ((ws_rule & WS_INDENT_WITH_NON_TAB) && i - written >= ws_tab_width(ws_rule)) {
                result |= WS_INDENT_WITH_NON_TAB;
                if (stream) {
                        fputs(ws, stream);
@@ -320,7 +330,7 @@ void ws_fix_copy(struct strbuf *dst, const char *src, int len, unsigned ws_rule,
                } else if (ch == ' ') {
                        last_space_in_indent = i;
                        if ((ws_rule & WS_INDENT_WITH_NON_TAB) &&
-                           8 <= i - last_tab_in_indent)
+                           ws_tab_width(ws_rule) <= i - last_tab_in_indent)
                                need_fix_leading_space = 1;
                } else
                        break;
@@ -350,7 +360,7 @@ void ws_fix_copy(struct strbuf *dst, const char *src, int len, unsigned ws_rule,
                                strbuf_addch(dst, ch);
                        } else {
                                consecutive_spaces++;
-                               if (consecutive_spaces == 8) {
+                               if (consecutive_spaces == ws_tab_width(ws_rule)) {
                                        strbuf_addch(dst, '\t');
                                        consecutive_spaces = 0;
                                }
@@ -363,12 +373,13 @@ void ws_fix_copy(struct strbuf *dst, const char *src, int len, unsigned ws_rule,
                fixed = 1;
        } else if ((ws_rule & WS_TAB_IN_INDENT) && last_tab_in_indent >= 0) {
                /* Expand tabs into spaces */
+               int start = dst->len;
                int last = last_tab_in_indent + 1;
                for (i = 0; i < last; i++) {
                        if (src[i] == '\t')
                                do {
                                        strbuf_addch(dst, ' ');
-                               } while (dst->len % 8);
+                               } while ((dst->len - start) % ws_tab_width(ws_rule));
                        else
                                strbuf_addch(dst, src[i]);
                }
index d9f3d9fe9369ceaac07f5b405e25cf94d824e102..123582b6cbdff90f4665fbf8db2c92d701680e4d 100644 (file)
@@ -21,11 +21,15 @@ static char default_wt_status_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_RED,    /* WT_STATUS_UNMERGED */
        GIT_COLOR_GREEN,  /* WT_STATUS_LOCAL_BRANCH */
        GIT_COLOR_RED,    /* WT_STATUS_REMOTE_BRANCH */
+       GIT_COLOR_NIL,    /* WT_STATUS_ONBRANCH */
 };
 
 static const char *color(int slot, struct wt_status *s)
 {
-       return s->use_color > 0 ? s->color_palette[slot] : "";
+       const char *c = s->use_color > 0 ? s->color_palette[slot] : "";
+       if (slot == WT_STATUS_ONBRANCH && color_is_nil(c))
+               c = s->color_palette[WT_STATUS_HEADER];
+       return c;
 }
 
 void wt_status_prepare(struct wt_status *s)
@@ -625,7 +629,8 @@ static void wt_status_print_tracking(struct wt_status *s)
 
 void wt_status_print(struct wt_status *s)
 {
-       const char *branch_color = color(WT_STATUS_HEADER, s);
+       const char *branch_color = color(WT_STATUS_ONBRANCH, s);
+       const char *branch_status_color = color(WT_STATUS_HEADER, s);
 
        if (s->branch) {
                const char *on_what = "On branch ";
@@ -634,11 +639,12 @@ void wt_status_print(struct wt_status *s)
                        branch_name += 11;
                else if (!strcmp(branch_name, "HEAD")) {
                        branch_name = "";
-                       branch_color = color(WT_STATUS_NOBRANCH, s);
+                       branch_status_color = color(WT_STATUS_NOBRANCH, s);
                        on_what = "Not currently on any branch.";
                }
                color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "# ");
-               color_fprintf_ln(s->fp, branch_color, "%s%s", on_what, branch_name);
+               color_fprintf(s->fp, branch_status_color, "%s", on_what);
+               color_fprintf_ln(s->fp, branch_color, "%s", branch_name);
                if (!s->is_initial)
                        wt_status_print_tracking(s);
        }
@@ -744,10 +750,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);
        }
index 9df9c9fad2512d7c1d7d6456cdd645d641b5a089..20b17cf4393b8f9acce93320fb97998ad7cd609b 100644 (file)
@@ -13,7 +13,9 @@ enum color_wt_status {
        WT_STATUS_NOBRANCH,
        WT_STATUS_UNMERGED,
        WT_STATUS_LOCAL_BRANCH,
-       WT_STATUS_REMOTE_BRANCH
+       WT_STATUS_REMOTE_BRANCH,
+       WT_STATUS_ONBRANCH,
+       WT_STATUS_MAXSLOT
 };
 
 enum untracked_status_type {
@@ -46,7 +48,7 @@ struct wt_status {
        int show_ignored_files;
        enum untracked_status_type show_untracked_files;
        const char *ignore_submodule_arg;
-       char color_palette[WT_STATUS_REMOTE_BRANCH+1][COLOR_MAXLEN];
+       char color_palette[WT_STATUS_MAXSLOT][COLOR_MAXLEN];
 
        /* These are computed during processing of the individual sections */
        int commitable;
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;
+}