summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Documentation/CodingGuidelines56
-rw-r--r--Documentation/Makefile23
-rw-r--r--Documentation/RelNotes/1.6.4.5.txt20
-rw-r--r--Documentation/RelNotes/1.6.5.9.txt18
-rw-r--r--Documentation/RelNotes/1.6.6.3.txt23
-rw-r--r--Documentation/RelNotes/1.7.0.8.txt10
-rw-r--r--Documentation/RelNotes/1.7.0.9.txt8
-rw-r--r--Documentation/RelNotes/1.7.1.3.txt10
-rw-r--r--Documentation/RelNotes/1.7.1.4.txt8
-rw-r--r--Documentation/RelNotes/1.7.2.4.txt10
-rw-r--r--Documentation/RelNotes/1.7.2.5.txt8
-rw-r--r--Documentation/RelNotes/1.7.3.3.txt54
-rw-r--r--Documentation/RelNotes/1.7.3.4.txt45
-rw-r--r--Documentation/RelNotes/1.7.4.txt69
-rw-r--r--Documentation/config.txt47
-rw-r--r--Documentation/diff-options.txt4
-rw-r--r--Documentation/fetch-options.txt11
-rw-r--r--Documentation/git-blame.txt6
-rw-r--r--Documentation/git-cherry-pick.txt2
-rw-r--r--Documentation/git-clone.txt4
-rw-r--r--Documentation/git-commit.txt21
-rw-r--r--Documentation/git-diff.txt11
-rw-r--r--Documentation/git-difftool.txt11
-rw-r--r--Documentation/git-fast-import.txt89
-rw-r--r--Documentation/git-fsck.txt3
-rw-r--r--Documentation/git-gc.txt2
-rw-r--r--Documentation/git-merge.txt27
-rw-r--r--Documentation/git-notes.txt85
-rw-r--r--Documentation/git-pull.txt9
-rw-r--r--Documentation/git-rebase.txt4
-rw-r--r--Documentation/git-remote-ext.txt125
-rw-r--r--Documentation/git-remote-fd.txt59
-rw-r--r--Documentation/git-revert.txt2
-rw-r--r--Documentation/git-rm.txt12
-rw-r--r--Documentation/git-send-email.txt3
-rw-r--r--Documentation/git-web--browse.txt6
-rw-r--r--Documentation/git.txt25
-rw-r--r--Documentation/gitattributes.txt6
-rw-r--r--Documentation/gitignore.txt30
-rw-r--r--Documentation/gitmodules.txt8
-rw-r--r--Documentation/revisions.txt10
-rw-r--r--INSTALL5
-rw-r--r--Makefile94
-rw-r--r--builtin.h4
-rw-r--r--builtin/add.c6
-rw-r--r--builtin/apply.c63
-rw-r--r--builtin/blame.c32
-rw-r--r--builtin/branch.c10
-rw-r--r--builtin/checkout-index.c3
-rw-r--r--builtin/clone.c2
-rw-r--r--builtin/commit.c89
-rw-r--r--builtin/describe.c85
-rw-r--r--builtin/diff.c2
-rw-r--r--builtin/fetch.c64
-rw-r--r--builtin/gc.c3
-rw-r--r--builtin/grep.c4
-rw-r--r--builtin/log.c5
-rw-r--r--builtin/ls-files.c3
-rw-r--r--builtin/mailinfo.c2
-rw-r--r--builtin/merge.c48
-rw-r--r--builtin/notes.c268
-rw-r--r--builtin/pack-objects.c22
-rw-r--r--builtin/remote-ext.c246
-rw-r--r--builtin/remote-fd.c79
-rw-r--r--builtin/revert.c18
-rw-r--r--builtin/rm.c12
-rw-r--r--builtin/shortlog.c4
-rw-r--r--builtin/tag.c16
-rw-r--r--builtin/update-index.c395
-rw-r--r--builtin/verify-tag.c10
-rw-r--r--cache.h26
-rw-r--r--color.c5
-rw-r--r--color.h5
-rw-r--r--commit.c26
-rw-r--r--commit.h4
-rw-r--r--compat/mingw.c60
-rw-r--r--compat/mingw.h38
-rw-r--r--compat/msvc.c29
-rw-r--r--compat/vcbuild/include/dirent.h128
-rw-r--r--compat/vcbuild/include/unistd.h4
-rw-r--r--compat/win32/dirent.c108
-rw-r--r--compat/win32/dirent.h24
-rw-r--r--compat/win32/sys/poll.c3
-rw-r--r--config.c21
-rw-r--r--config.mak.in4
-rw-r--r--configure.ac58
-rwxr-xr-xcontrib/completion/git-completion.bash85
-rwxr-xr-xcontrib/hooks/post-receive-email8
-rw-r--r--daemon.c4
-rw-r--r--diff.c36
-rw-r--r--dir.c112
-rw-r--r--dir.h4
-rw-r--r--entry.c16
-rw-r--r--environment.c50
-rw-r--r--fast-import.c235
-rwxr-xr-xgit-add--interactive.perl13
-rw-r--r--git-compat-util.h9
-rwxr-xr-xgit-difftool--helper.sh1
-rwxr-xr-xgit-instaweb.sh2
-rw-r--r--git-parse-remote.sh12
-rwxr-xr-xgit-pull.sh15
-rwxr-xr-xgit-rebase--interactive.sh16
-rwxr-xr-xgit-rebase.sh39
-rw-r--r--git-sh-setup.sh29
-rwxr-xr-xgit-submodule.sh16
-rwxr-xr-xgit-svn.perl15
-rwxr-xr-xgit-web--browse.sh177
-rw-r--r--git.c23
-rw-r--r--gitk-git/gitk81
-rw-r--r--gitk-git/po/pt_br.po1277
-rw-r--r--gitk-git/po/sv.po601
-rw-r--r--gitweb/README14
-rwxr-xr-xgitweb/gitweb.perl459
-rw-r--r--gitweb/static/gitweb.css6
-rw-r--r--help.c47
-rw-r--r--http-backend.c4
-rw-r--r--http-fetch.c16
-rw-r--r--http-push.c31
-rw-r--r--http.c24
-rw-r--r--http.h2
-rw-r--r--merge-recursive.c652
-rw-r--r--name-hash.c72
-rw-r--r--notes-cache.c3
-rw-r--r--notes-merge.c737
-rw-r--r--notes-merge.h98
-rw-r--r--notes.c272
-rw-r--r--notes.h47
-rw-r--r--parse-options.c83
-rw-r--r--parse-options.h11
-rw-r--r--path.c113
-rw-r--r--pretty.c36
-rw-r--r--read-cache.c23
-rw-r--r--reflog-walk.c1
-rw-r--r--revision.c4
-rw-r--r--setup.c6
-rw-r--r--sha1_file.c117
-rw-r--r--sha1_name.c142
-rw-r--r--strbuf.c18
-rw-r--r--submodule.c99
-rw-r--r--submodule.h5
-rw-r--r--symlinks.c64
-rw-r--r--t/Makefile16
-rw-r--r--t/annotate-tests.sh12
-rw-r--r--t/lib-httpd.sh3
-rw-r--r--t/lib-httpd/apache.conf36
-rw-r--r--t/lib-httpd/passwd1
-rwxr-xr-xt/t0050-filesystem.sh4
-rwxr-xr-xt/t1412-reflog-loop.sh34
-rwxr-xr-xt/t1506-rev-parse-diagnosis.sh86
-rwxr-xr-xt/t1511-rev-parse-caret.sh73
-rwxr-xr-xt/t2006-checkout-index-basic.sh24
-rwxr-xr-xt/t2107-update-index-basic.sh32
-rwxr-xr-xt/t3004-ls-files-basic.sh39
-rwxr-xr-xt/t3030-merge-recursive.sh37
-rwxr-xr-xt/t3200-branch.sh11
-rwxr-xr-xt/t3301-notes.sh23
-rwxr-xr-xt/t3303-notes-subtrees.sh19
-rwxr-xr-xt/t3308-notes-merge.sh368
-rwxr-xr-xt/t3309-notes-merge-auto-resolve.sh647
-rwxr-xr-xt/t3310-notes-merge-manual-resolve.sh556
-rwxr-xr-xt/t3311-notes-merge-fanout.sh436
-rwxr-xr-xt/t3404-rebase-interactive.sh1
-rwxr-xr-xt/t3407-rebase-abort.sh12
-rwxr-xr-xt/t3415-rebase-autosquash.sh29
-rwxr-xr-xt/t3419-rebase-patch-id.sh4
-rwxr-xr-xt/t3501-revert-cherry-pick.sh10
-rwxr-xr-xt/t3509-cherry-pick-merge-df.sh66
-rwxr-xr-xt/t3900-i18n-commit.sh29
-rwxr-xr-xt/t4014-format-patch.sh23
-rwxr-xr-xt/t4015-diff-whitespace.sh28
-rwxr-xr-xt/t4019-diff-wserror.sh94
-rwxr-xr-xt/t4120-apply-popt.sh26
-rwxr-xr-xt/t4124-apply-ws-rule.sh47
-rwxr-xr-xt/t4132-apply-removal.sh2
-rwxr-xr-xt/t4135-apply-weird-filenames.sh16
-rw-r--r--t/t4135/damaged-tz.diff5
-rw-r--r--t/t4135/funny-tz.diff5
-rwxr-xr-xt/t4202-log.sh9
-rwxr-xr-xt/t5520-pull.sh7
-rwxr-xr-xt/t5526-fetch-submodules.sh195
-rwxr-xr-xt/t5550-http-fetch.sh23
-rwxr-xr-xt/t5551-http-fetch.sh8
-rwxr-xr-xt/t6020-merge-df.sh76
-rwxr-xr-xt/t6022-merge-rename.sh366
-rwxr-xr-xt/t6032-merge-large-rename.sh30
-rwxr-xr-xt/t6036-recursive-corner-cases.sh185
-rwxr-xr-xt/t6500-gc.sh28
-rwxr-xr-xt/t7004-tag.sh66
-rwxr-xr-xt/t7006-pager.sh29
-rwxr-xr-xt/t7300-clean.sh2
-rwxr-xr-xt/t7400-submodule-basic.sh28
-rwxr-xr-xt/t7500-commit.sh80
-rwxr-xr-xt/t7500/edit-content4
-rwxr-xr-xt/t7508-status.sh29
-rwxr-xr-xt/t7600-merge.sh11
-rwxr-xr-xt/t7607-merge-overwrite.sh102
-rwxr-xr-xt/t7609-merge-co-error-msgs.sh16
-rwxr-xr-xt/t7611-merge-abort.sh313
-rwxr-xr-xt/t8002-blame.sh5
-rwxr-xr-xt/t8003-blame-corner-cases.sh (renamed from t/t8003-blame.sh)0
-rwxr-xr-xt/t8004-blame-with-conflicts.sh (renamed from t/t8004-blame.sh)0
-rwxr-xr-xt/t8006-blame-textconv.sh21
-rwxr-xr-xt/t9010-svn-fe.sh20
-rwxr-xr-xt/t9119-git-svn-info.sh106
-rwxr-xr-xt/t9143-git-svn-gc.sh4
-rwxr-xr-xt/t9158-git-svn-mergeinfo.sh41
-rwxr-xr-xt/t9300-fast-import.sh389
-rwxr-xr-xt/t9301-fast-import-notes.sh5
-rw-r--r--tag.c15
-rw-r--r--tag.h1
-rw-r--r--test-treap.c11
-rw-r--r--thread-utils.h4
-rw-r--r--trace.c8
-rw-r--r--transport-helper.c312
-rw-r--r--transport.h1
-rw-r--r--unpack-trees.c157
-rw-r--r--unpack-trees.h9
-rw-r--r--url.c14
-rw-r--r--url.h3
-rw-r--r--userdiff.c4
-rw-r--r--vcs-svn/repo_tree.c4
-rw-r--r--vcs-svn/trp.h3
-rw-r--r--vcs-svn/trp.txt10
-rw-r--r--wrapper.c210
-rw-r--r--ws.c23
-rw-r--r--wt-status.c24
-rw-r--r--wt-status.h6
-rw-r--r--zlib.c61
229 files changed, 12742 insertions, 2395 deletions
diff --git a/.gitignore b/.gitignore
index 20560b8..87b833c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -112,6 +112,8 @@
/git-remote-https
/git-remote-ftp
/git-remote-ftps
+/git-remote-fd
+/git-remote-ext
/git-remote-testgit
/git-repack
/git-replace
diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines
index 09ffc46..1b1c45d 100644
--- a/Documentation/CodingGuidelines
+++ b/Documentation/CodingGuidelines
@@ -31,6 +31,10 @@ But if you must have a list of rules, here they are.
For shell scripts specifically (not exhaustive):
+ - We use tabs for indentation.
+
+ - Case arms are indented at the same depth as case and esac lines.
+
- We prefer $( ... ) for command substitution; unlike ``, it
properly nests. It should have been the way Bourne spelled
it from day one, but unfortunately isn't.
@@ -139,3 +143,55 @@ For C programs:
- When we pass <string, length> pair to functions, we should try to
pass them in that order.
+
+Writing Documentation:
+
+ Every user-visible change should be reflected in the documentation.
+ The same general rule as for code applies -- imitate the existing
+ conventions. A few commented examples follow to provide reference
+ when writing or modifying command usage strings and synopsis sections
+ in the manual pages:
+
+ Placeholders are enclosed in angle brackets:
+ <file>
+ --sort=<key>
+ --abbrev[=<n>]
+
+ Possibility of multiple occurences is indicated by three dots:
+ <file>...
+ (One or more of <file>.)
+
+ Optional parts are enclosed in square brackets:
+ [<extra>]
+ (Zero or one <extra>.)
+
+ --exec-path[=<path>]
+ (Option with an optional argument. Note that the "=" is inside the
+ brackets.)
+
+ [<patch>...]
+ (Zero or more of <patch>. Note that the dots are inside, not
+ outside the brackets.)
+
+ Multiple alternatives are indicated with vertical bar:
+ [-q | --quiet]
+ [--utf8 | --no-utf8]
+
+ Parentheses are used for grouping:
+ [(<rev>|<range>)...]
+ (Any number of either <rev> or <range>. Parens are needed to make
+ it clear that "..." pertains to both <rev> and <range>.)
+
+ [(-p <parent>)...]
+ (Any number of option -p, each with one <parent> argument.)
+
+ git remote set-head <name> (-a | -d | <branch>)
+ (One and only one of "-a", "-d" or "<branch>" _must_ (no square
+ brackets) be provided.)
+
+ And a somewhat more contrived example:
+ --diff-filter=[(A|C|D|M|R|T|U|X|B)...[*]]
+ Here "=" is outside the brackets, because "--diff-filter=" is a
+ valid usage. "*" has its own pair of brackets, because it can
+ (optionally) be specified only when one or more of the letters is
+ also provided.
diff --git a/Documentation/Makefile b/Documentation/Makefile
index e117bc4..36989b7 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -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
index 0000000..eb6307d
--- /dev/null
+++ b/Documentation/RelNotes/1.6.4.5.txt
@@ -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
index 0000000..bb469dd
--- /dev/null
+++ b/Documentation/RelNotes/1.6.5.9.txt
@@ -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
index 0000000..11483ac
--- /dev/null
+++ b/Documentation/RelNotes/1.6.6.3.txt
@@ -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
index 0000000..7f05b48
--- /dev/null
+++ b/Documentation/RelNotes/1.7.0.8.txt
@@ -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
index 0000000..bfb3166
--- /dev/null
+++ b/Documentation/RelNotes/1.7.0.9.txt
@@ -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
index 0000000..5b18518
--- /dev/null
+++ b/Documentation/RelNotes/1.7.1.3.txt
@@ -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
index 0000000..7c734b4
--- /dev/null
+++ b/Documentation/RelNotes/1.7.1.4.txt
@@ -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
index 0000000..f7950a4
--- /dev/null
+++ b/Documentation/RelNotes/1.7.2.4.txt
@@ -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
index 0000000..bf976c4
--- /dev/null
+++ b/Documentation/RelNotes/1.7.2.5.txt
@@ -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
index 0000000..9b2b244
--- /dev/null
+++ b/Documentation/RelNotes/1.7.3.3.txt
@@ -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
index 0000000..e57f7c1
--- /dev/null
+++ b/Documentation/RelNotes/1.7.3.4.txt
@@ -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.
diff --git a/Documentation/RelNotes/1.7.4.txt b/Documentation/RelNotes/1.7.4.txt
index 9f946e2..ace061f 100644
--- a/Documentation/RelNotes/1.7.4.txt
+++ b/Documentation/RelNotes/1.7.4.txt
@@ -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,15 +19,52 @@ 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.
+ * Different pagers can be chosen depending on which subcommand is
+ being run under the pager, using "pager.<subcommand>" variable.
+
+ * The hardcoded tab-width of 8 used in whitespace breakage checks is now
+ configurable via the attributes mechanism.
+
+ * Support of case insensitive filesystems (i.e. "core.ignorecase") has
+ been improved. For example, the gitignore mechanism didn't pay attention
+ to the case insensitivity.
+
+ * The <tree>:<path> syntax to name a blob in a tree, and :<path>
+ syntax to name a blob in the index (e.g. "master:Makefile",
+ ":hello.c") have been extended. You can start <path> with "./" to
+ implicitly have the (sub)directory you are in prefixed to the
+ lookup. Similarly, ":../Makefile" from a subdirectory would mean
+ "the Makefile of the parent directory in the index".
+
+ * "git blame" learned --show-email option to display the e-mail
+ addresses instead of the names of authors.
+
+ * "git commit" learned --fixup and --squash options to help later invocation
+ of the interactive rebase.
+
+ * "git daemon" can be built in MinGW environment.
+
* "git daemon" can take more than one --listen option to listen to
multiple addresses.
+ * "git describe --exact-match" was optimized not to read commit
+ objects unnecessarily.
+
* "git diff" and "git grep" learned how functions and subroutines
in Fortran look like.
+ * "git fetch" learned "--recurse-submodules" option.
+
* "git mergetool" tells vim/gvim to show three-way diff by default
(use vimdiff2/gvimdiff2 as the tool name for old behaviour).
@@ -41,6 +82,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
@@ -58,11 +106,16 @@ Updates since v1.7.3
git-only login over ssh as login shell, with custom set of
commands.
+ * The current branch name in "git status" output can be colored differently
+ from the generic header color by setting "color.status.branch" variable.
+
* "git submodule sync" updates metainformation for all submodules,
not just the ones that have been checked out.
* gitweb can use custom 'highlight' command with its configuration file.
+ * other gitweb updates.
+
Also contains various documentation updates.
@@ -73,33 +126,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.4-567-g38a5932
echo O=$(git describe master)
git shortlog --no-merges ^maint ^$O master
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 6a6c0b5..54597f1 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -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
@@ -974,7 +996,7 @@ gc.packrefs::
Running `git pack-refs` in a repository renders it
unclonable by Git versions prior to 1.5.1.2 over dumb
transports such as HTTP. This variable determines whether
- 'git gc' runs `git pack-refs`. This can be set to `nobare`
+ 'git gc' runs `git pack-refs`. This can be set to `notbare`
to enable it within all non-bare repos or it can be set to a
boolean value. The default is `true`.
@@ -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
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index f3e9538..c93124b 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -230,7 +230,7 @@ eligible for being picked up as a possible source of a rename to
another file.
-M[<n>]::
---detect-renames[=<n>]::
+--find-renames[=<n>]::
ifndef::git-log[]
Detect renames.
endif::git-log[]
@@ -246,7 +246,7 @@ endif::git-log[]
hasn't changed.
-C[<n>]::
---detect-copies[=<n>]::
+--find-copies[=<n>]::
Detect copies as well as renames. See also `--find-copies-harder`.
If `n` is specified, it has the same meaning as for `-M<n>`.
diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt
index 678675c..695696d 100644
--- a/Documentation/fetch-options.txt
+++ b/Documentation/fetch-options.txt
@@ -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
diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt
index a27f439..c71671b 100644
--- a/Documentation/git-blame.txt
+++ b/Documentation/git-blame.txt
@@ -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.
diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
index 3c96fa8..7300870 100644
--- a/Documentation/git-cherry-pick.txt
+++ b/Documentation/git-cherry-pick.txt
@@ -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
diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index 2320382..42e7021 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -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
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 42fb1f5..b586c0f 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -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.
diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt
index dd1fb32..f6ac847 100644
--- a/Documentation/git-diff.txt
+++ b/Documentation/git-diff.txt
@@ -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>...]::
diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt
index 8250bad..db87f1d 100644
--- a/Documentation/git-difftool.txt
+++ b/Documentation/git-difftool.txt
@@ -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
-------
@@ -55,14 +56,16 @@ the configured command line will be invoked with the following
variables available: `$LOCAL` is set to the name of the temporary
file containing the contents of the diff pre-image and `$REMOTE`
is set to the name of the temporary file containing the contents
-of the diff post-image. `$BASE` is provided for compatibility
-with custom merge tool commands and has the same value as `$LOCAL`.
+of the diff post-image. `$MERGED` is the name of the file which is
+being compared. `$BASE` is provided for compatibility
+with custom merge tool commands and has the same value as `$MERGED`.
-x <command>::
--extcmd=<command>::
Specify a custom command for viewing diffs.
'git-difftool' ignores the configured defaults and runs
`$command $LOCAL $REMOTE` when this option is specified.
+ Additionally, `$BASE` is set in the environment.
-g::
--gui::
diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt
index 5d0c245..f56dfca 100644
--- a/Documentation/git-fast-import.txt
+++ b/Documentation/git-fast-import.txt
@@ -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
------
diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt
index 3ad48a6..86f9b2b 100644
--- a/Documentation/git-fsck.txt
+++ b/Documentation/git-fsck.txt
@@ -123,9 +123,6 @@ dangling <type> <object>::
The <type> object <object>, is present in the database but never
'directly' used. A dangling commit could be a root node.
-warning: git-fsck: tree <tree> has full pathnames in it::
- And it shouldn't...
-
sha1 mismatch <object>::
The database has an object who's sha1 doesn't match the
database value.
diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt
index 801aede..2663241 100644
--- a/Documentation/git-gc.txt
+++ b/Documentation/git-gc.txt
@@ -107,7 +107,7 @@ how long records of conflicted merge you have not resolved are
kept. This defaults to 15 days.
The optional configuration variable 'gc.packrefs' determines if
-'git gc' runs 'git pack-refs'. This can be set to "nobare" to enable
+'git gc' runs 'git pack-refs'. This can be set to "notbare" to enable
it within all non-bare repos or it can be set to a boolean value.
This defaults to true.
diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt
index d43416d..c1efaaa 100644
--- a/Documentation/git-merge.txt
+++ b/Documentation/git-merge.txt
@@ -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
diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index 2981d8c..296f314 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -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
--------
diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt
index e47361f..3046691 100644
--- a/Documentation/git-pull.txt
+++ b/Documentation/git-pull.txt
@@ -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.
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 30e5c0e..96680c8 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -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
index 0000000..f4fbf67
--- /dev/null
+++ b/Documentation/git-remote-ext.txt
@@ -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
index 0000000..abc4944
--- /dev/null
+++ b/Documentation/git-remote-fd.txt
@@ -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
diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt
index f40984d..752fc88 100644
--- a/Documentation/git-revert.txt
+++ b/Documentation/git-revert.txt
@@ -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
diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt
index 71e3d9f..0adbe8b 100644
--- a/Documentation/git-rm.txt
+++ b/Documentation/git-rm.txt
@@ -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:
diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
index ebc024a..7ec9dab 100644
--- a/Documentation/git-send-email.txt
+++ b/Documentation/git-send-email.txt
@@ -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
-------------
diff --git a/Documentation/git-web--browse.txt b/Documentation/git-web--browse.txt
index 51e8e0a..c0416e5 100644
--- a/Documentation/git-web--browse.txt
+++ b/Documentation/git-web--browse.txt
@@ -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
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 0c897df..0128371 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -44,31 +44,39 @@ 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.5/git.html[documentation for release 1.7.2.5]
* release notes for
+ link:RelNotes/1.7.2.5.txt[1.7.2.5],
+ 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.4/git.html[documentation for release 1.7.1.4]
* release notes for
+ link:RelNotes/1.7.1.4.txt[1.7.1.4],
+ 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.9/git.html[documentation for release 1.7.0.9]
* release notes for
+ link:RelNotes/1.7.0.9.txt[1.7.0.9],
+ 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],
@@ -78,16 +86,18 @@ Documentation for older releases are available here:
link:RelNotes/1.7.0.1.txt[1.7.0.1],
link:RelNotes/1.7.0.txt[1.7.0].
-* link:v1.6.6.2/git.html[documentation for release 1.6.6.2]
+* link:v1.6.6.3/git.html[documentation for release 1.6.6.3]
* release notes for
+ link:RelNotes/1.6.6.3.txt[1.6.6.3],
link:RelNotes/1.6.6.2.txt[1.6.6.2],
link:RelNotes/1.6.6.1.txt[1.6.6.1],
link:RelNotes/1.6.6.txt[1.6.6].
-* link:v1.6.5.8/git.html[documentation for release 1.6.5.8]
+* link:v1.6.5.9/git.html[documentation for release 1.6.5.9]
* release notes for
+ link:RelNotes/1.6.5.9.txt[1.6.5.9],
link:RelNotes/1.6.5.8.txt[1.6.5.8],
link:RelNotes/1.6.5.7.txt[1.6.5.7],
link:RelNotes/1.6.5.6.txt[1.6.5.6],
@@ -98,9 +108,10 @@ Documentation for older releases are available here:
link:RelNotes/1.6.5.1.txt[1.6.5.1],
link:RelNotes/1.6.5.txt[1.6.5].
-* link:v1.6.4.4/git.html[documentation for release 1.6.4.4]
+* link:v1.6.4.5/git.html[documentation for release 1.6.4.5]
* release notes for
+ link:RelNotes/1.6.4.5.txt[1.6.4.5],
link:RelNotes/1.6.4.4.txt[1.6.4.4],
link:RelNotes/1.6.4.3.txt[1.6.4.3],
link:RelNotes/1.6.4.2.txt[1.6.4.2],
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index c80ca5d..5a7f936 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -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.
diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt
index 7dc2e8b..8416f34 100644
--- a/Documentation/gitignore.txt
+++ b/Documentation/gitignore.txt
@@ -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,
diff --git a/Documentation/gitmodules.txt b/Documentation/gitmodules.txt
index bcffd95..6c93202 100644
--- a/Documentation/gitmodules.txt
+++ b/Documentation/gitmodules.txt
@@ -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
diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt
index 3d4b79c..9e92734 100644
--- a/Documentation/revisions.txt
+++ b/Documentation/revisions.txt
@@ -106,6 +106,12 @@ the `$GIT_DIR/refs` directory or from the `$GIT_DIR/packed-refs` file.
and dereference the tag recursively until a non-tag object is
found.
+* A suffix '{caret}' to a revision parameter followed by a brace
+ pair that contains a text led by a slash (e.g. `HEAD^{/fix nasty bug}`):
+ this is the same as `:/fix nasty bug` syntax below except that
+ it returns the youngest matching commit which is reachable from
+ the ref before '{caret}'.
+
* A colon, followed by a slash, followed by a text (e.g. `:/fix nasty bug`): this names
a commit whose commit message matches the specified regular expression.
This name returns the youngest matching commit which is
@@ -121,6 +127,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 10a1cba..16e45f1 100644
--- 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
diff --git a/Makefile b/Makefile
index 919ed2b..57d9c65 100644
--- 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 = \
diff --git a/builtin.h b/builtin.h
index 8dd4569..904e067 100644
--- 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);
diff --git a/builtin/add.c b/builtin/add.c
index 71f9b04..12b964e 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -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",
diff --git a/builtin/apply.c b/builtin/apply.c
index 96246e9..14951da 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -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);
}
}
diff --git a/builtin/blame.c b/builtin/blame.c
index f5fccc1..aa30ec5 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -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:
diff --git a/builtin/branch.c b/builtin/branch.c
index 807355a..9e546e4 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -313,12 +313,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
(struct object *)commit, refname);
}
- /* Resize buffer */
- if (ref_list->index >= ref_list->alloc) {
- ref_list->alloc = alloc_nr(ref_list->alloc);
- ref_list->list = xrealloc(ref_list->list,
- ref_list->alloc * sizeof(struct ref_item));
- }
+ ALLOC_GROW(ref_list->list, ref_list->index + 1, ref_list->alloc);
/* Record the new item */
newitem = &(ref_list->list[ref_list->index++]);
@@ -668,6 +663,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)
diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c
index 1ee3044..f1fec24 100644
--- a/builtin/checkout-index.c
+++ b/builtin/checkout-index.c
@@ -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;
diff --git a/builtin/clone.c b/builtin/clone.c
index 19ed640..61e0989 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -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",
diff --git a/builtin/commit.c b/builtin/commit.c
index 4fd1a16..6c09857 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -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"));
diff --git a/builtin/describe.c b/builtin/describe.c
index 9304dd0..342129f 100644
--- a/builtin/describe.c
+++ b/builtin/describe.c
@@ -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) {
diff --git a/builtin/diff.c b/builtin/diff.c
index a43d326..945e758 100644
--- a/builtin/diff.c
+++ b/builtin/diff.c
@@ -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,
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 6bcce55..357f3cd 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -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);
diff --git a/builtin/gc.c b/builtin/gc.c
index 397a1e6..1a80702 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -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)
diff --git a/builtin/grep.c b/builtin/grep.c
index adb5424..fdf7131 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -17,11 +17,7 @@
#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>...]",
diff --git a/builtin/log.c b/builtin/log.c
index d0297a1..d8c6c28 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -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)
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index 6a307ab..fb2d5f4 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -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)
diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c
index 2320d98..71e6262 100644
--- a/builtin/mailinfo.c
+++ b/builtin/mailinfo.c
@@ -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] == '-') {
diff --git a/builtin/merge.c b/builtin/merge.c
index c24a7be..42fff38 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -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;
diff --git a/builtin/notes.c b/builtin/notes.c
index c85cbf5..4d5556e 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -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);
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index f8eba53..b0503b2 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -16,11 +16,7 @@
#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
index 0000000..1f77317
--- /dev/null
+++ b/builtin/remote-ext.c
@@ -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
index 0000000..1f2467b
--- /dev/null
+++ b/builtin/remote-fd.c
@@ -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;
+}
diff --git a/builtin/revert.c b/builtin/revert.c
index 57b51e4..bb6e9e8 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -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);
diff --git a/builtin/rm.c b/builtin/rm.c
index c7b7bb3..ff491d7 100644
--- a/builtin/rm.c
+++ b/builtin/rm.c
@@ -20,15 +20,6 @@ static struct {
const char **name;
} list;
-static void add_list(const char *name)
-{
- if (list.nr >= list.alloc) {
- list.alloc = alloc_nr(list.alloc);
- list.name = xrealloc(list.name, list.alloc * sizeof(const char *));
- }
- list.name[list.nr++] = name;
-}
-
static int check_local_mod(unsigned char *head, int index_only)
{
/*
@@ -182,7 +173,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
struct cache_entry *ce = active_cache[i];
if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen))
continue;
- add_list(ce->name);
+ ALLOC_GROW(list.name, list.nr + 1, list.alloc);
+ list.name[list.nr++] = ce->name;
}
if (pathspec) {
diff --git a/builtin/shortlog.c b/builtin/shortlog.c
index 2135b0d..1a21e4b 100644
--- a/builtin/shortlog.c
+++ b/builtin/shortlog.c
@@ -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)) {
diff --git a/builtin/tag.c b/builtin/tag.c
index d1d7d87..aa1f87d 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -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);
}
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 62d9f3f..56baf27 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -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);
}
diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c
index 8136dba..3134766 100644
--- a/builtin/verify-tag.c
+++ b/builtin/verify-tag.c
@@ -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 33decd9..b455258 100644
--- 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 1b00554..6a5a54e 100644
--- 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 03ca064..170ff40 100644
--- 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 */
diff --git a/commit.c b/commit.c
index 554dcc3..74d6601 100644
--- 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)
diff --git a/commit.h b/commit.h
index 6452928..eb6c5af 100644
--- 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);
int parse_commit(struct commit *item);
@@ -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 *);
diff --git a/compat/mingw.c b/compat/mingw.c
index fdbf093..bee6054 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -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
diff --git a/compat/mingw.h b/compat/mingw.h
index 99a7467..cafc1eb 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -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
*/
diff --git a/compat/msvc.c b/compat/msvc.c
index ac04a4c..71843d7 100644
--- a/compat/msvc.c
+++ b/compat/msvc.c
@@ -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
index 440618d..0000000
--- a/compat/vcbuild/include/dirent.h
+++ /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_ */
diff --git a/compat/vcbuild/include/unistd.h b/compat/vcbuild/include/unistd.h
index 2a4f276..b14fcf9 100644
--- a/compat/vcbuild/include/unistd.h
+++ b/compat/vcbuild/include/unistd.h
@@ -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
index 0000000..7a0debe
--- /dev/null
+++ b/compat/win32/dirent.c
@@ -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
index 0000000..927a25c
--- /dev/null
+++ b/compat/win32/dirent.h
@@ -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 */
diff --git a/compat/win32/sys/poll.c b/compat/win32/sys/poll.c
index 7e74ebe..708a6c9 100644
--- a/compat/win32/sys/poll.c
+++ b/compat/win32/sys/poll.c
@@ -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>
diff --git a/config.c b/config.c
index c63d683..d73b090 100644
--- 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,19 @@ int git_config_maybe_bool(const char *name, const char *value)
return -1;
}
+int git_config_maybe_bool(const char *name, const char *value)
+{
+ long v = git_config_maybe_bool_text(name, value);
+ if (0 <= v)
+ return v;
+ if (git_parse_long(value, &v))
+ return !!v;
+ 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 +499,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;
diff --git a/config.mak.in b/config.mak.in
index a0c34ee..9614973 100644
--- a/config.mak.in
+++ b/config.mak.in
@@ -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@
diff --git a/configure.ac b/configure.ac
index cc55b6d..5792425 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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.
#
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index f710469..803da09 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -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)..." ||
@@ -735,7 +735,6 @@ __git_list_porcelain_commands ()
quiltimport) : import;;
read-tree) : plumbing;;
receive-pack) : plumbing;;
- reflog) : plumbing;;
remote-*) : transport;;
repo-config) : deprecated;;
rerere) : plumbing;;
@@ -1632,6 +1631,18 @@ _git_rebase ()
__gitcomp "$(__git_refs)"
}
+_git_reflog ()
+{
+ local subcommands="show delete expire"
+ local subcommand="$(__git_find_on_cmdline "$subcommands")"
+
+ if [ -z "$subcommand" ]; then
+ __gitcomp "$subcommands"
+ else
+ __gitcomp "$(__git_refs)"
+ fi
+}
+
__git_send_email_confirm_options="always never auto cc compose"
__git_send_email_suppresscc_options="author self cc bodycc sob cccmd body all"
@@ -1864,30 +1875,50 @@ _git_config ()
;;
esac
__gitcomp "
- add.ignore-errors
+ add.ignoreErrors
+ advice.commitBeforeMerge
+ advice.detachedHead
+ advice.implicitIdentity
+ advice.pushNonFastForward
+ advice.resolveConflict
+ advice.statusHints
alias.
+ am.keepcr
apply.ignorewhitespace
apply.whitespace
branch.autosetupmerge
branch.autosetuprebase
+ browser.
clean.requireForce
color.branch
color.branch.current
color.branch.local
color.branch.plain
color.branch.remote
+ color.decorate.HEAD
+ color.decorate.branch
+ color.decorate.remoteBranch
+ color.decorate.stash
+ color.decorate.tag
color.diff
color.diff.commit
color.diff.frag
+ color.diff.func
color.diff.meta
color.diff.new
color.diff.old
color.diff.plain
color.diff.whitespace
color.grep
- color.grep.external
+ color.grep.context
+ color.grep.filename
+ color.grep.function
+ color.grep.linenumber
color.grep.match
+ color.grep.selected
+ color.grep.separator
color.interactive
+ color.interactive.error
color.interactive.header
color.interactive.help
color.interactive.prompt
@@ -1901,21 +1932,29 @@ _git_config ()
color.status.untracked
color.status.updated
color.ui
+ commit.status
commit.template
+ core.abbrevguard
+ core.askpass
+ core.attributesfile
core.autocrlf
core.bare
+ core.bigFileThreshold
core.compression
core.createObject
core.deltaBaseCacheLimit
core.editor
+ core.eol
core.excludesfile
core.fileMode
core.fsyncobjectfiles
core.gitProxy
core.ignoreCygwinFSTricks
core.ignoreStat
+ core.ignorecase
core.logAllRefUpdates
core.loosecompression
+ core.notesRef
core.packedGitLimit
core.packedGitWindowSize
core.pager
@@ -1925,6 +1964,7 @@ _git_config ()
core.repositoryFormatVersion
core.safecrlf
core.sharedRepository
+ core.sparseCheckout
core.symlinks
core.trustctime
core.warnAmbiguousRefs
@@ -1932,15 +1972,17 @@ _git_config ()
core.worktree
diff.autorefreshindex
diff.external
+ diff.ignoreSubmodules
diff.mnemonicprefix
+ diff.noprefix
diff.renameLimit
- diff.renameLimit.
diff.renames
diff.suppressBlankEmpty
diff.tool
diff.wordRegex
difftool.
difftool.prompt
+ fetch.recurseSubmodules
fetch.unpackLimit
format.attach
format.cc
@@ -1952,6 +1994,8 @@ _git_config ()
format.subjectprefix
format.suffix
format.thread
+ format.to
+ gc.
gc.aggressiveWindow
gc.auto
gc.autopacklimit
@@ -1989,15 +2033,20 @@ _git_config ()
http.lowSpeedLimit
http.lowSpeedTime
http.maxRequests
+ http.minSessions
http.noEPSV
+ http.postBuffer
http.proxy
http.sslCAInfo
http.sslCAPath
http.sslCert
+ http.sslCertPasswordProtected
http.sslKey
http.sslVerify
+ http.useragent
i18n.commitEncoding
i18n.logOutputEncoding
+ imap.authMethod
imap.folder
imap.host
imap.pass
@@ -2006,6 +2055,7 @@ _git_config ()
imap.sslverify
imap.tunnel
imap.user
+ init.templatedir
instaweb.browser
instaweb.httpd
instaweb.local
@@ -2013,19 +2063,29 @@ _git_config ()
instaweb.port
interactive.singlekey
log.date
+ log.decorate
log.showroot
mailmap.file
man.
man.viewer
+ merge.
merge.conflictstyle
merge.log
merge.renameLimit
+ merge.renormalize
merge.stat
merge.tool
merge.verbosity
mergetool.
mergetool.keepBackup
+ mergetool.keepTemporaries
mergetool.prompt
+ notes.displayRef
+ notes.rewrite.
+ notes.rewrite.amend
+ notes.rewrite.rebase
+ notes.rewriteMode
+ notes.rewriteRef
pack.compression
pack.deltaCacheLimit
pack.deltaCacheSize
@@ -2036,31 +2096,42 @@ _git_config ()
pack.window
pack.windowMemory
pager.
+ pretty.
pull.octopus
pull.twohead
push.default
+ rebase.autosquash
rebase.stat
+ receive.autogc
receive.denyCurrentBranch
+ receive.denyDeleteCurrent
receive.denyDeletes
receive.denyNonFastForwards
receive.fsckObjects
receive.unpackLimit
+ receive.updateserverinfo
+ remotes.
repack.usedeltabaseoffset
rerere.autoupdate
rerere.enabled
+ sendemail.
sendemail.aliasesfile
- sendemail.aliasesfiletype
+ sendemail.aliasfiletype
sendemail.bcc
sendemail.cc
sendemail.cccmd
sendemail.chainreplyto
sendemail.confirm
sendemail.envelopesender
+ sendemail.from
+ sendemail.identity
sendemail.multiedit
sendemail.signedoffbycc
+ sendemail.smtpdomain
sendemail.smtpencryption
sendemail.smtppass
sendemail.smtpserver
+ sendemail.smtpserveroption
sendemail.smtpserverport
sendemail.smtpuser
sendemail.suppresscc
@@ -2071,6 +2142,8 @@ _git_config ()
showbranch.default
status.relativePaths
status.showUntrackedFiles
+ status.submodulesummary
+ submodule.
tar.umask
transfer.unpackLimit
url.
diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email
index 85724bf..f99ea95 100755
--- a/contrib/hooks/post-receive-email
+++ b/contrib/hooks/post-receive-email
@@ -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
}
#
diff --git a/daemon.c b/daemon.c
index 13435b4..d2a4e02 100644
--- a/daemon.c
+++ b/daemon.c
@@ -13,6 +13,10 @@
#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 c248bc6..5422c43 100644
--- 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,20 +3152,20 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
else if (!prefixcmp(arg, "-B") || !prefixcmp(arg, "--break-rewrites=") ||
!strcmp(arg, "--break-rewrites")) {
if ((options->break_opt = diff_scoreopt_parse(arg)) == -1)
- return -1;
+ return error("invalid argument to -B: %s", arg+2);
}
- else if (!prefixcmp(arg, "-M") || !prefixcmp(arg, "--detect-renames=") ||
- !strcmp(arg, "--detect-renames")) {
+ 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, "--detect-copies=") ||
- !strcmp(arg, "--detect-copies")) {
+ else if (!prefixcmp(arg, "-C") || !prefixcmp(arg, "--find-copies=") ||
+ !strcmp(arg, "--find-copies")) {
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"))
@@ -3380,12 +3384,12 @@ static int diff_scoreopt_parse(const char *opt)
opt += strlen("break-rewrites");
if (*opt == 0 || *opt++ == '=')
cmd = 'B';
- } else if (!prefixcmp(opt, "detect-copies")) {
- opt += strlen("detect-copies");
+ } else if (!prefixcmp(opt, "find-copies")) {
+ opt += strlen("find-copies");
if (*opt == 0 || *opt++ == '=')
cmd = 'C';
- } else if (!prefixcmp(opt, "detect-renames")) {
- opt += strlen("detect-renames");
+ } else if (!prefixcmp(opt, "find-renames")) {
+ opt += strlen("find-renames");
if (*opt == 0 || *opt++ == '=')
cmd = 'M';
}
@@ -4408,7 +4412,7 @@ size_t fill_textconv(struct userdiff_driver *driver,
return df->size;
}
- if (driver->textconv_cache) {
+ if (driver->textconv_cache && df->sha1_valid) {
*outbuf = notes_cache_get(driver->textconv_cache, df->sha1,
&size);
if (*outbuf)
@@ -4419,7 +4423,7 @@ size_t fill_textconv(struct userdiff_driver *driver,
if (!*outbuf)
die("unable to read files to diff");
- if (driver->textconv_cache) {
+ if (driver->textconv_cache && df->sha1_valid) {
/* ignore errors, as we might be in a readonly repository */
notes_cache_put(driver->textconv_cache, df->sha1, *outbuf,
size);
diff --git a/dir.c b/dir.c
index b2dfb69..38f3e3e 100644
--- 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;
}
@@ -470,6 +500,39 @@ enum exist_status {
};
/*
+ * 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
* a directory (which is defined not as an entry, but as
@@ -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 278d84c..b3e2104 100644
--- 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 004182c..b017167 100644
--- 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;
}
diff --git a/environment.c b/environment.c
index de5581f..c79f2a9 100644
--- a/environment.c
+++ b/environment.c
@@ -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";
+}
diff --git a/fast-import.c b/fast-import.c
index 77549eb..7857760 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -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 */
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index 77f60fa..a329c5a 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -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
diff --git a/git-compat-util.h b/git-compat-util.h
index d0a1e48..d6d269f 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -105,7 +105,11 @@
#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>
@@ -119,7 +123,11 @@
#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);
diff --git a/git-difftool--helper.sh b/git-difftool--helper.sh
index 524f5ea..0594bf7 100755
--- a/git-difftool--helper.sh
+++ b/git-difftool--helper.sh
@@ -49,6 +49,7 @@ launch_merge_tool () {
fi
if use_ext_cmd; then
+ export BASE
eval $GIT_DIFFTOOL_EXTCMD '"$LOCAL"' '"$REMOTE"'
else
run_merge_tool "$merge_tool"
diff --git a/git-instaweb.sh b/git-instaweb.sh
index e6f6ecd..10fcebb 100755
--- a/git-instaweb.sh
+++ b/git-instaweb.sh
@@ -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
}
diff --git a/git-parse-remote.sh b/git-parse-remote.sh
index 5f47b18..1cc2ba6 100644
--- a/git-parse-remote.sh
+++ b/git-parse-remote.sh
@@ -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
}
diff --git a/git-pull.sh b/git-pull.sh
index 8eb74d4..eb87f49 100755
--- a/git-pull.sh
+++ b/git-pull.sh
@@ -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)
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index c2383bf..a5ffd9a 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -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
diff --git a/git-rebase.sh b/git-rebase.sh
index 10a238a..d8e1903 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -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
diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index ae031a1..aa16b83 100644
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -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 /{
diff --git a/git-submodule.sh b/git-submodule.sh
index 33bc41f..c21b77a 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -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
diff --git a/git-svn.perl b/git-svn.perl
index 757de82..177dd25 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -84,7 +84,7 @@ my ($_stdin, $_help, $_edit,
$_version, $_fetch_all, $_no_rebase, $_fetch_parent,
$_merge, $_strategy, $_dry_run, $_local,
$_prefix, $_no_checkout, $_url, $_verbose,
- $_git_format, $_commit_url, $_tag);
+ $_git_format, $_commit_url, $_tag, $_merge_info);
$Git::SVN::_follow_parent = 1;
$_q ||= 0;
my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
@@ -154,6 +154,7 @@ my %cmd = (
'commit-url=s' => \$_commit_url,
'revision|r=i' => \$_revision,
'no-rebase' => \$_no_rebase,
+ 'mergeinfo=s' => \$_merge_info,
%cmt_opts, %fc_opts } ],
branch => [ \&cmd_branch,
'Create a branch in the SVN repository',
@@ -569,6 +570,7 @@ sub cmd_dcommit {
print "Committed r$_[0]\n";
$cmt_rev = $_[0];
},
+ mergeinfo => $_merge_info,
svn_path => '');
if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) {
print "No changes\n$d~1 == $d\n";
@@ -4451,6 +4453,7 @@ sub new {
$self->{path_prefix} = length $self->{svn_path} ?
"$self->{svn_path}/" : '';
$self->{config} = $opts->{config};
+ $self->{mergeinfo} = $opts->{mergeinfo};
return $self;
}
@@ -4760,6 +4763,11 @@ sub change_file_prop {
$self->SUPER::change_file_prop($fbat, $pname, $pval, $self->{pool});
}
+sub change_dir_prop {
+ my ($self, $pbat, $pname, $pval) = @_;
+ $self->SUPER::change_dir_prop($pbat, $pname, $pval, $self->{pool});
+}
+
sub _chg_file_get_blob ($$$$) {
my ($self, $fbat, $m, $which) = @_;
my $fh = $::_repository->temp_acquire("git_blob_$which");
@@ -4853,6 +4861,11 @@ sub apply_diff {
fatal("Invalid change type: $f");
}
}
+
+ if (defined($self->{mergeinfo})) {
+ $self->change_dir_prop($self->{bat}{''}, "svn:mergeinfo",
+ $self->{mergeinfo});
+ }
$self->rmdirs if $_rmdir;
if (@$mods == 0) {
$self->abort_edit;
diff --git a/git-web--browse.sh b/git-web--browse.sh
index 3fc4166..e9de241 100755
--- a/git-web--browse.sh
+++ b/git-web--browse.sh
@@ -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 0409ac9..d532576 100644
--- 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 },
diff --git a/gitk-git/gitk b/gitk-git/gitk
index 1b0e09a..e82c6bf 100644
--- a/gitk-git/gitk
+++ b/gitk-git/gitk
@@ -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
index 0000000..1486e32
--- /dev/null
+++ b/gitk-git/po/pt_br.po
@@ -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"
diff --git a/gitk-git/po/sv.po b/gitk-git/po/sv.po
index 386763a..2f07a2e 100644
--- a/gitk-git/po/sv.po
+++ b/gitk-git/po/sv.po
@@ -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"
diff --git a/gitweb/README b/gitweb/README
index bf3664f..4a67393 100644
--- a/gitweb/README
+++ b/gitweb/README
@@ -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
~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 679f2da..4779618 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -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->());
}
@@ -1199,7 +1230,7 @@ sub href {
$href =~ s,/$,,;
# Then add the project name, if present
- $href .= "/".esc_url($params{'project'});
+ $href .= "/".esc_path_info($params{'project'});
delete $params{'project'};
# since we destructively absorb parameters, we keep this
@@ -1209,7 +1240,8 @@ sub href {
# Summary just uses the project path URL, any other action is
# added to the URL
if (defined $params{'action'}) {
- $href .= "/".esc_url($params{'action'}) unless $params{'action'} eq 'summary';
+ $href .= "/".esc_path_info($params{'action'})
+ unless $params{'action'} eq 'summary';
delete $params{'action'};
}
@@ -1219,13 +1251,13 @@ sub href {
|| $params{'hash_parent'} || $params{'hash'});
if (defined $params{'hash_base'}) {
if (defined $params{'hash_parent_base'}) {
- $href .= esc_url($params{'hash_parent_base'});
+ $href .= esc_path_info($params{'hash_parent_base'});
# skip the file_parent if it's the same as the file_name
if (defined $params{'file_parent'}) {
if (defined $params{'file_name'} && $params{'file_parent'} eq $params{'file_name'}) {
delete $params{'file_parent'};
} elsif ($params{'file_parent'} !~ /\.\./) {
- $href .= ":/".esc_url($params{'file_parent'});
+ $href .= ":/".esc_path_info($params{'file_parent'});
delete $params{'file_parent'};
}
}
@@ -1233,19 +1265,19 @@ sub href {
delete $params{'hash_parent'};
delete $params{'hash_parent_base'};
} elsif (defined $params{'hash_parent'}) {
- $href .= esc_url($params{'hash_parent'}). "..";
+ $href .= esc_path_info($params{'hash_parent'}). "..";
delete $params{'hash_parent'};
}
- $href .= esc_url($params{'hash_base'});
+ $href .= esc_path_info($params{'hash_base'});
if (defined $params{'file_name'} && $params{'file_name'} !~ /\.\./) {
- $href .= ":/".esc_url($params{'file_name'});
+ $href .= ":/".esc_path_info($params{'file_name'});
delete $params{'file_name'};
}
delete $params{'hash'};
delete $params{'hash_base'};
} elsif (defined $params{'hash'}) {
- $href .= esc_url($params{'hash'});
+ $href .= esc_path_info($params{'hash'});
delete $params{'hash'};
}
@@ -1278,6 +1310,9 @@ sub href {
}
$href .= "?" . join(';', @result) if scalar @result;
+ # final transformation: trailing spaces must be escaped (URI-encoded)
+ $href =~ s/(\s+)$/CGI::escape($1)/e;
+
return $href;
}
@@ -1360,6 +1395,17 @@ sub esc_param {
return $str;
}
+# the quoting rules for path_info fragment are slightly different
+sub esc_path_info {
+ my $str = shift;
+ return undef unless defined $str;
+
+ # path_info doesn't treat '+' as space (specially), but '?' must be escaped
+ $str =~ s/([^A-Za-z0-9\-_.~();\/;:@&= +]+)/CGI::escape($1)/eg;
+
+ return $str;
+}
+
# quote unsafe chars in whole URL, so some characters cannot be quoted
sub esc_url {
my $str = shift;
@@ -1369,6 +1415,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 +1827,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 +1911,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 +2622,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 +2812,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 +3248,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 +3267,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;
@@ -3401,6 +3494,51 @@ sub get_page_title {
return $title;
}
+sub print_feed_meta {
+ if (defined $project) {
+ my %href_params = get_feed_info();
+ if (!exists $href_params{'-title'}) {
+ $href_params{'-title'} = 'log';
+ }
+
+ foreach my $format qw(RSS Atom) {
+ my $type = lc($format);
+ my %link_attr = (
+ '-rel' => 'alternate',
+ '-title' => esc_attr("$project - $href_params{'-title'} - $format feed"),
+ '-type' => "application/$type+xml"
+ );
+
+ $href_params{'action'} = $type;
+ $link_attr{'-href'} = href(%href_params);
+ print "<link ".
+ "rel=\"$link_attr{'-rel'}\" ".
+ "title=\"$link_attr{'-title'}\" ".
+ "href=\"$link_attr{'-href'}\" ".
+ "type=\"$link_attr{'-type'}\" ".
+ "/>\n";
+
+ $href_params{'extra_options'} = '--no-merges';
+ $link_attr{'-href'} = href(%href_params);
+ $link_attr{'-title'} .= ' (no merges)';
+ print "<link ".
+ "rel=\"$link_attr{'-rel'}\" ".
+ "title=\"$link_attr{'-title'}\" ".
+ "href=\"$link_attr{'-href'}\" ".
+ "type=\"$link_attr{'-type'}\" ".
+ "/>\n";
+ }
+
+ } else {
+ printf('<link rel="alternate" title="%s projects list" '.
+ 'href="%s" type="text/plain; charset=utf-8" />'."\n",
+ 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",
+ esc_attr($site_name), href(project=>undef, action=>"opml"));
+ }
+}
+
sub git_header_html {
my $status = shift || "200 OK";
my $expires = shift;
@@ -3443,57 +3581,17 @@ 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";
- }
- }
- if (defined $project) {
- my %href_params = get_feed_info();
- if (!exists $href_params{'-title'}) {
- $href_params{'-title'} = 'log';
- }
-
- foreach my $format qw(RSS Atom) {
- my $type = lc($format);
- my %link_attr = (
- '-rel' => 'alternate',
- '-title' => "$project - $href_params{'-title'} - $format feed",
- '-type' => "application/$type+xml"
- );
-
- $href_params{'action'} = $type;
- $link_attr{'-href'} = href(%href_params);
- print "<link ".
- "rel=\"$link_attr{'-rel'}\" ".
- "title=\"$link_attr{'-title'}\" ".
- "href=\"$link_attr{'-href'}\" ".
- "type=\"$link_attr{'-type'}\" ".
- "/>\n";
-
- $href_params{'extra_options'} = '--no-merges';
- $link_attr{'-href'} = href(%href_params);
- $link_attr{'-title'} .= ' (no merges)';
- print "<link ".
- "rel=\"$link_attr{'-rel'}\" ".
- "title=\"$link_attr{'-title'}\" ".
- "href=\"$link_attr{'-href'}\" ".
- "type=\"$link_attr{'-type'}\" ".
- "/>\n";
+ print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
}
-
- } 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"));
- printf('<link rel="alternate" title="%s projects feeds" '.
- 'href="%s" type="text/x-opml" />'."\n",
- $site_name, href(project=>undef, action=>"opml"));
}
+ print_feed_meta()
+ if ($status eq '200 OK');
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 +3604,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 +3696,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 +3710,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 +3824,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 +3880,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 +5122,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 +5134,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 +5366,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 +5375,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 +5401,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 +5453,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 +5562,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 +5749,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 +5762,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 +5772,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 +5921,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 +5941,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 +6003,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 +6471,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);
diff --git a/gitweb/static/gitweb.css b/gitweb/static/gitweb.css
index 4132aab..79d7eeb 100644
--- a/gitweb/static/gitweb.css
+++ b/gitweb/static/gitweb.css
@@ -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 7f4928e..7654f1b 100644
--- 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;
diff --git a/http-backend.c b/http-backend.c
index 14c90c2..8501504 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -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);
diff --git a/http-fetch.c b/http-fetch.c
index 762c750..923904f 100644
--- a/http-fetch.c
+++ b/http-fetch.c
@@ -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;
}
diff --git a/http-push.c b/http-push.c
index c9bcd11..ff41a0e 100644
--- a/http-push.c
+++ b/http-push.c
@@ -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 0a5011f..9e76772 100644
--- 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 173f74c..5c6e243 100644
--- 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
diff --git a/merge-recursive.c b/merge-recursive.c
index 875859f..16c2dbe 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -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);
diff --git a/name-hash.c b/name-hash.c
index 0031d78..c6b6a3f 100644
--- a/name-hash.c
+++ b/name-hash.c
@@ -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;
}
diff --git a/notes-cache.c b/notes-cache.c
index dee6d62..4c8984e 100644
--- a/notes-cache.c
+++ b/notes-cache.c
@@ -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
index 0000000..71c4d45
--- /dev/null
+++ b/notes-merge.c
@@ -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
index 0000000..168a672
--- /dev/null
+++ b/notes-merge.h
@@ -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 70d0013..a013c1b 100644
--- a/notes.c
+++ b/notes.c
@@ -150,86 +150,6 @@ static struct leaf_node *note_tree_find(struct notes_tree *t,
}
/*
- * 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.
* Otherwise replace the int_node at the given index in the given parent node
@@ -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 5106761..83bd6e0 100644
--- a/notes.h
+++ b/notes.h
@@ -12,7 +12,10 @@
* 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
@@ -44,6 +48,20 @@ extern struct notes_tree {
} 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
*
* Default behaviour is to initialize the notes tree from the tree object
@@ -76,11 +94,24 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
/*
* Add the given note object to the given notes_tree structure
*
+ * If there already exists a note for the given object_sha1, the given
+ * combine_notes function is invoked to break the tie. If not given (i.e.
+ * combine_notes == NULL), the default combine_notes function for the given
+ * notes_tree is used.
+ *
+ * Passing note_sha1 == null_sha1 indicates the addition of an
+ * empty/non-existent note. This is a (potentially expensive) no-op unless
+ * there already exists a note for the given object_sha1, AND combining that
+ * note with the empty note (using the given combine_notes function) results
+ * in a new/changed note.
+ *
+ * Returns zero on success; non-zero means combine_notes failed.
+ *
* IMPORTANT: The changes made by add_note() to the given notes_tree structure
* are not persistent until a subsequent call to write_notes_tree() returns
* zero.
*/
-void add_note(struct notes_tree *t, const unsigned char *object_sha1,
+int add_note(struct notes_tree *t, const unsigned char *object_sha1,
const unsigned char *note_sha1, combine_notes_fn combine_notes);
/*
@@ -105,11 +136,18 @@ const unsigned char *get_note(struct notes_tree *t,
/*
* Copy a note from one object to another in the given notes_tree.
*
- * Fails if the to_obj already has a note unless 'force' is true.
+ * Returns 1 if the to_obj already has a note and 'force' is false. Otherwise,
+ * returns non-zero if 'force' is true, but the given combine_notes function
+ * failed to combine from_obj's note with to_obj's existing note.
+ * Returns zero on success.
+ *
+ * IMPORTANT: The changes made by copy_note() to the given notes_tree structure
+ * are not persistent until a subsequent call to write_notes_tree() returns
+ * zero.
*/
int copy_note(struct notes_tree *t,
const unsigned char *from_obj, const unsigned char *to_obj,
- int force, combine_notes_fn combine_fn);
+ int force, combine_notes_fn combine_notes);
/*
* Flags controlling behaviour of for_each_note()
@@ -151,6 +189,7 @@ int copy_note(struct notes_tree *t,
* notes tree) from within the callback:
* - add_note()
* - remove_note()
+ * - copy_note()
* - free_notes()
*/
typedef int each_note_fn(const unsigned char *object_sha1,
diff --git a/parse-options.c b/parse-options.c
index 0fa79bc..42b51ef 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -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)
diff --git a/parse-options.h b/parse-options.h
index ae8647d..31ec5d2 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -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 a2c9d1e..8951333 100644
--- 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;
diff --git a/pretty.c b/pretty.c
index f85444b..8549934 100644
--- 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);
diff --git a/read-cache.c b/read-cache.c
index 1f42473..4f2e890 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -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 */
diff --git a/reflog-walk.c b/reflog-walk.c
index 4879615..5d81d39 100644
--- a/reflog-walk.c
+++ b/reflog-walk.c
@@ -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,
diff --git a/revision.c b/revision.c
index f8d4f87..7b9eaef 100644
--- a/revision.c
+++ b/revision.c
@@ -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 346ef2e..91887a4 100644
--- 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;
}
diff --git a/sha1_file.c b/sha1_file.c
index 0cd9435..1cafdfa 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -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)
diff --git a/sha1_name.c b/sha1_name.c
index a96de0b..709ff2e 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -7,6 +7,8 @@
#include "refs.h"
#include "remote.h"
+static int get_sha1_oneline(const char *, unsigned char *, struct commit_list *);
+
static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
{
struct alternate_object_database *alt;
@@ -206,7 +208,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++;
@@ -560,6 +564,8 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
expected_type = OBJ_BLOB;
else if (sp[0] == '}')
expected_type = OBJ_NONE;
+ else if (sp[0] == '/')
+ expected_type = OBJ_COMMIT;
else
return -1;
@@ -574,19 +580,37 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
if (!o || (!o->parsed && !parse_object(o->sha1)))
return -1;
hashcpy(sha1, o->sha1);
+ return 0;
}
- else {
+
+ /*
+ * At this point, the syntax look correct, so
+ * if we do not get the needed object, we should
+ * barf.
+ */
+ o = peel_to_type(name, len, o, expected_type);
+ if (!o)
+ return -1;
+
+ hashcpy(sha1, o->sha1);
+ if (sp[0] == '/') {
+ /* "$commit^{/foo}" */
+ char *prefix;
+ int ret;
+ struct commit_list *list = NULL;
+
/*
- * At this point, the syntax look correct, so
- * if we do not get the needed object, we should
- * barf.
+ * $commit^{/}. Some regex implementation may reject.
+ * We don't need regex anyway. '' pattern always matches.
*/
- o = peel_to_type(name, len, o, expected_type);
- if (o) {
- hashcpy(sha1, o->sha1);
+ if (sp[1] == '}')
return 0;
- }
- return -1;
+
+ prefix = xstrndup(sp + 1, name + len - 1 - (sp + 1));
+ commit_list_insert((struct commit *)o, &list);
+ ret = get_sha1_oneline(prefix, sha1, list);
+ free(prefix);
+ return ret;
}
return 0;
}
@@ -684,15 +708,14 @@ static int handle_one_ref(const char *path,
if (object->type != OBJ_COMMIT)
return 0;
commit_list_insert_by_date((struct commit *)object, list);
- object->flags |= ONELINE_SEEN;
return 0;
}
-static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
+static int get_sha1_oneline(const char *prefix, unsigned char *sha1,
+ struct commit_list *list)
{
- struct commit_list *list = NULL, *backup = NULL, *l;
- int retval = -1;
- char *temp_commit_buffer = NULL;
+ struct commit_list *backup = NULL, *l;
+ int found = 0;
regex_t regex;
if (prefix[0] == '!') {
@@ -704,41 +727,45 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
if (regcomp(&regex, prefix, REG_EXTENDED))
die("Invalid search pattern: %s", prefix);
- for_each_ref(handle_one_ref, &list);
- for (l = list; l; l = l->next)
+ for (l = list; l; l = l->next) {
+ l->item->object.flags |= ONELINE_SEEN;
commit_list_insert(l->item, &backup);
+ }
while (list) {
- char *p;
+ char *p, *to_free = NULL;
struct commit *commit;
enum object_type type;
unsigned long size;
+ int matches;
commit = pop_most_recent_commit(&list, ONELINE_SEEN);
if (!parse_object(commit->object.sha1))
continue;
- free(temp_commit_buffer);
if (commit->buffer)
p = commit->buffer;
else {
p = read_sha1_file(commit->object.sha1, &type, &size);
if (!p)
continue;
- temp_commit_buffer = p;
+ to_free = p;
}
- if (!(p = strstr(p, "\n\n")))
- continue;
- if (!regexec(&regex, p + 2, 0, NULL, 0)) {
+
+ p = strstr(p, "\n\n");
+ matches = p && !regexec(&regex, p + 2, 0, NULL, 0);
+ free(to_free);
+
+ if (matches) {
hashcpy(sha1, commit->object.sha1);
- retval = 0;
+ found = 1;
break;
}
}
regfree(&regex);
- free(temp_commit_buffer);
free_commit_list(list);
for (l = backup; l; l = l->next)
clear_commit_marks(l->item, ONELINE_SEEN);
- return retval;
+ free_commit_list(backup);
+ return found ? 0 : -1;
}
struct grab_nth_branch_switch_cbdata {
@@ -934,6 +961,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 +1091,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,17 +1122,21 @@ 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 */
- return get_sha1_oneline(name + 2, sha1);
+ if (namelen > 2 && name[1] == '/') {
+ struct commit_list *list = NULL;
+ for_each_ref(handle_one_ref, &list);
+ return get_sha1_oneline(name + 2, sha1, list);
+ }
if (namelen < 3 ||
name[2] != ':' ||
name[1] < '0' || '3' < name[1])
@@ -1079,7 +1145,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 +1170,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 +1198,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 +1214,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)
diff --git a/strbuf.c b/strbuf.c
index 65b4cf4..9b3c445 100644
--- 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);
-}
diff --git a/submodule.c b/submodule.c
index 91a4758..6f1c107 100644
--- a/submodule.c
+++ b/submodule.c
@@ -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;
diff --git a/submodule.h b/submodule.h
index 386f410..4729023 100644
--- a/submodule.h
+++ b/submodule.h
@@ -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]);
diff --git a/symlinks.c b/symlinks.c
index 8860120..3cacebd 100644
--- a/symlinks.c
+++ b/symlinks.c
@@ -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;
}
/*
diff --git a/t/Makefile b/t/Makefile
index 73c6ec4..47cbeb6 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -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
diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh
index 212bd60..d34208c 100644
--- a/t/annotate-tests.sh
+++ b/t/annotate-tests.sh
@@ -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' \
diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh
index e733f65..3f24384 100644
--- a/t/lib-httpd.sh
+++ b/t/lib-httpd.sh
@@ -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"
diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf
index 4961505..0a4cdfa 100644
--- a/t/lib-httpd/apache.conf
+++ b/t/lib-httpd/apache.conf
@@ -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
index 0000000..f2fbcad
--- /dev/null
+++ b/t/lib-httpd/passwd
@@ -0,0 +1 @@
+user@host:nKpa8pZUHx/ic
diff --git a/t/t0050-filesystem.sh b/t/t0050-filesystem.sh
index 057c97c..1542cf6 100755
--- a/t/t0050-filesystem.sh
+++ b/t/t0050-filesystem.sh
@@ -4,8 +4,8 @@ test_description='Various filesystem issues'
. ./test-lib.sh
-auml=`printf '\xc3\xa4'`
-aumlcdiar=`printf '\x61\xcc\x88'`
+auml=$(printf '\303\244')
+aumlcdiar=$(printf '\141\314\210')
case_insensitive=
unibad=
diff --git a/t/t1412-reflog-loop.sh b/t/t1412-reflog-loop.sh
new file mode 100755
index 0000000..7f519e5
--- /dev/null
+++ b/t/t1412-reflog-loop.sh
@@ -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
diff --git a/t/t1506-rev-parse-diagnosis.sh b/t/t1506-rev-parse-diagnosis.sh
index 0eeeb0e..9f8adb1 100755
--- a/t/t1506-rev-parse-diagnosis.sh
+++ b/t/t1506-rev-parse-diagnosis.sh
@@ -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/t1511-rev-parse-caret.sh b/t/t1511-rev-parse-caret.sh
new file mode 100755
index 0000000..e043cb7
--- /dev/null
+++ b/t/t1511-rev-parse-caret.sh
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+test_description='tests for ref^{stuff}'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ echo blob >a-blob &&
+ git tag -a -m blob blob-tag `git hash-object -w a-blob`
+ mkdir a-tree &&
+ echo moreblobs >a-tree/another-blob &&
+ git add . &&
+ TREE_SHA1=`git write-tree` &&
+ git tag -a -m tree tree-tag "$TREE_SHA1" &&
+ git commit -m Initial &&
+ git tag -a -m commit commit-tag &&
+ git branch ref &&
+ git checkout master &&
+ echo modified >>a-blob &&
+ git add -u &&
+ git commit -m Modified
+'
+
+test_expect_success 'ref^{non-existent}' '
+ test_must_fail git rev-parse ref^{non-existent}
+'
+
+test_expect_success 'ref^{}' '
+ git rev-parse ref >expected &&
+ git rev-parse ref^{} >actual &&
+ test_cmp expected actual &&
+ git rev-parse commit-tag^{} >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'ref^{commit}' '
+ git rev-parse ref >expected &&
+ git rev-parse ref^{commit} >actual &&
+ test_cmp expected actual &&
+ git rev-parse commit-tag^{commit} >actual &&
+ test_cmp expected actual &&
+ test_must_fail git rev-parse tree-tag^{commit} &&
+ test_must_fail git rev-parse blob-tag^{commit}
+'
+
+test_expect_success 'ref^{tree}' '
+ echo $TREE_SHA1 >expected &&
+ git rev-parse ref^{tree} >actual &&
+ test_cmp expected actual &&
+ git rev-parse commit-tag^{tree} >actual &&
+ test_cmp expected actual &&
+ git rev-parse tree-tag^{tree} >actual &&
+ test_cmp expected actual &&
+ test_must_fail git rev-parse blob-tag^{tree}
+'
+
+test_expect_success 'ref^{/.}' '
+ git rev-parse master >expected &&
+ git rev-parse master^{/.} >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'ref^{/non-existent}' '
+ test_must_fail git rev-parse master^{/non-existent}
+'
+
+test_expect_success 'ref^{/Initial}' '
+ git rev-parse ref >expected &&
+ git rev-parse master^{/Initial} >actual &&
+ test_cmp expected actual
+'
+
+test_done
diff --git a/t/t2006-checkout-index-basic.sh b/t/t2006-checkout-index-basic.sh
new file mode 100755
index 0000000..b855983
--- /dev/null
+++ b/t/t2006-checkout-index-basic.sh
@@ -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
index 0000000..809fafe
--- /dev/null
+++ b/t/t2107-update-index-basic.sh
@@ -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_success '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
index 0000000..490e052
--- /dev/null
+++ b/t/t3004-ls-files-basic.sh
@@ -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
diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh
index 20d4f11..34794f8 100755
--- a/t/t3030-merge-recursive.sh
+++ b/t/t3030-merge-recursive.sh
@@ -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
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index f54a533..f308235 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -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'
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 7e84ab9..dc2e04a 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -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
diff --git a/t/t3303-notes-subtrees.sh b/t/t3303-notes-subtrees.sh
index 75ec187..704aee8 100755
--- a/t/t3303-notes-subtrees.sh
+++ b/t/t3303-notes-subtrees.sh
@@ -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
index 0000000..24d82b4
--- /dev/null
+++ b/t/t3308-notes-merge.sh
@@ -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
index 0000000..461fd84
--- /dev/null
+++ b/t/t3309-notes-merge-auto-resolve.sh
@@ -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
index 0000000..4ec4d11
--- /dev/null
+++ b/t/t3310-notes-merge-manual-resolve.sh
@@ -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
index 0000000..93516ef
--- /dev/null
+++ b/t/t3311-notes-merge-fanout.sh
@@ -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 ref