Code

Merge branch 'jk/argv-array' into maint
authorJunio C Hamano <gitster@pobox.com>
Wed, 26 Oct 2011 23:13:31 +0000 (16:13 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 26 Oct 2011 23:13:31 +0000 (16:13 -0700)
* jk/argv-array:
  run_hook: use argv_array API
  checkout: use argv_array API
  bisect: use argv_array API
  quote: provide sq_dequote_to_argv_array
  refactor argv_array into generic code
  quote.h: fix bogus comment
  add sha1_array API docs

458 files changed:
.gitignore
Documentation/.gitignore
Documentation/Makefile
Documentation/RelNotes/1.7.6.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.6.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.6.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.6.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.7.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.7.txt [new file with mode: 0644]
Documentation/SubmittingPatches
Documentation/config.txt
Documentation/diff-options.txt
Documentation/git-am.txt
Documentation/git-annotate.txt
Documentation/git-archive.txt
Documentation/git-bisect.txt
Documentation/git-branch.txt
Documentation/git-check-attr.txt
Documentation/git-check-ref-format.txt
Documentation/git-cherry-pick.txt
Documentation/git-cherry.txt
Documentation/git-citool.txt
Documentation/git-clean.txt
Documentation/git-clone.txt
Documentation/git-commit-tree.txt
Documentation/git-commit.txt
Documentation/git-count-objects.txt
Documentation/git-cvsexportcommit.txt
Documentation/git-daemon.txt
Documentation/git-diff-files.txt
Documentation/git-diff-index.txt
Documentation/git-difftool.txt
Documentation/git-fast-export.txt
Documentation/git-fast-import.txt
Documentation/git-fetch-pack.txt
Documentation/git-fetch.txt
Documentation/git-filter-branch.txt
Documentation/git-format-patch.txt
Documentation/git-fsck-objects.txt
Documentation/git-gc.txt
Documentation/git-get-tar-commit-id.txt
Documentation/git-grep.txt
Documentation/git-gui.txt
Documentation/git-help.txt
Documentation/git-http-backend.txt
Documentation/git-http-fetch.txt
Documentation/git-http-push.txt
Documentation/git-imap-send.txt
Documentation/git-init-db.txt
Documentation/git-init.txt
Documentation/git-instaweb.txt
Documentation/git-log.txt
Documentation/git-lost-found.txt
Documentation/git-mailinfo.txt
Documentation/git-mailsplit.txt
Documentation/git-merge-file.txt
Documentation/git-merge-index.txt
Documentation/git-merge-one-file.txt
Documentation/git-merge-tree.txt
Documentation/git-mergetool--lib.txt
Documentation/git-mergetool.txt
Documentation/git-mktag.txt
Documentation/git-mktree.txt
Documentation/git-mv.txt
Documentation/git-notes.txt
Documentation/git-pack-redundant.txt
Documentation/git-pack-refs.txt
Documentation/git-parse-remote.txt
Documentation/git-patch-id.txt
Documentation/git-peek-remote.txt
Documentation/git-prune-packed.txt
Documentation/git-prune.txt
Documentation/git-pull.txt
Documentation/git-push.txt
Documentation/git-read-tree.txt
Documentation/git-rebase.txt
Documentation/git-receive-pack.txt
Documentation/git-reflog.txt
Documentation/git-relink.txt
Documentation/git-remote-ext.txt
Documentation/git-remote-fd.txt
Documentation/git-remote-helpers.txt
Documentation/git-remote-testgit.txt [new file with mode: 0644]
Documentation/git-remote.txt
Documentation/git-repack.txt
Documentation/git-repo-config.txt
Documentation/git-request-pull.txt
Documentation/git-rerere.txt
Documentation/git-rev-parse.txt
Documentation/git-revert.txt
Documentation/git-rm.txt
Documentation/git-send-email.txt
Documentation/git-send-pack.txt
Documentation/git-sh-i18n--envsubst.txt
Documentation/git-sh-i18n.txt
Documentation/git-sh-setup.txt
Documentation/git-shell.txt
Documentation/git-show-branch.txt
Documentation/git-show-index.txt
Documentation/git-show.txt
Documentation/git-stash.txt
Documentation/git-status.txt
Documentation/git-stripspace.txt
Documentation/git-submodule.txt
Documentation/git-svn.txt
Documentation/git-symbolic-ref.txt
Documentation/git-tag.txt
Documentation/git-tar-tree.txt
Documentation/git-unpack-file.txt
Documentation/git-unpack-objects.txt
Documentation/git-update-index.txt
Documentation/git-update-ref.txt
Documentation/git-update-server-info.txt
Documentation/git-upload-archive.txt
Documentation/git-upload-pack.txt
Documentation/git-var.txt
Documentation/git-verify-pack.txt
Documentation/git-verify-tag.txt
Documentation/git-web--browse.txt
Documentation/git-whatchanged.txt
Documentation/git-write-tree.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/gitcvs-migration.txt
Documentation/gitdiffcore.txt
Documentation/gitk.txt
Documentation/gitnamespaces.txt [new file with mode: 0644]
Documentation/gitrepository-layout.txt
Documentation/gittutorial-2.txt
Documentation/gittutorial.txt
Documentation/gitworkflows.txt
Documentation/glossary-content.txt
Documentation/howto/maintain-git.txt
Documentation/rev-list-options.txt
Documentation/technical/api-builtin.txt
Documentation/technical/api-gitattributes.txt
Documentation/technical/api-ref-iteration.txt [new file with mode: 0644]
Documentation/technical/api-string-list.txt
Documentation/technical/index-format.txt
Documentation/technical/pack-protocol.txt
GIT-VERSION-GEN
INSTALL
Makefile
RelNotes
abspath.c
archive-tar.c
archive-zip.c
archive.c
archive.h
attr.c
attr.h
bisect.c
bisect.h
branch.c
branch.h
builtin/apply.c
builtin/archive.c
builtin/bisect--helper.c
builtin/branch.c
builtin/bundle.c
builtin/check-attr.c
builtin/check-ref-format.c
builtin/checkout-index.c
builtin/checkout.c
builtin/clean.c
builtin/clone.c
builtin/commit.c
builtin/config.c
builtin/describe.c
builtin/diff.c
builtin/fast-export.c
builtin/fetch-pack.c
builtin/fetch.c
builtin/fmt-merge-msg.c
builtin/fsck.c
builtin/grep.c
builtin/index-pack.c
builtin/init-db.c
builtin/log.c
builtin/ls-files.c
builtin/ls-tree.c
builtin/merge.c
builtin/pack-objects.c
builtin/patch-id.c
builtin/push.c
builtin/receive-pack.c
builtin/reflog.c
builtin/remote.c
builtin/reset.c
builtin/revert.c
builtin/show-branch.c
builtin/tag.c
builtin/unpack-objects.c
builtin/update-ref.c
builtin/upload-archive.c
builtin/verify-pack.c
bundle.c
bundle.h
cache.h
color.c
color.h
combine-diff.c
commit.c
commit.h
compat/cygwin.c
compat/mingw.c
compat/mingw.h
compat/obstack.c [new file with mode: 0644]
compat/obstack.h [new file with mode: 0644]
config.c
connect.c
contrib/completion/git-completion.bash
contrib/convert-objects/git-convert-objects.txt
contrib/fast-import/git-p4
contrib/fast-import/git-p4.txt
contrib/gitview/gitview.txt
contrib/hooks/post-receive-email
contrib/svn-fe/svn-fe.txt
convert.c
convert.h [new file with mode: 0644]
csum-file.c
csum-file.h
date.c
diff-lib.c
diff.c
diff.h
diffcore-pickaxe.c
diffcore.h
entry.c
environment.c
fast-import.c
fsck.c
generate-cmdlist.sh
git-am.sh
git-bisect.sh
git-compat-util.h
git-difftool--helper.sh
git-filter-branch.sh
git-instaweb.sh
git-merge-one-file.sh
git-mergetool--lib.sh
git-mergetool.sh
git-pull.sh
git-rebase--interactive.sh
git-rebase.sh
git-remote-testgit.py
git-sh-i18n.sh
git-sh-setup.sh
git-stash.sh
git-submodule.sh
git-svn.perl
git.c
git_remote_helpers/git/exporter.py
git_remote_helpers/git/importer.py
git_remote_helpers/git/non_local.py
git_remote_helpers/git/repo.py
git_remote_helpers/util.py
gitweb/INSTALL
gitweb/Makefile
gitweb/README
gitweb/gitweb.perl
gitweb/static/js/blame_incremental.js
gitweb/static/js/javascript-detection.js
graph.c
grep.c
grep.h
help.c
http-backend.c
http-fetch.c
http-push.c
http.c
http.h
kwset.c [new file with mode: 0644]
kwset.h [new file with mode: 0644]
ll-merge.c
log-tree.c
merge-recursive.c
merge-recursive.h
mergetools/araxis [new file with mode: 0644]
mergetools/bc3 [new file with mode: 0644]
mergetools/defaults [new file with mode: 0644]
mergetools/diffuse [new file with mode: 0644]
mergetools/ecmerge [new file with mode: 0644]
mergetools/emerge [new file with mode: 0644]
mergetools/kdiff3 [new file with mode: 0644]
mergetools/kompare [new file with mode: 0644]
mergetools/meld [new file with mode: 0644]
mergetools/opendiff [new file with mode: 0644]
mergetools/p4merge [new file with mode: 0644]
mergetools/tkdiff [new file with mode: 0644]
mergetools/tortoisemerge [new file with mode: 0644]
mergetools/vim [new file with mode: 0644]
mergetools/xxdiff [new file with mode: 0644]
notes-merge.c
notes.c
pack-check.c
pack-write.c
pack.h
pager.c
parse-options-cb.c [new file with mode: 0644]
parse-options.c
parse-options.h
path.c
quote.c
read-cache.c
refs.c
refs.h
remote-curl.c
remote.c
remote.h
revision.c
revision.h
run-command.c
setup.c
sh-i18n--envsubst.c
sha1_file.c
strbuf.c
strbuf.h
streaming.c [new file with mode: 0644]
streaming.h [new file with mode: 0644]
string-list.c
string-list.h
submodule.c
submodule.h
t/aggregate-results.sh
t/gitweb-lib.sh
t/lib-diff-alternative.sh [new file with mode: 0644]
t/t0003-attributes.sh
t/t0006-date.sh
t/t0021-conversion.sh
t/t0040-parse-options.sh
t/t1013-loose-object-format.sh [new file with mode: 0755]
t/t1013/objects/14/9cedb5c46929d18e0f118e9fa31927487af3b6 [new file with mode: 0644]
t/t1013/objects/16/56f9233d999f61ef23ef390b9c71d75399f435 [new file with mode: 0644]
t/t1013/objects/1e/72a6b2c4a577ab0338860fa9fe87f761fc9bbd [new file with mode: 0644]
t/t1013/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 [new file with mode: 0644]
t/t1013/objects/2e/65efe2a145dda7ee51d1741299f848e5bf752e [new file with mode: 0644]
t/t1013/objects/6b/aee0540ea990d9761a3eb9ab183003a71c3696 [new file with mode: 0644]
t/t1013/objects/70/e6a83d8dcb26fc8bc0cf702e2ddeb6adca18fd [new file with mode: 0644]
t/t1013/objects/76/e7fa9941f4d5f97f64fea65a2cba436bc79cbb [new file with mode: 0644]
t/t1013/objects/78/75c6237d3fcdd0ac2f0decc7d3fa6a50b66c09 [new file with mode: 0644]
t/t1013/objects/7a/37b887a73791d12d26c0d3e39568a8fb0fa6e8 [new file with mode: 0644]
t/t1013/objects/85/df50785d62d3b05ab03d9cbf7e4a0b49449730 [new file with mode: 0644]
t/t1013/objects/8d/4e360d6c70fbd72411991c02a09c442cf7a9fa [new file with mode: 0644]
t/t1013/objects/95/b1625de3ba8b2214d1e0d0591138aea733f64f [new file with mode: 0644]
t/t1013/objects/9a/e9e86b7bd6cb1472d9373702d8249973da0832 [new file with mode: 0644]
t/t1013/objects/bd/15045f6ce8ff75747562173640456a394412c8 [new file with mode: 0644]
t/t1013/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 [new file with mode: 0644]
t/t1013/objects/f8/16d5255855ac160652ee5253b06cd8ee14165a [new file with mode: 0644]
t/t1020-subdirectory.sh
t/t1300-repo-config.sh
t/t1304-default-acl.sh
t/t1402-check-ref-format.sh
t/t1412-reflog-loop.sh
t/t1450-fsck.sh
t/t2018-checkout-branch.sh
t/t2022-checkout-paths.sh [new file with mode: 0755]
t/t3005-ls-files-relative.sh [new file with mode: 0755]
t/t3030-merge-recursive.sh
t/t3103-ls-tree-misc.sh [new file with mode: 0755]
t/t3200-branch.sh
t/t3307-notes-man.sh
t/t3404-rebase-interactive.sh
t/t3409-rebase-preserve-merges.sh
t/t3411-rebase-preserve-around-merges.sh
t/t3503-cherry-pick-root.sh
t/t3900-i18n-commit.sh
t/t3902-quoted.sh
t/t3903-stash.sh
t/t3905-stash-include-untracked.sh [new file with mode: 0755]
t/t4012-diff-binary.sh
t/t4014-format-patch.sh
t/t4033-diff-patience.sh
t/t4048-diff-combined-binary.sh [new file with mode: 0755]
t/t4049-diff-stat-count.sh [new file with mode: 0755]
t/t4050-diff-histogram.sh [new file with mode: 0755]
t/t4150-am.sh
t/t4151-am-abort.sh
t/t4254-am-corrupt.sh [new file with mode: 0755]
t/t5000-tar-tree.sh
t/t5302-pack-index.sh
t/t5505-remote.sh
t/t5509-fetch-push-namespaces.sh [new file with mode: 0755]
t/t5526-fetch-submodules.sh
t/t5531-deep-submodule-push.sh
t/t5540-http-push.sh
t/t5601-clone.sh
t/t5707-clone-detached.sh [new file with mode: 0755]
t/t5708-clone-config.sh [new file with mode: 0755]
t/t5800-remote-helpers.sh
t/t6019-rev-list-ancestry-path.sh
t/t6020-merge-df.sh
t/t6022-merge-rename.sh
t/t6023-merge-file.sh
t/t6027-merge-binary.sh
t/t6030-bisect-porcelain.sh
t/t6036-recursive-corner-cases.sh
t/t6040-tracking-info.sh
t/t6042-merge-rename-corner-cases.sh [new file with mode: 0755]
t/t7004-tag.sh
t/t7006-pager.sh
t/t7008-grep-binary.sh
t/t7102-reset.sh
t/t7400-submodule-basic.sh
t/t7401-submodule-summary.sh
t/t7403-submodule-sync.sh
t/t7406-submodule-update.sh
t/t7407-submodule-foreach.sh
t/t7503-pre-commit-hook.sh
t/t7508-status.sh
t/t7602-merge-octopus-many.sh
t/t7607-merge-overwrite.sh
t/t7609-merge-co-error-msgs.sh
t/t7610-mergetool.sh
t/t7800-difftool.sh
t/t7810-grep.sh
t/t9158-git-svn-mergeinfo.sh
t/t9159-git-svn-no-parent-mergeinfo.sh [new file with mode: 0755]
t/t9160-git-svn-preserve-empty-dirs.sh [new file with mode: 0755]
t/t9161-git-svn-mergeinfo-push.sh [new file with mode: 0755]
t/t9161/branches.dump [new file with mode: 0644]
t/t9200-git-cvsexportcommit.sh
t/t9300-fast-import.sh
t/t9350-fast-export.sh
t/t9800-git-p4.sh
t/test-binary-1.png [new file with mode: 0644]
t/test-binary-2.png [new file with mode: 0644]
t/test-lib.sh
t/test4012.png [deleted file]
t/test9200a.png [deleted file]
t/test9200b.png [deleted file]
templates/hooks--post-commit.sample [deleted file]
templates/hooks--post-receive.sample [deleted file]
templates/hooks--pre-commit.sample
test-parse-options.c
test-path-utils.c
transport-helper.c
transport.c
transport.h
tree-walk.c
tree-walk.h
unpack-trees.c
unpack-trees.h
upload-pack.c
usage.c
userdiff.c
userdiff.h
ws.c
wt-status.c
xdiff/xdiff.h
xdiff/xdiffi.c
xdiff/xdiffi.h
xdiff/xhistogram.c [new file with mode: 0644]
xdiff/xpatience.c
xdiff/xprepare.c
xdiff/xutils.c
xdiff/xutils.h
zlib.c

index acffdfaae684dc49d7205d580fd8921ca4696f7f..8572c8c0b0199589a8c1875825f5b2e7e4dc4a86 100644 (file)
@@ -1,5 +1,6 @@
 /GIT-BUILD-OPTIONS
 /GIT-CFLAGS
+/GIT-LDFLAGS
 /GIT-GUI-VARS
 /GIT-VERSION-FILE
 /bin-wrappers/
index 1c3a9fead579a9b52037f1bbe245998db8a2f40b..d62aebd848b2a44f977ad4d7c4b75b6ff72b2163 100644 (file)
@@ -3,6 +3,7 @@
 *.[1-8]
 *.made
 *.texi
+*.pdf
 git.info
 gitman.info
 howto-index.txt
index 36989b7f6541cb8444385b64d2c955ae2a1f6d1b..6346a75dda72533667a96eea2bd65bb280383f99 100644 (file)
@@ -6,7 +6,7 @@ MAN5_TXT=gitattributes.txt gitignore.txt gitmodules.txt githooks.txt \
        gitrepository-layout.txt
 MAN7_TXT=gitcli.txt gittutorial.txt gittutorial-2.txt \
        gitcvs-migration.txt gitcore-tutorial.txt gitglossary.txt \
-       gitdiffcore.txt gitrevisions.txt gitworkflows.txt
+       gitdiffcore.txt gitnamespaces.txt gitrevisions.txt gitworkflows.txt
 
 MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT)
 MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT))
@@ -232,6 +232,7 @@ cmd-list.made: cmd-list.perl ../command-list.txt $(MAN1_TXT)
 clean:
        $(RM) *.xml *.xml+ *.html *.html+ *.1 *.5 *.7
        $(RM) *.texi *.texi+ *.texi++ git.info gitman.info
+       $(RM) *.pdf
        $(RM) howto-index.txt howto/*.html doc.dep
        $(RM) technical/api-*.html technical/api-index.txt
        $(RM) $(cmds_txt) *.made
diff --git a/Documentation/RelNotes/1.7.6.1.txt b/Documentation/RelNotes/1.7.6.1.txt
new file mode 100644 (file)
index 0000000..42e46ab
--- /dev/null
@@ -0,0 +1,63 @@
+Git v1.7.6.1 Release Notes
+==========================
+
+Fixes since v1.7.6
+------------------
+
+ * Various codepaths that invoked zlib deflate/inflate assumed that these
+   functions can compress or uncompress more than 4GB data in one call on
+   platforms with 64-bit long, which has been corrected.
+
+ * "git unexecutable" reported that "unexecutable" was not found, even
+   though the actual error was that "unexecutable" was found but did
+   not have a proper she-bang line to be executed.
+
+ * Error exits from $PAGER were silently ignored.
+
+ * "git checkout -b <branch>" was confused when attempting to create a
+   branch whose name ends with "-g" followed by hexadecimal digits,
+   and refused to work.
+
+ * "git checkout -b <branch>" sometimes wrote a bogus reflog entry,
+   causing later "git checkout -" to fail.
+
+ * "git diff --cc" learned to correctly ignore binary files.
+
+ * "git diff -c/--cc" mishandled a deletion that resolves a conflict, and
+   looked in the working tree instead.
+
+ * "git fast-export" forgot to quote pathnames with unsafe characters
+   in its output.
+
+ * "git fetch" over smart-http transport used to abort when the
+   repository was updated between the initial connection and the
+   subsequent object transfer.
+
+ * "git fetch" did not recurse into submodules in subdirectories.
+
+ * "git ls-tree" did not error out when asked to show a corrupt tree.
+
+ * "git pull" without any argument left an extra whitespace after the
+   command name in its reflog.
+
+ * "git push --quiet" was not really quiet.
+
+ * "git rebase -i -p" incorrectly dropped commits from side branches.
+
+ * "git reset [<commit>] paths..." did not reset the index entry correctly
+   for unmerged paths.
+
+ * "git submodule add" did not allow a relative repository path when
+   the superproject did not have any default remote url.
+
+ * "git submodule foreach" failed to correctly give the standard input to
+   the user-supplied command it invoked.
+
+ * submodules that the user has never showed interest in by running
+   "git submodule init" was incorrectly marked as interesting by "git
+   submodule sync".
+
+ * "git submodule update --quiet" was not really quiet.
+
+  * "git tag -l <glob>..." did not take multiple glob patterns from the
+   command line.
diff --git a/Documentation/RelNotes/1.7.6.2.txt b/Documentation/RelNotes/1.7.6.2.txt
new file mode 100644 (file)
index 0000000..67ae414
--- /dev/null
@@ -0,0 +1,8 @@
+Git v1.7.6.2 Release Notes
+==========================
+
+Fixes since v1.7.6.1
+--------------------
+
+ * v1.7.6.1 broke "git push --quiet"; it used to be a no-op against an old
+   version of Git running on the other end, but v1.7.6.1 made it abort.
diff --git a/Documentation/RelNotes/1.7.6.3.txt b/Documentation/RelNotes/1.7.6.3.txt
new file mode 100644 (file)
index 0000000..9597183
--- /dev/null
@@ -0,0 +1,24 @@
+Git v1.7.6.3 Release Notes
+==========================
+
+Fixes since v1.7.6.2
+--------------------
+
+ * "git -c var=value subcmd" misparsed the custom configuration when
+   value contained an equal sign.
+
+ * "git fetch" had a major performance regression, wasting many
+   needless cycles in a repository where there is no submodules
+   present. This was especially bad, when there were many refs.
+
+ * "git reflog $refname" did not default to the "show" subcommand as
+   the documentation advertised the command to do.
+
+ * "git reset" did not leave meaningful log message in the reflog.
+
+ * "git status --ignored" did not show ignored items when there is no
+   untracked items.
+
+ * "git tag --contains $commit" was unnecessarily inefficient.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.6.4.txt b/Documentation/RelNotes/1.7.6.4.txt
new file mode 100644 (file)
index 0000000..e19acac
--- /dev/null
@@ -0,0 +1,32 @@
+Git v1.7.6.4 Release Notes
+==========================
+
+Fixes since v1.7.6.3
+--------------------
+
+ * The error reporting logic of "git am" when the command is fed a file
+   whose mail-storage format is unknown was fixed.
+
+ * "git branch --set-upstream @{-1} foo" did not expand @{-1} correctly.
+
+ * "git check-ref-format --print" used to parrot a candidate string that
+   began with a slash (e.g. /refs/heads/master) without stripping it, to make
+   the result a suitably normalized string the caller can append to "$GIT_DIR/".
+
+ * "git clone" failed to clone locally from a ".git" file that itself
+   is not a directory but is a pointer to one.
+
+ * "git clone" from a local repository that borrows from another
+   object store using a relative path in its objects/info/alternates
+   file did not adjust the alternates in the resulting repository.
+
+ * "git describe --dirty" did not refresh the index before checking the
+   state of the working tree files.
+
+ * "git ls-files ../$path" that is run from a subdirectory reported errors
+   incorrectly when there is no such path that matches the given pathspec.
+
+ * "git mergetool" could loop forever prompting when nothing can be read
+   from the standard input.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.7.1.txt b/Documentation/RelNotes/1.7.7.1.txt
new file mode 100644 (file)
index 0000000..ac9b838
--- /dev/null
@@ -0,0 +1,60 @@
+Git v1.7.7.1 Release Notes
+==========================
+
+Fixes since v1.7.7
+------------------
+
+ * On some BSD systems, adding +s bit on directories is detrimental
+   (it is not necessary on BSD to begin with). "git init --shared"
+   has been updated to take this into account without extra makefile
+   settings on platforms the Makefile knows about.
+
+ * After incorrectly written third-party tools store a tag object in
+   HEAD, git diagnosed it as a repository corruption and refused to
+   proceed in order to avoid spreading the damage. We now gracefully
+   recover from such a situation by pretending as if the commit that
+   is pointed at by the tag were in HEAD.
+
+ * "git apply --whitespace=error" did not bother to report the exact
+   line number in the patch that introduced new blank lines at the end
+   of the file.
+
+ * "git apply --index" did not check corrupted patch.
+
+ * "git checkout $tree $directory/" resurrected paths locally removed or
+   modified only in the working tree in $directory/ that did not appear
+   in $directory of the given $tree. They should have been kept intact.
+
+ * "git diff $tree $path" used to apply the pathspec at the output stage,
+   reading the whole tree, wasting resources.
+
+ * The code to check for updated submodules during a "git fetch" of the
+   superproject had an unnecessary quadratic loop.
+
+ * "git fetch" from a large bundle did not enable the progress output.
+
+ * When "git fsck --lost-and-found" found that an empty blob object in the
+   object store is unreachable, it incorrectly reported an error after
+   writing the lost blob out successfully.
+
+ * "git filter-branch" did not refresh the index before checking that the
+   working tree was clean.
+
+ * "git grep $tree" when run with multiple threads had an unsafe access to
+   the object database that should have been protected with mutex.
+
+ * The "--ancestry-path" option to "git log" and friends misbehaved in a
+   history with complex criss-cross merges and showed an uninteresting
+   side history as well.
+
+ * Test t1304 assumed LOGNAME is always set, which may not be true on
+   some systems.
+
+ * Tests with --valgrind failed to find "mergetool" scriptlets.
+
+ * "git patch-id" miscomputed the patch-id in a patch that has a line longer
+   than 1kB.
+
+ * When an "exec" insn failed after modifying the index and/or the working
+   tree during "rebase -i", we now check and warn that the changes need to
+   be cleaned up.
diff --git a/Documentation/RelNotes/1.7.7.txt b/Documentation/RelNotes/1.7.7.txt
new file mode 100644 (file)
index 0000000..7655ccc
--- /dev/null
@@ -0,0 +1,134 @@
+Git v1.7.7 Release Notes
+========================
+
+Updates since v1.7.6
+--------------------
+
+ * The scripting part of the codebase is getting prepared for i18n/l10n.
+
+ * Interix, Cygwin and Minix ports got updated.
+
+ * Various updates to git-p4 (in contrib/), fast-import, and git-svn.
+
+ * Gitweb learned to read from /etc/gitweb-common.conf when it exists,
+   before reading from gitweb_config.perl or from /etc/gitweb.conf
+   (this last one is read only when per-repository gitweb_config.perl
+   does not exist).
+
+ * Various codepaths that invoked zlib deflate/inflate assumed that these
+   functions can compress or uncompress more than 4GB data in one call on
+   platforms with 64-bit long, which has been corrected.
+
+ * Git now recognizes loose objects written by other implementations that
+   use a non-standard window size for zlib deflation (e.g. Agit running on
+   Android with 4kb window). We used to reject anything that was not
+   deflated with 32kb window.
+
+ * Interaction between the use of pager and coloring of the output has
+   been improved, especially when a command that is not built-in was
+   involved.
+
+ * "git am" learned to pass the "--exclude=<path>" option through to underlying
+   "git apply".
+
+ * You can now feed many empty lines before feeding an mbox file to
+   "git am".
+
+ * "git archive" can be told to pass the output to gzip compression and
+   produce "archive.tar.gz".
+
+ * "git bisect" can be used in a bare repository (provided that the test
+   you perform per each iteration does not need a working tree, of
+   course).
+
+ * The length of abbreviated object names in "git branch -v" output
+   now honors the core.abbrev configuration variable.
+
+ * "git check-attr" can take relative paths from the command line.
+
+ * "git check-attr" learned an "--all" option to list the attributes for a
+   given path.
+
+ * "git checkout" (both the code to update the files upon checking out a
+   different branch and the code to checkout a specific set of files) learned
+   to stream the data from object store when possible, without having to
+   read the entire contents of a file into memory first. An earlier round
+   of this code that is not in any released version had a large leak but
+   now it has been plugged.
+
+ * "git clone" can now take a "--config key=value" option to set the
+   repository configuration options that affect the initial checkout.
+
+ * "git commit <paths>..." now lets you feed relative pathspecs that
+   refer to outside your current subdirectory.
+
+ * "git diff --stat" learned a --stat-count option to limit the output of
+   a diffstat report.
+
+ * "git diff" learned a "--histogram" option to use a different diff
+   generation machinery stolen from jgit, which might give better
+   performance.
+
+ * "git diff" had a weird worst case behaviour that can be triggered
+   when comparing files with potentially many places that could match.
+
+ * "git fetch", "git push" and friends no longer show connection
+   errors for addresses that couldn't be connected to when at least one
+   address succeeds (this is arguably a regression but a deliberate
+   one).
+
+ * "git grep" learned "--break" and "--heading" options, to let users mimic
+   the output format of "ack".
+
+ * "git grep" learned a "-W" option that shows wider context using the same
+   logic used by "git diff" to determine the hunk header.
+
+ * Invoking the low-level "git http-fetch" without "-a" option (which
+   git itself never did---normal users should not have to worry about
+   this) is now deprecated.
+
+ * The "--decorate" option to "git log" and its family learned to
+   highlight grafted and replaced commits.
+
+ * "git rebase master topci" no longer spews usage hints after giving
+   the "fatal: no such branch: topci" error message.
+
+ * The recursive merge strategy implementation got a fairly large
+   fix for many corner cases that may rarely happen in real world
+   projects (it has been verified that none of the 16000+ merges in
+   the Linux kernel history back to v2.6.12 is affected with the
+   corner case bugs this update fixes).
+
+ * "git stash" learned an "--include-untracked option".
+
+ * "git submodule update" used to stop at the first error updating a
+   submodule; it now goes on to update other submodules that can be
+   updated, and reports the ones with errors at the end.
+
+ * "git push" can be told with the "--recurse-submodules=check" option to
+   refuse pushing of the supermodule, if any of its submodules'
+   commits hasn't been pushed out to their remotes.
+
+ * "git upload-pack" and "git receive-pack" learned to pretend that only a
+   subset of the refs exist in a repository. This may help a site to
+   put many tiny repositories into one repository (this would not be
+   useful for larger repositories as repacking would be problematic).
+
+ * "git verify-pack" has been rewritten to use the "index-pack" machinery
+   that is more efficient in reading objects in packfiles.
+
+ * test scripts for gitweb tried to run even when CGI-related perl modules
+   are not installed; they now exit early when the latter are unavailable.
+
+Also contains various documentation updates and minor miscellaneous
+changes.
+
+
+Fixes since v1.7.6
+------------------
+
+Unless otherwise noted, all fixes in the 1.7.6.X maintenance track are
+included in this release.
+
+ * "git branch -m" and "git checkout -b" incorrectly allowed the tip
+   of the branch that is currently checked out updated.
index 938eccf2a5ac9dd629be1bc6bb53a59723c99083..0dbf2c9843dd3eed014d788892c8719036287308 100644 (file)
@@ -134,8 +134,7 @@ Another thing: NULL pointers shall be written as NULL, not as 0.
 
 (2) Generate your patch using git tools out of your commits.
 
-git based diff tools (git, Cogito, and StGIT included) generate
-unidiff which is the preferred format.
+git based diff tools generate unidiff which is the preferred format.
 
 You do not have to be afraid to use -M option to "git diff" or
 "git format-patch", if your patch involves file renames.  The
index 6b937771994f5b0a532b6f2cc522a9d3f35c9c09..eae75a39c0bcc779ed2c66ef6fa9b03a24a4a2fd 100644 (file)
@@ -147,7 +147,7 @@ advice.*::
 
 core.fileMode::
        If false, the executable bit differences between the index and
-       the working copy are ignored; useful on broken filesystems like FAT.
+       the working tree are ignored; useful on broken filesystems like FAT.
        See linkgit:git-update-index[1].
 +
 The default is true, except linkgit:git-clone[1] or linkgit:git-init[1]
@@ -179,7 +179,7 @@ is created.
 
 core.trustctime::
        If false, the ctime differences between the index and the
-       working copy are ignored; useful when the inode change time
+       working tree are ignored; useful when the inode change time
        is regularly modified by something outside Git (file system
        crawlers and some backup systems).
        See linkgit:git-update-index[1]. True by default.
@@ -292,7 +292,7 @@ core.ignoreStat::
        If true, commands which modify both the working tree and the index
        will mark the updated paths with the "assume unchanged" bit in the
        index. These marked files are then assumed to stay unchanged in the
-       working copy, until you mark them otherwise manually - Git will not
+       working tree, until you mark them otherwise manually - Git will not
        detect the file changes by lstat() calls. This is useful on systems
        where those are very slow, such as Microsoft Windows.
        See linkgit:git-update-index[1].
@@ -344,7 +344,9 @@ core.logAllRefUpdates::
        SHA1, the date/time and the reason of the update, but
        only when the file exists.  If this configuration
        variable is set to true, missing "$GIT_DIR/logs/<ref>"
-       file is automatically created for branch heads.
+       file is automatically created for branch heads (i.e. under
+       refs/heads/), remote refs (i.e. under refs/remotes/),
+       note refs (i.e. under refs/notes/), and the symbolic ref HEAD.
 +
 This information can be used to determine what commit
 was the tip of a branch "2 days ago".
@@ -676,7 +678,7 @@ branch.<name>.rebase::
 browser.<tool>.cmd::
        Specify the command to invoke the specified browser. The
        specified command is evaluated in shell with the URLs passed
-       as arguments. (See linkgit:git-web--browse[1].)
+       as arguments. (See linkgit:git-web{litdd}browse[1].)
 
 browser.<tool>.path::
        Override the path for the given tool that may be used to
@@ -1196,6 +1198,14 @@ http.proxy::
        environment variable (see linkgit:curl[1]).  This can be overridden
        on a per-remote basis; see remote.<name>.proxy
 
+http.cookiefile::
+       File containing previously stored cookie lines which should be used
+       in the git http session, if they match the server. The file format
+       of the file to read cookies from should be plain HTTP headers or
+       the Netscape/Mozilla cookie file format (see linkgit:curl[1]).
+       NOTE that the file specified with http.cookiefile is only used as
+       input. No cookies will be stored in the file.
+
 http.sslVerify::
        Whether to verify the SSL certificate when fetching or pushing
        over HTTPS. Can be overridden by the 'GIT_SSL_NO_VERIFY' environment
@@ -1443,7 +1453,8 @@ notes.rewriteRef::
        You may also specify this configuration several times.
 +
 Does not have a default value; you must configure this variable to
-enable note rewriting.
+enable note rewriting.  Set it to `refs/notes/commits` to enable
+rewriting for the default commit notes.
 +
 This setting can be overridden with the `GIT_NOTES_REWRITE_REF`
 environment variable, which must be a colon separated list of refs or
index 24f189f96b760dc9201a202827351ca82aab6780..5c53bdba948ea817e01e169a9fc183d4fc2d1313 100644 (file)
@@ -45,14 +45,24 @@ ifndef::git-format-patch[]
        Synonym for `-p --raw`.
 endif::git-format-patch[]
 
+--minimal::
+       Spend extra time to make sure the smallest possible
+       diff is produced.
+
 --patience::
        Generate a diff using the "patience diff" algorithm.
 
---stat[=<width>[,<name-width>]]::
+--stat[=<width>[,<name-width>[,<count>]]]::
        Generate a diffstat.  You can override the default
        output width for 80-column terminal by `--stat=<width>`.
        The width of the filename part can be controlled by
        giving another width to it separated by a comma.
+       By giving a third parameter `<count>`, you can limit the
+       output to the first `<count>` lines, followed by
+       `...` if there are more.
++
+These parameters can also be set individually with `--stat-width=<width>`,
+`--stat-name-width=<name-width>` and `--stat-count=<count>`.
 
 --numstat::
        Similar to `\--stat`, but shows number of added and
@@ -416,6 +426,17 @@ endif::git-format-patch[]
 --no-ext-diff::
        Disallow external diff drivers.
 
+--textconv::
+--no-textconv::
+       Allow (or disallow) external text conversion filters to be run
+       when comparing binary files. See linkgit:gitattributes[5] for
+       details. Because textconv filters are typically a one-way
+       conversion, the resulting diff is suitable for human
+       consumption, but cannot be applied. For this reason, textconv
+       filters are enabled by default only for linkgit:git-diff[1] and
+       linkgit:git-log[1], but not for linkgit:git-format-patch[1] or
+       diff plumbing commands.
+
 --ignore-submodules[=<when>]::
        Ignore changes to submodules in the diff generation. <when> can be
        either "none", "untracked", "dirty" or "all", which is the default
index 6b1b5af64e2daec1ecc8ee832f58f5186d740a79..887466d7777bb3cedce976b55d446f0f7803c423 100644 (file)
@@ -13,7 +13,8 @@ SYNOPSIS
         [--3way] [--interactive] [--committer-date-is-author-date]
         [--ignore-date] [--ignore-space-change | --ignore-whitespace]
         [--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>]
-        [--reject] [-q | --quiet] [--scissors | --no-scissors]
+        [--exclude=<path>] [--reject] [-q | --quiet]
+        [--scissors | --no-scissors]
         [(<mbox> | <Maildir>)...]
 'git am' (--continue | --skip | --abort)
 
@@ -87,6 +88,7 @@ default.   You can use `--no-utf8` to override this.
 -C<n>::
 -p<n>::
 --directory=<dir>::
+--exclude=<path>::
 --reject::
        These flags are passed to the 'git apply' (see linkgit:git-apply[1])
        program that applies
index 9eb75c37dae574f4f9d4699b795f53e94e6fa0dd..05fd482b74ed00445613c7cc9b8a0b019ae31550 100644 (file)
@@ -7,6 +7,7 @@ git-annotate - Annotate file lines with commit information
 
 SYNOPSIS
 --------
+[verse]
 'git annotate' [options] file [revision]
 
 DESCRIPTION
index 9c750e24446b58efc2bd8d5e44566d060eaa353a..ac7006e6400d85c31bf31a3022a38225373fe3f7 100644 (file)
@@ -101,6 +101,25 @@ tar.umask::
        details.  If `--remote` is used then only the configuration of
        the remote repository takes effect.
 
+tar.<format>.command::
+       This variable specifies a shell command through which the tar
+       output generated by `git archive` should be piped. The command
+       is executed using the shell with the generated tar file on its
+       standard input, and should produce the final output on its
+       standard output. Any compression-level options will be passed
+       to the command (e.g., "-9"). An output file with the same
+       extension as `<format>` will be use this format if no other
+       format is given.
++
+The "tar.gz" and "tgz" formats are defined automatically and default to
+`gzip -cn`. You may override them with custom commands.
+
+tar.<format>.remote::
+       If true, enable `<format>` for use by remote clients via
+       linkgit:git-upload-archive[1]. Defaults to false for
+       user-defined formats, but true for the "tar.gz" and "tgz"
+       formats.
+
 ATTRIBUTES
 ----------
 
@@ -123,32 +142,46 @@ while archiving any tree in your `$GIT_DIR/info/attributes` file.
 
 EXAMPLES
 --------
-git archive --format=tar --prefix=junk/ HEAD | (cd /var/tmp/ && tar xf -)::
+`git archive --format=tar --prefix=junk/ HEAD | (cd /var/tmp/ && tar xf -)`::
 
        Create a tar archive that contains the contents of the
        latest commit on the current branch, and extract it in the
        `/var/tmp/junk` directory.
 
-git archive --format=tar --prefix=git-1.4.0/ v1.4.0 | gzip >git-1.4.0.tar.gz::
+`git archive --format=tar --prefix=git-1.4.0/ v1.4.0 | gzip >git-1.4.0.tar.gz`::
 
        Create a compressed tarball for v1.4.0 release.
 
-git archive --format=tar --prefix=git-1.4.0/ v1.4.0{caret}\{tree\} | gzip >git-1.4.0.tar.gz::
+`git archive --format=tar.gz --prefix=git-1.4.0/ v1.4.0 >git-1.4.0.tar.gz`::
+
+       Same as above, but using the builtin tar.gz handling.
+
+`git archive --prefix=git-1.4.0/ -o git-1.4.0.tar.gz v1.4.0`::
+
+       Same as above, but the format is inferred from the output file.
+
+`git archive --format=tar --prefix=git-1.4.0/ v1.4.0{caret}\{tree\} | gzip >git-1.4.0.tar.gz`::
 
        Create a compressed tarball for v1.4.0 release, but without a
        global extended pax header.
 
-git archive --format=zip --prefix=git-docs/ HEAD:Documentation/ > git-1.4.0-docs.zip::
+`git archive --format=zip --prefix=git-docs/ HEAD:Documentation/ > git-1.4.0-docs.zip`::
 
        Put everything in the current head's Documentation/ directory
        into 'git-1.4.0-docs.zip', with the prefix 'git-docs/'.
 
-git archive -o latest.zip HEAD::
+`git archive -o latest.zip HEAD`::
 
        Create a Zip archive that contains the contents of the latest
        commit on the current branch. Note that the output format is
        inferred by the extension of the output file.
 
+`git config tar.tar.xz.command "xz -c"`::
+
+       Configure a "tar.xz" format for making LZMA-compressed tarfiles.
+       You can use it specifying `--format=tar.xz`, or by creating an
+       output file like `-o foo.tar.xz`.
+
 
 SEE ALSO
 --------
index 7b7bafba0c514755f9eec423b5aed19db640c091..e4f46bc18dba1e55da83e4af76b8a7f30a7f40be 100644 (file)
@@ -8,6 +8,7 @@ git-bisect - Find by binary search the change that introduced a bug
 
 SYNOPSIS
 --------
+[verse]
 'git bisect' <subcommand> <options>
 
 DESCRIPTION
@@ -16,7 +17,7 @@ The command takes various subcommands, and different options depending
 on the subcommand:
 
  git bisect help
- git bisect start [<bad> [<good>...]] [--] [<paths>...]
+ git bisect start [--no-checkout] [<bad> [<good>...]] [--] [<paths>...]
  git bisect bad [<rev>]
  git bisect good [<rev>...]
  git bisect skip [(<rev>|<range>)...]
@@ -262,6 +263,19 @@ rewind the tree to the pristine state.  Finally the script should exit
 with the status of the real test to let the "git bisect run" command loop
 determine the eventual outcome of the bisect session.
 
+OPTIONS
+-------
+--no-checkout::
++
+Do not checkout the new working tree at each iteration of the bisection
+process. Instead just update a special reference named 'BISECT_HEAD' to make
+it point to the commit that should be tested.
++
+This option may be useful when the test you would perform in each step
+does not require a checked out tree.
++
+If the repository is bare, `--no-checkout` is assumed.
+
 EXAMPLES
 --------
 
@@ -342,6 +356,25 @@ $ git bisect run sh -c "make || exit 125; ~/check_test_case.sh"
 This shows that you can do without a run script if you write the test
 on a single line.
 
+* Locate a good region of the object graph in a damaged repository
++
+------------
+$ git bisect start HEAD <known-good-commit> [ <boundary-commit> ... ] --no-checkout
+$ git bisect run sh -c '
+       GOOD=$(git for-each-ref "--format=%(objectname)" refs/bisect/good-*) &&
+       git rev-list --objects BISECT_HEAD --not $GOOD >tmp.$$ &&
+       git pack-objects --stdout >/dev/null <tmp.$$
+       rc=$?
+       rm -f tmp.$$
+       test $rc = 0'
+
+------------
++
+In this case, when 'git bisect run' finishes, bisect/bad will refer to a commit that
+has at least one parent whose reachable graph is fully traversable in the sense
+required by 'git pack objects'.
+
+
 SEE ALSO
 --------
 link:git-bisect-lk2009.html[Fighting regressions with git bisect],
index c50f1898272f6b2a9a1c1cd236d3b27621d4fdb9..507b8d0ab2a2d524a607c5b91e6d17f6d2f36392 100644 (file)
@@ -113,7 +113,8 @@ OPTIONS
 
 --abbrev=<length>::
        Alter the sha1's minimum display length in the output listing.
-       The default value is 7.
+       The default value is 7 and can be overridden by the `core.abbrev`
+       config option.
 
 --no-abbrev::
        Display the full sha1s in the output listing rather than abbreviating them.
index 30eca6cee62ad7b9ccfc9d71209a17287c65d48c..1f7312a1895ae66901e5472132406d01303da1f8 100644 (file)
@@ -9,8 +9,8 @@ git-check-attr - Display gitattributes information
 SYNOPSIS
 --------
 [verse]
-'git check-attr' attr... [--] pathname...
-'git check-attr' --stdin [-z] attr... < <list-of-paths>
+'git check-attr' [-a | --all | attr...] [--] pathname...
+'git check-attr' --stdin [-z] [-a | --all | attr...] < <list-of-paths>
 
 DESCRIPTION
 -----------
@@ -19,6 +19,11 @@ For every pathname, this command will list if each attribute is 'unspecified',
 
 OPTIONS
 -------
+-a, --all::
+       List all attributes that are associated with the specified
+       paths.  If this option is used, then 'unspecified' attributes
+       will not be included in the output.
+
 --stdin::
        Read file names from stdin instead of from the command-line.
 
@@ -28,8 +33,11 @@ OPTIONS
 
 \--::
        Interpret all preceding arguments as attributes and all following
-       arguments as path names. If not supplied, only the first argument will
-       be treated as an attribute.
+       arguments as path names.
+
+If none of `--stdin`, `--all`, or `--` is used, the first argument
+will be treated as an attribute and the rest of the arguments as
+pathnames.
 
 OUTPUT
 ------
@@ -69,6 +77,13 @@ org/example/MyClass.java: diff: java
 org/example/MyClass.java: myAttr: set
 ---------------
 
+* Listing all attributes for a file:
+---------------
+$ git check-attr --all -- org/example/MyClass.java
+org/example/MyClass.java: diff: java
+org/example/MyClass.java: myAttr: set
+---------------
+
 * Listing an attribute for multiple files:
 ---------------
 $ git check-attr myAttr -- org/example/MyClass.java org/example/NoMyAttr.java
index 205d83dd0b281d59e0f53284d29846c8eb58a246..c9fdf84a08847bd8cb8c9ce1e2f50241975e267c 100644 (file)
@@ -18,9 +18,12 @@ Checks if a given 'refname' is acceptable, and exits with a non-zero
 status if it is not.
 
 A reference is used in git to specify branches and tags.  A
-branch head is stored under the `$GIT_DIR/refs/heads` directory, and
-a tag is stored under the `$GIT_DIR/refs/tags` directory (or, if refs
-are packed by `git gc`, as entries in the `$GIT_DIR/packed-refs` file).
+branch head is stored in the `refs/heads` hierarchy, while
+a tag is stored in the `refs/tags` hierarchy of the ref namespace
+(typically in `$GIT_DIR/refs/heads` and `$GIT_DIR/refs/tags`
+directories or, as entries in file `$GIT_DIR/packed-refs`
+if refs are packed by `git gc`).
+
 git imposes the following rules on how references are named:
 
 . They can include slash `/` for hierarchical (directory)
index 9d8fe0d26145ae45f566d3e8d4253c396630b41f..7cfa3d92ac8dc7a90068311c0047c667f98515a8 100644 (file)
@@ -7,6 +7,7 @@ git-cherry-pick - Apply the changes introduced by some existing commits
 
 SYNOPSIS
 --------
+[verse]
 'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] <commit>...
 
 DESCRIPTION
@@ -111,31 +112,31 @@ effect to your index in a row.
 
 EXAMPLES
 --------
-git cherry-pick master::
+`git cherry-pick master`::
 
        Apply the change introduced by the commit at the tip of the
        master branch and create a new commit with this change.
 
-git cherry-pick ..master::
-git cherry-pick ^HEAD master::
+`git cherry-pick ..master`::
+`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{tilde}4 master{tilde}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
        these changes.
 
-git cherry-pick -n master~1 next::
+`git cherry-pick -n master~1 next`::
 
        Apply to the working tree and the index the changes introduced
        by the second last commit pointed to by master and by the last
        commit pointed to by next, but do not create any commit with
        these changes.
 
-git cherry-pick --ff ..next::
+`git cherry-pick --ff ..next`::
 
        If history is linear and HEAD is an ancestor of next, update
        the working tree and advance the HEAD pointer to match next.
@@ -143,7 +144,7 @@ git cherry-pick --ff ..next::
        are in next but not HEAD to the current branch, creating a new
        commit for each new change.
 
-git rev-list --reverse master \-- README | git cherry-pick -n --stdin::
+`git rev-list --reverse master \-- README | git cherry-pick -n --stdin`::
 
        Apply the changes introduced by all commits on the master
        branch that touched README to the working tree and index,
index 79448c505bd66f370ab0d79fd9f6a31af84752be..f6c19c734d0fad09e7f7203a69959546f4b6a052 100644 (file)
@@ -7,6 +7,7 @@ git-cherry - Find commits not merged upstream
 
 SYNOPSIS
 --------
+[verse]
 'git cherry' [-v] [<upstream> [<head> [<limit>]]]
 
 DESCRIPTION
index 6e5c8126f595b95f70df2b02c0779cb06527bfa7..c7a11c36c10039f9b06241c28731a8cc208c53c7 100644 (file)
@@ -7,6 +7,7 @@ git-citool - Graphical alternative to git-commit
 
 SYNOPSIS
 --------
+[verse]
 'git citool'
 
 DESCRIPTION
index 974e04ef1abaf4a3018b83b0323a46cbaa5339d4..79fb9841441d02cd635dcdd2aecef42d38c6b59d 100644 (file)
@@ -47,12 +47,14 @@ OPTIONS
 
 -e <pattern>::
 --exclude=<pattern>::
-       Specify special exceptions to not be cleaned.  Each <pattern> is
-       the same form as in $GIT_DIR/info/excludes and this option can be
-       given multiple times.
+       In addition to those found in .gitignore (per directory) and
+       $GIT_DIR/info/exclude, also consider these patterns to be in the
+       set of the ignore rules in effect.
 
 -x::
-       Don't use the ignore rules.  This allows removing all untracked
+       Don't use the standard ignore rules read from .gitignore (per
+       directory) and $GIT_DIR/info/exclude, but do still use the ignore
+       rules given with `-e` options.  This allows removing all untracked
        files, including build products.  This can be used (possibly in
        conjunction with 'git reset') to create a pristine
        working directory to test a clean build.
index b093e45497248076c335391f3e95b6384520ef6b..4b8b26b75e63cc56e679d2e2c6d8fd1240010419 100644 (file)
@@ -159,6 +159,17 @@ objects from the source repository into a pack in the cloned repository.
        Specify the directory from which templates will be used;
        (See the "TEMPLATE DIRECTORY" section of linkgit:git-init[1].)
 
+--config <key>=<value>::
+-c <key>=<value>::
+       Set a configuration variable in the newly-created repository;
+       this takes effect immediately after the repository is
+       initialized, but before the remote history is fetched or any
+       files checked out.  The key is in the same format as expected by
+       linkgit:git-config[1] (e.g., `core.eol=true`). If multiple
+       values are given for the same key, each value will be written to
+       the config file. This makes it safe, for example, to add
+       additional fetch refspecs to the origin remote.
+
 --depth <depth>::
        Create a 'shallow' clone with a history truncated to the
        specified number of revisions.  A shallow repository has a
index f524d76019960512ba65a0110a542fedf9a54ebb..0fdb82ee8697a53beab6defd4db9eca8840b1dd0 100644 (file)
@@ -8,6 +8,7 @@ git-commit-tree - Create a new commit object
 
 SYNOPSIS
 --------
+[verse]
 'git commit-tree' <tree> [(-p <parent commit>)...] < changelog
 
 DESCRIPTION
index 7951cb7b005bf472c449a563d073036ceb8b921a..5cc84a139133dca2fdcb594007c8b0d6464d5ca8 100644 (file)
@@ -284,7 +284,7 @@ When recording your own work, the contents of modified files in
 your working tree are temporarily stored to a staging area
 called the "index" with 'git add'.  A file can be
 reverted back, only in the index but not in the working tree,
-to that of the last commit with `git reset HEAD -- <file>`,
+to that of the last commit with `git reset HEAD \-- <file>`,
 which effectively reverts 'git add' and prevents the changes to
 this file from participating in the next commit.  After building
 the state to be committed incrementally with these commands,
index a73933a931161ba81d6bf8dfaa9f2fe4287e0331..23c80cea6465d23476935abcfabba8e1deb915ee 100644 (file)
@@ -7,6 +7,7 @@ git-count-objects - Count unpacked number of objects and their disk consumption
 
 SYNOPSIS
 --------
+[verse]
 'git count-objects' [-v]
 
 DESCRIPTION
index ad93a3e84e732291bbd1e4de196ddb8ce23eea32..7f79cec3f8e9f90f61e461521eed2ca23fba69d1 100644 (file)
@@ -8,6 +8,7 @@ git-cvsexportcommit - Export a single commit to a CVS checkout
 
 SYNOPSIS
 --------
+[verse]
 'git cvsexportcommit' [-h] [-u] [-v] [-c] [-P] [-p] [-a] [-d cvsroot]
        [-w cvsworkdir] [-W] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID
 
index ebd13be72e58cf0d0a66ddd0105117747ac900be..69a1e4af9ec007a6cc59cce07298a6a825369ef1 100644 (file)
@@ -93,14 +93,14 @@ OPTIONS
        Listen on an alternative port.  Incompatible with '--inetd' option.
 
 --init-timeout=<n>::
-       Timeout between the moment the connection is established and the
-       client request is received (typically a rather low value, since
+       Timeout (in seconds) between the moment the connection is established
+       and the client request is received (typically a rather low value, since
        that should be basically immediate).
 
 --timeout=<n>::
-       Timeout for specific client sub-requests. This includes the time
-       it takes for the server to process the sub-request and the time spent
-       waiting for the next client's request.
+       Timeout (in seconds) for specific client sub-requests. This includes
+       the time it takes for the server to process the sub-request and the
+       time spent waiting for the next client's request.
 
 --max-connections=<n>::
        Maximum number of concurrent clients, defaults to 32.  Set it to
index 8d481948bd9b29393c8884abfef9d0a76c922c2d..906774f0f7e0f907740d1382d48f7859219478e7 100644 (file)
@@ -8,6 +8,7 @@ git-diff-files - Compares files in the working tree and the index
 
 SYNOPSIS
 --------
+[verse]
 'git diff-files' [-q] [-0|-1|-2|-3|-c|--cc] [<common diff options>] [<path>...]
 
 DESCRIPTION
index 2ea22abca2bc72abb0cedfe6f47616c52a60d3f4..c0b7c581add35e24128a6b7e9aa79a50f914aad5 100644 (file)
@@ -8,6 +8,7 @@ git-diff-index - Compares content and mode of blobs between the index and reposi
 
 SYNOPSIS
 --------
+[verse]
 'git diff-index' [-m] [--cached] [<common diff options>] <tree-ish> [<path>...]
 
 DESCRIPTION
index 590f410abf0b902160eca0e92888191a2e2a0b89..a03515f1eccddded2efbd46ca522f3bac8896d90 100644 (file)
@@ -7,6 +7,7 @@ git-difftool - Show changes using common diff tools
 
 SYNOPSIS
 --------
+[verse]
 'git difftool' [<options>] [<commit> [<commit>]] [--] [<path>...]
 
 DESCRIPTION
index 781bd6edc3cce9d1488bf793231d08119e142948..f37eada63a449995861f41e5020c04f26d40c6f2 100644 (file)
@@ -8,6 +8,7 @@ git-fast-export - Git data exporter
 
 SYNOPSIS
 --------
+[verse]
 'git fast-export [options]' | 'git fast-import'
 
 DESCRIPTION
@@ -82,6 +83,10 @@ marks the same across runs.
        allow that.  So fake a tagger to be able to fast-import the
        output.
 
+--use-done-feature::
+       Start the stream with a 'feature done' stanza, and terminate
+       it with a 'done' command.
+
 --no-data::
        Skip output of blob objects and instead refer to blobs via
        their original SHA-1 hash.  This is useful when rewriting the
index 249249aac7b2b9def824794b2bf71e2c7fe497ce..ec6ef3119792a9e66a3a46bf6f0754458ea6a061 100644 (file)
@@ -8,6 +8,7 @@ git-fast-import - Backend for fast Git data importers
 
 SYNOPSIS
 --------
+[verse]
 frontend | 'git fast-import' [options]
 
 DESCRIPTION
@@ -101,6 +102,12 @@ OPTIONS
        when the `cat-blob` command is encountered in the stream.
        The default behaviour is to write to `stdout`.
 
+--done::
+       Require a `done` command at the end of the stream.
+       This option might be useful for detecting errors that
+       cause the frontend to terminate before it has started to
+       write a stream.
+
 --export-pack-edges=<file>::
        After creating a packfile, print a line of data to
        <file> listing the filename of the packfile and the last
@@ -330,6 +337,11 @@ and control the current import process.  More detailed discussion
        standard output.  This command is optional and is not needed
        to perform an import.
 
+`done`::
+       Marks the end of the stream. This command is optional
+       unless the `done` feature was requested using the
+       `--done` command line option or `feature done` command.
+
 `cat-blob`::
        Causes fast-import to print a blob in 'cat-file --batch'
        format to the file descriptor set with `--cat-blob-fd` or
@@ -413,8 +425,8 @@ Here `<name>` is the person's display name (for example
 (``cm@example.com'').  `LT` and `GT` are the literal less-than (\x3c)
 and greater-than (\x3e) symbols.  These are required to delimit
 the email address from the other fields in the line.  Note that
-`<name>` is free-form and may contain any sequence of bytes, except
-`LT` and `LF`.  It is typically UTF-8 encoded.
+`<name>` and `<email>` are free-form and may contain any sequence
+of bytes, except `LT`, `GT` and `LF`.  `<name>` is typically UTF-8 encoded.
 
 The time of the change is specified by `<when>` using the date format
 that was selected by the \--date-format=<fmt> command line option.
@@ -648,9 +660,14 @@ paths for a commit are encouraged to do so.
 
 `notemodify`
 ^^^^^^^^^^^^
-Included in a `commit` command to add a new note (annotating a given
-commit) or change the content of an existing note.  This command has
-two different means of specifying the content of the note.
+Included in a `commit` `<notes_ref>` command to add a new note
+annotating a `<committish>` or change this annotation contents.
+Internally it is similar to filemodify 100644 on `<committish>`
+path (maybe split into subdirectories). It's not advised to
+use any other commands to write to the `<notes_ref>` tree except
+`filedeleteall` to delete all existing notes in this tree.
+This command has two different means of specifying the content
+of the note.
 
 External data format::
        The data content for the note was already supplied by a prior
@@ -995,10 +1012,14 @@ force::
        (see OPTIONS, above).
 
 import-marks::
+import-marks-if-exists::
        Like --import-marks except in two respects: first, only one
-       "feature import-marks" command is allowed per stream;
-       second, an --import-marks= command-line option overrides
-       any "feature import-marks" command in the stream.
+       "feature import-marks" or "feature import-marks-if-exists"
+       command is allowed per stream; second, an --import-marks=
+       or --import-marks-if-exists command-line option overrides
+       any of these "feature" commands in the stream; third,
+       "feature import-marks-if-exists" like a corresponding
+       command-line option silently skips a nonexistent file.
 
 cat-blob::
 ls::
@@ -1015,6 +1036,11 @@ notes::
        Versions of fast-import not supporting notes will exit
        with a message indicating so.
 
+done::
+       Error out if the stream ends without a 'done' command.
+       Without this feature, errors causing the frontend to end
+       abruptly at a convenient point in the stream can go
+       undetected.
 
 `option`
 ~~~~~~~~
@@ -1044,6 +1070,15 @@ not be passed as option:
 * cat-blob-fd
 * force
 
+`done`
+~~~~~~
+If the `done` feature is not in use, treated as if EOF was read.
+This can be used to tell fast-import to finish early.
+
+If the `--done` command line option or `feature done` command is
+in use, the `done` command is mandatory and marks the end of the
+stream.
+
 Crash Reports
 -------------
 If fast-import is supplied invalid input it will terminate with a
index 48d4bf6d68fd51d9374f896ac839351936580e31..ed1bdaacd10788ab35a2ae1de870d5972e30d432 100644 (file)
@@ -8,6 +8,7 @@ git-fetch-pack - Receive missing objects from another repository
 
 SYNOPSIS
 --------
+[verse]
 'git fetch-pack' [--all] [--quiet|-q] [--keep|-k] [--thin] [--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]
 
 DESCRIPTION
index 60ac8d26eb3203abd29adc4c0a007f80bb616caa..b41d7c1de1e501e267bc9dfb0c13819b04e3b8aa 100644 (file)
@@ -8,12 +8,10 @@ git-fetch - Download objects and refs from another repository
 
 SYNOPSIS
 --------
+[verse]
 'git fetch' [<options>] [<repository> [<refspec>...]]
-
 'git fetch' [<options>] <group>
-
 'git fetch' --multiple [<options>] [(<repository> | <group>)...]
-
 'git fetch' --all [<options>]
 
 
index 9dc1f2a94708d40daf428a427dd2ea4256cc172a..0f2f1173834bdee77bd1d8d77e070af07a7b3580 100644 (file)
@@ -32,8 +32,9 @@ changes, which would normally have no effect.  Nevertheless, this may be
 useful in the future for compensating for some git bugs or such,
 therefore such a usage is permitted.
 
-*NOTE*: This command honors `.git/info/grafts`. If you have any grafts
-defined, running this command will make them permanent.
+*NOTE*: This command honors `.git/info/grafts` and `.git/refs/replace/`.
+If you have any grafts or replacement refs defined, running this command
+will make them permanent.
 
 *WARNING*! The rewritten history will have different object names for all
 the objects and will not converge with the original branch.  You will not
index d13c9b23f7bce6379f638e5bf86b30c95abef30e..6ea9be775c09111c14668e0de390a04a301f3235 100644 (file)
@@ -166,15 +166,22 @@ will want to ensure that threading is disabled for `git send-email`.
 --to=<email>::
        Add a `To:` header to the email headers. This is in addition
        to any configured headers, and may be used multiple times.
+       The negated form `--no-to` discards all `To:` headers added so
+       far (from config or command line).
 
 --cc=<email>::
        Add a `Cc:` header to the email headers. This is in addition
        to any configured headers, and may be used multiple times.
+       The negated form `--no-cc` discards all `Cc:` headers added so
+       far (from config or command line).
 
 --add-header=<header>::
        Add an arbitrary header to the email headers.  This is in addition
        to any configured headers, and may be used multiple times.
-       For example, `--add-header="Organization: git-foo"`
+       For example, `--add-header="Organization: git-foo"`.
+       The negated form `--no-add-header` discards *all* (`To:`,
+       `Cc:`, and custom) headers added so far from config or command
+       line.
 
 --cover-letter::
        In addition to the patches, generate a cover letter file
index 90ebb8a59401ddd16622533cc998bbb218137e34..eec4bdb600d8ca03a6fd7138e945dceb6dc43ec7 100644 (file)
@@ -8,6 +8,7 @@ git-fsck-objects - Verifies the connectivity and validity of the objects in the
 
 SYNOPSIS
 --------
+[verse]
 'git fsck-objects' ...
 
 DESCRIPTION
index 4966cb57846412b097bbc8ba15d09005f4019340..815afcb9228aada96a143a4e5dcdec0e0ee807e5 100644 (file)
@@ -8,6 +8,7 @@ git-gc - Cleanup unnecessary files and optimize the local repository
 
 SYNOPSIS
 --------
+[verse]
 'git gc' [--aggressive] [--auto] [--quiet] [--prune=<date> | --no-prune]
 
 DESCRIPTION
index 8035736c9682d5615c98dc83eaff6cf389d6c463..1e2a20dd265c2c41aef0ea66ca9831321d951d5c 100644 (file)
@@ -8,6 +8,7 @@ git-get-tar-commit-id - Extract commit ID from an archive created using git-arch
 
 SYNOPSIS
 --------
+[verse]
 'git get-tar-commit-id' < <tarfile>
 
 
index e150c77cffb5d082822bd65c7bfbeb1f431fc42f..e44a4988b7a7ed7100da6e2ba098d7fda17a2f83 100644 (file)
@@ -148,14 +148,12 @@ OPTIONS
        gives the default to color output.
        Same as `--color=never`.
 
--[ABC] <context>::
-       Show `context` trailing (`A` -- after), or leading (`B`
-       -- before), or both (`C` -- context) lines, and place a
-       line containing `--` between contiguous groups of
-       matches.
+--break::
+       Print an empty line between matches from different files.
 
--<num>::
-       A shortcut for specifying `-C<num>`.
+--heading::
+       Show the filename above the matches in that file instead of
+       at the start of each shown line.
 
 -p::
 --show-function::
@@ -165,6 +163,29 @@ OPTIONS
        patch hunk headers (see 'Defining a custom hunk-header' in
        linkgit:gitattributes[5]).
 
+-<num>::
+-C <num>::
+--context <num>::
+       Show <num> leading and trailing lines, and place a line
+       containing `--` between contiguous groups of matches.
+
+-A <num>::
+--after-context <num>::
+       Show <num> trailing lines, and place a line containing
+       `--` between contiguous groups of matches.
+
+-B <num>::
+--before-context <num>::
+       Show <num> leading lines, and place a line containing
+       `--` between contiguous groups of matches.
+
+-W::
+--function-context::
+       Show the surrounding text from the previous line containing a
+       function name up to the one before the next function name,
+       effectively showing the whole function in which the match was
+       found.
+
 -f <file>::
        Read patterns from <file>, one per line.
 
@@ -208,15 +229,15 @@ OPTIONS
 Examples
 --------
 
-git grep {apostrophe}time_t{apostrophe} \-- {apostrophe}*.[ch]{apostrophe}::
+`git grep {apostrophe}time_t{apostrophe} \-- {apostrophe}*.[ch]{apostrophe}`::
        Looks for `time_t` in all tracked .c and .h files in the working
        directory and its subdirectories.
 
-git grep -e {apostrophe}#define{apostrophe} --and \( -e MAX_PATH -e PATH_MAX \)::
+`git grep -e {apostrophe}#define{apostrophe} --and \( -e MAX_PATH -e PATH_MAX \)`::
        Looks for a line that has `#define` and either `MAX_PATH` or
        `PATH_MAX`.
 
-git grep --all-match -e NODE -e Unexpected::
+`git grep --all-match -e NODE -e Unexpected`::
        Looks for a line that has `NODE` or `Unexpected` in
        files that have lines that match both.
 
index 32a833e0ae522f034b9b7c9115c486aa68658b2c..0041994443c42033ee7c378ffad8983b0163489e 100644 (file)
@@ -7,6 +7,7 @@ git-gui - A portable graphical interface to Git
 
 SYNOPSIS
 --------
+[verse]
 'git gui' [<command>] [arguments]
 
 DESCRIPTION
@@ -49,7 +50,7 @@ version::
 
 Examples
 --------
-git gui blame Makefile::
+`git gui blame Makefile`::
 
        Show the contents of the file 'Makefile' in the current
        working directory, and provide annotations for both the
@@ -58,41 +59,41 @@ git gui blame Makefile::
        uncommitted changes (if any) are explicitly attributed to
        'Not Yet Committed'.
 
-git gui blame v0.99.8 Makefile::
+`git gui blame v0.99.8 Makefile`::
 
        Show the contents of 'Makefile' in revision 'v0.99.8'
        and provide annotations for each line.  Unlike the above
        example the file is read from the object database and not
        the working directory.
 
-git gui blame --line=100 Makefile::
+`git gui blame --line=100 Makefile`::
 
        Loads annotations as described above and automatically
        scrolls the view to center on line '100'.
 
-git gui citool::
+`git gui citool`::
 
        Make one commit and return to the shell when it is complete.
        This command returns a non-zero exit code if the window was
        closed in any way other than by making a commit.
 
-git gui citool --amend::
+`git gui citool --amend`::
 
        Automatically enter the 'Amend Last Commit' mode of
        the interface.
 
-git gui citool --nocommit::
+`git gui citool --nocommit`::
 
        Behave as normal citool, but instead of making a commit
        simply terminate with a zero exit code. It still checks
        that the index does not contain any unmerged entries, so
        you can use it as a GUI version of linkgit:git-mergetool[1]
 
-git citool::
+`git citool`::
 
        Same as `git gui citool` (above).
 
-git gui browser maint::
+`git gui browser maint`::
 
        Show a browser for the tree of the 'maint' branch.  Files
        selected in the browser can be viewed with the internal
index 42aa2b0c012aaa559162517ee8913ba8bbe72212..9e0b3f68116321dfb6a01f0095c6cdfa5a1a4f20 100644 (file)
@@ -7,6 +7,7 @@ git-help - display help information about git
 
 SYNOPSIS
 --------
+[verse]
 'git help' [-a|--all|-i|--info|-m|--man|-w|--web] [COMMAND]
 
 DESCRIPTION
index 277d9e141bf81bddb4ba661e43763b7774a1417d..f4e0741c115905f61a0e92c5255658f4b8e25711 100644 (file)
@@ -119,6 +119,14 @@ ScriptAliasMatch \
 
 ScriptAlias /git/ /var/www/cgi-bin/gitweb.cgi/
 ----------------------------------------------------------------
++
+To serve multiple repositories from different linkgit:gitnamespaces[7] in a
+single repository:
++
+----------------------------------------------------------------
+SetEnvIf Request_URI "^/git/([^/]*)" GIT_NAMESPACE=$1
+ScriptAliasMatch ^/git/[^/]*(.*) /usr/libexec/git-core/git-http-backend/storage.git$1
+----------------------------------------------------------------
 
 Accelerated static Apache 2.x::
        Similar to the above, but Apache can be used to return static
index fefa75219822331f7c7bc426a1224978a5da6795..070cd1e6ed93b064bdd1fdfc1767a3f9a999d321 100644 (file)
@@ -8,12 +8,16 @@ git-http-fetch - Download from a remote git repository via HTTP
 
 SYNOPSIS
 --------
+[verse]
 'git http-fetch' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] [--stdin] <commit> <url>
 
 DESCRIPTION
 -----------
 Downloads a remote git repository via HTTP.
 
+*NOTE*: use of this command without -a is deprecated.  The -a
+behaviour will become the default in a future release.
+
 OPTIONS
 -------
 commit-id::
index 82ae34b9b80d8f3fd8605d5c06cc37c7a203f758..2e67362bd4b60607e7ccfeafbfebdc4cf68058e7 100644 (file)
@@ -8,6 +8,7 @@ git-http-push - Push objects over HTTP/DAV to another repository
 
 SYNOPSIS
 --------
+[verse]
 'git http-push' [--all] [--dry-run] [--force] [--verbose] <url> <ref> [<ref>...]
 
 DESCRIPTION
index 4e09708cc96fbf88b468141c0234095ee35fee3c..875d2831a541170402849e2c8598fabafa42a0df 100644 (file)
@@ -8,6 +8,7 @@ git-imap-send - Send a collection of patches from stdin to an IMAP folder
 
 SYNOPSIS
 --------
+[verse]
 'git imap-send'
 
 
index 9f97f5a91584a7469a3b0d2f8705febbe5c5ab58..a21e34678933ee5a6b222afc40293fec4166b110 100644 (file)
@@ -8,6 +8,7 @@ git-init-db - Creates an empty git repository
 
 SYNOPSIS
 --------
+[verse]
 'git init-db' [-q | --quiet] [--bare] [--template=<template_directory>] [--separate-git-dir <git dir>] [--shared[=<permissions>]]
 
 
index f2777a7786e6bf9c0cb907c77134b06321523654..9ac2bbaa565455b25146a6fe0e24d0deebd90c4c 100644 (file)
@@ -8,6 +8,7 @@ git-init - Create an empty git repository or reinitialize an existing one
 
 SYNOPSIS
 --------
+[verse]
 'git init' [-q | --quiet] [--bare] [--template=<template_directory>]
          [--separate-git-dir <git dir>]
          [--shared[=<permissions>]] [directory]
index 08f85ba046598070432e9d9cd45052ae20484402..ea95c90460b976ee187833c248366af579065f83 100644 (file)
@@ -51,8 +51,8 @@ OPTIONS
 
 start::
 --start::
-       Start the httpd instance and exit.  This does not generate
-       any of the configuration files for spawning a new instance.
+       Start the httpd instance and exit.  Regenerate configuration files
+       as necessary for spawning a new instance.
 
 stop::
 --stop::
@@ -62,8 +62,8 @@ stop::
 
 restart::
 --restart::
-       Restart the httpd instance and exit.  This does not generate
-       any of the configuration files for spawning a new instance.
+       Restart the httpd instance and exit.  Regenerate configuration files
+       as necessary for spawning a new instance.
 
 CONFIGURATION
 -------------
index de5c0d37a5c1c8c948aa01e276c3e198eb617be6..249fc878ec2058f9fcfc6655ecaeb299ba5622a7 100644 (file)
@@ -8,6 +8,7 @@ git-log - Show commit logs
 
 SYNOPSIS
 --------
+[verse]
 'git log' [<options>] [<since>..<until>] [[\--] <path>...]
 
 DESCRIPTION
@@ -68,10 +69,13 @@ produced by --stat etc.
        its size is not included.
 
 [\--] <path>...::
-       Show only commits that affect any of the specified paths. To
-       prevent confusion with options and branch names, paths may need
-       to be prefixed with "\-- " to separate them from options or
-       refnames.
+       Show only commits that are enough to explain how the files
+       that match the specified paths came to be.  See "History
+       Simplification" below for details and other simplification
+       modes.
++
+To prevent confusion with options and branch names, paths may need to
+be prefixed with "\-- " to separate them from options or refnames.
 
 include::rev-list-options.txt[]
 
@@ -87,45 +91,45 @@ include::diff-generate-patch.txt[]
 
 Examples
 --------
-git log --no-merges::
+`git log --no-merges`::
 
        Show the whole commit history, but skip any merges
 
-git log v2.6.12.. include/scsi drivers/scsi::
+`git log v2.6.12.. include/scsi drivers/scsi`::
 
        Show all commits since version 'v2.6.12' that changed any file
        in the include/scsi or drivers/scsi subdirectories
 
-git log --since="2 weeks ago" \-- gitk::
+`git log --since="2 weeks ago" \-- gitk`::
 
        Show the changes during the last two weeks to the file 'gitk'.
        The "--" is necessary to avoid confusion with the *branch* named
        'gitk'
 
-git log --name-status release..test::
+`git log --name-status release..test`::
 
        Show the commits that are in the "test" branch but not yet
        in the "release" branch, along with the list of paths
        each commit modifies.
 
-git log --follow builtin-rev-list.c::
+`git log --follow builtin-rev-list.c`::
 
        Shows the commits that changed builtin-rev-list.c, including
        those commits that occurred before the file was given its
        present name.
 
-git log --branches --not --remotes=origin::
+`git log --branches --not --remotes=origin`::
 
        Shows all commits that are in any of local branches but not in
        any of remote-tracking branches for 'origin' (what you have that
        origin doesn't).
 
-git log master --not --remotes=*/master::
+`git log master --not --remotes=*/master`::
 
        Shows all commits that are in local master but not in any remote
        repository master branches.
 
-git log -p -m --first-parent::
+`git log -p -m --first-parent`::
 
        Shows the history including change diffs, but only from the
        "main branch" perspective, skipping commits that come from merged
index adf7e1c0557b3dab145403d73687f5e125ae73f4..c406a1100159471cf904629b5d4afc0469c382c0 100644 (file)
@@ -7,6 +7,7 @@ git-lost-found - Recover lost refs that luckily have not yet been pruned
 
 SYNOPSIS
 --------
+[verse]
 'git lost-found'
 
 DESCRIPTION
index ed45662cc9bb0ef93c6748d02e3791ac915830f9..51dc3257486b07edaec71fe98fed6533e17e1a77 100644 (file)
@@ -8,6 +8,7 @@ git-mailinfo - Extracts patch and authorship from a single e-mail message
 
 SYNOPSIS
 --------
+[verse]
 'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n] [--scissors] <msg> <patch>
 
 
index 9b2049d674a5d0b71a9454b2ce4afbfa59e2db34..4d1b871d96177ca36e7fadd115685316292dc727 100644 (file)
@@ -7,6 +7,7 @@ git-mailsplit - Simple UNIX mbox splitter program
 
 SYNOPSIS
 --------
+[verse]
 'git mailsplit' [-b] [-f<nn>] [-d<prec>] [--keep-cr] -o<directory> [--] [(<mbox>|<Maildir>)...]
 
 DESCRIPTION
index 635c66956e90452c58938f2d48e84454983f22e6..d7db2a3737fbdb032747bc6dd2740be1a56dfced 100644 (file)
@@ -76,12 +76,12 @@ OPTIONS
 EXAMPLES
 --------
 
-git merge-file README.my README README.upstream::
+`git merge-file README.my README README.upstream`::
 
        combines the changes of README.my and README.upstream since README,
        tries to merge them and writes the result into README.my.
 
-git merge-file -L a -L b -L c tmp/a123 tmp/b234 tmp/c345::
+`git merge-file -L a -L b -L c tmp/a123 tmp/b234 tmp/c345`::
 
        merges tmp/a123 and tmp/c345 with the base tmp/b234, but uses labels
        `a` and `c` instead of `tmp/a123` and `tmp/c345`.
index 6ce54673b0c239a04503ce9da231826c61470bd1..e0df1b33408df881057963896fb0d0d3cd5edd66 100644 (file)
@@ -8,6 +8,7 @@ git-merge-index - Run a merge for files needing merging
 
 SYNOPSIS
 --------
+[verse]
 'git merge-index' [-o] [-q] <merge-program> (-a | [--] <file>*)
 
 DESCRIPTION
index ee059def79f85e770b23b34bce1dfc293434aa8b..04e803d5d3362c97e92efc22bdec4fe526946c6f 100644 (file)
@@ -8,6 +8,7 @@ git-merge-one-file - The standard helper program to use with git-merge-index
 
 SYNOPSIS
 --------
+[verse]
 'git merge-one-file'
 
 DESCRIPTION
index 3bfa7b422026181f675a7398d41e1153fdfbd837..c5f84b649504f1708be9cd925023af3afd685337 100644 (file)
@@ -8,6 +8,7 @@ git-merge-tree - Show three-way merge without touching index
 
 SYNOPSIS
 --------
+[verse]
 'git merge-tree' <base-tree> <branch1> <branch2>
 
 DESCRIPTION
index 63ededec1d7099bb2156461895f32dcf008dedcc..f98a41b87c16007d6d9fa916c6a3bb31fb049216 100644 (file)
@@ -7,7 +7,8 @@ git-mergetool--lib - Common git merge tool shell scriptlets
 
 SYNOPSIS
 --------
-'TOOL_MODE=(diff|merge) . "$(git --exec-path)/git-mergetool--lib"'
+[verse]
+'TOOL_MODE=(diff|merge) . "$(git --exec-path)/git-mergetool{litdd}lib"'
 
 DESCRIPTION
 -----------
index 8c79ae8d2a610bf4f1dea1a840bfc52e85d90fd9..2a49de7cfe5188b531bc02f1115e253bfa10e0e0 100644 (file)
@@ -7,6 +7,7 @@ git-mergetool - Run merge conflict resolution tools to resolve merge conflicts
 
 SYNOPSIS
 --------
+[verse]
 'git mergetool' [--tool=<tool>] [-y|--no-prompt|--prompt] [<file>...]
 
 DESCRIPTION
@@ -16,9 +17,10 @@ Use `git mergetool` to run one of several merge utilities to resolve
 merge conflicts.  It is typically run after 'git merge'.
 
 If one or more <file> parameters are given, the merge tool program will
-be run to resolve differences on each file.  If no <file> names are
-specified, 'git mergetool' will run the merge tool program on every file
-with merge conflicts.
+be run to resolve differences on each file (skipping those without
+conflicts).  Specifying a directory will include all unresolved files in
+that path.  If no <file> names are specified, 'git mergetool' will run
+the merge tool program on every file with merge conflicts.
 
 OPTIONS
 -------
index 037ab1045d91b730e5cae9073226bd09ebe67b9a..65e167a5c580c694a66cbb391938359a38ddd3e0 100644 (file)
@@ -8,6 +8,7 @@ git-mktag - Creates a tag object
 
 SYNOPSIS
 --------
+[verse]
 'git mktag' < signature_file
 
 DESCRIPTION
index afe21be64dfa9ea2540f9c4fc24d97413643879f..5c6ebdfad93ea87f973194b2fe31e784033dc3c6 100644 (file)
@@ -8,6 +8,7 @@ git-mktree - Build a tree-object from ls-tree formatted text
 
 SYNOPSIS
 --------
+[verse]
 'git mktree' [-z] [--missing] [--batch]
 
 DESCRIPTION
index db0e030d69f92a1f52cd7c98de99504e850ab2db..b8db3739640491566dee6e381bae319b7e7be8c6 100644 (file)
@@ -8,6 +8,7 @@ git-mv - Move or rename a file, a directory, or a symlink
 
 SYNOPSIS
 --------
+[verse]
 'git mv' <options>... <args>...
 
 DESCRIPTION
index 6a187f2e2336575fb4974b8c492359913a8c7a9e..e8319eac6928300d1eb12070874e705b9be060b6 100644 (file)
@@ -142,8 +142,9 @@ OPTIONS
 
 -C <object>::
 --reuse-message=<object>::
-       Take the note message from the given blob object (for
-       example, another note).
+       Take the given blob object (for example, another note) as the
+       note message. (Use `git notes copy <object>` instead to
+       copy notes between objects.)
 
 -c <object>::
 --reedit-message=<object>::
@@ -285,6 +286,8 @@ $ blob=$(git hash-object -w a.out)
 $ git notes --ref=built add -C "$blob" HEAD
 ------------
 
+(You cannot simply use `git notes --ref=built add -F a.out HEAD`
+because that is not binary-safe.)
 Of course, it doesn't make much sense to display non-text-format notes
 with 'git log', so if you use such notes, you'll probably need to write
 some special-purpose tools to do something useful with them.
index db9f0f70554d997fab9d8441f78c10e523dda178..f2869da57282658e8f689905a686ba2f74bc9020 100644 (file)
@@ -8,6 +8,7 @@ git-pack-redundant - Find redundant pack files
 
 SYNOPSIS
 --------
+[verse]
 'git pack-redundant' [ --verbose ] [ --alt-odb ] < --all | .pack filename ... >
 
 DESCRIPTION
index 54b92534ceb7895dadf1bec22782af3d09f1ef78..a3c6677bfaaf628410eb85ce06f58d3c2b4df966 100644 (file)
@@ -7,6 +7,7 @@ git-pack-refs - Pack heads and tags for efficient repository access
 
 SYNOPSIS
 --------
+[verse]
 'git pack-refs' [--all] [--no-prune]
 
 DESCRIPTION
index 02217f6ba2007bb7bc55c577b77773dfcce93225..a45ea1ece81a1340de7748afa2390ca6f03ab549 100644 (file)
@@ -8,6 +8,7 @@ git-parse-remote - Routines to help parsing remote repository access parameters
 
 SYNOPSIS
 --------
+[verse]
 '. "$(git --exec-path)/git-parse-remote"'
 
 DESCRIPTION
index 50e26f43c1cd9fde593760d1557ac57b3695ae5c..90268f02e7381b6f1403e54103988bfde6522073 100644 (file)
@@ -7,6 +7,7 @@ git-patch-id - Compute unique ID for a patch
 
 SYNOPSIS
 --------
+[verse]
 'git patch-id' < <patch>
 
 DESCRIPTION
index a34d62f0daa3ada5c5287008b05d989eb52cc8f0..87ea3fb05418f858ed3aac3d34eceb371bc0e07f 100644 (file)
@@ -8,6 +8,7 @@ git-peek-remote - List the references in a remote repository
 
 SYNOPSIS
 --------
+[verse]
 'git peek-remote' [--upload-pack=<git-upload-pack>] [<host>:]<directory>
 
 DESCRIPTION
index 9e6202cdffd01fc685a78d1bd2c56128a8b8b73a..80dc022edea58b76aeea2f7df0876fd98bc6dc05 100644 (file)
@@ -8,6 +8,7 @@ git-prune-packed - Remove extra objects that are already in pack files
 
 SYNOPSIS
 --------
+[verse]
 'git prune-packed' [-n|--dry-run] [-q|--quiet]
 
 
index f616a739efdab16a58c33e9c2671e8afbaf13b98..80d01b05710e250a5f1d548fca3fba54d50e0f29 100644 (file)
@@ -8,6 +8,7 @@ git-prune - Prune all unreachable objects from the object database
 
 SYNOPSIS
 --------
+[verse]
 'git prune' [-n] [-v] [--expire <expire>] [--] [<head>...]
 
 DESCRIPTION
index 14609cbd4dc2f7f3c16f7b660e4aff76eca6065b..e1da46876682e9d95a7505e1bc116cbe07f2ec61 100644 (file)
@@ -8,6 +8,7 @@ git-pull - Fetch from and merge with another repository or a local branch
 
 SYNOPSIS
 --------
+[verse]
 'git pull' [options] [<repository> [<refspec>...]]
 
 
index 88acfcd4cc8e0eeafc809df0d72b37daded6f8a6..aede48877fb080bd12c346c74cf7453860d7de21 100644 (file)
@@ -162,6 +162,12 @@ useful if you write an alias or script around 'git push'.
        is specified. This flag forces progress status even if the
        standard error stream is not directed to a terminal.
 
+--recurse-submodules=check::
+       Check whether all submodule commits used by the revisions to be
+       pushed are available on a remote tracking branch. Otherwise the
+       push will be aborted and the command will exit with non-zero status.
+
+
 include::urls-remotes.txt[]
 
 OUTPUT
@@ -327,12 +333,12 @@ a case where you do mean to lose history.
 Examples
 --------
 
-git push::
+`git push`::
        Works like `git push <remote>`, where <remote> is the
        current branch's remote (or `origin`, if no remote is
        configured for the current branch).
 
-git push origin::
+`git push origin`::
        Without additional configuration, works like
        `git push origin :`.
 +
@@ -344,45 +350,45 @@ use `git config remote.origin.push HEAD`.  Any valid <refspec> (like
 the ones in the examples below) can be configured as the default for
 `git push origin`.
 
-git push origin :::
+`git push origin :`::
        Push "matching" branches to `origin`. See
        <refspec> in the <<OPTIONS,OPTIONS>> section above for a
        description of "matching" branches.
 
-git push origin master::
+`git push origin master`::
        Find a ref that matches `master` in the source repository
        (most likely, it would find `refs/heads/master`), and update
        the same ref (e.g. `refs/heads/master`) in `origin` repository
        with it.  If `master` did not exist remotely, it would be
        created.
 
-git push origin HEAD::
+`git push origin HEAD`::
        A handy way to push the current branch to the same name on the
        remote.
 
-git push origin master:satellite/master dev:satellite/dev::
+`git push origin master:satellite/master dev:satellite/dev`::
        Use the source ref that matches `master` (e.g. `refs/heads/master`)
        to update the ref that matches `satellite/master` (most probably
        `refs/remotes/satellite/master`) in the `origin` repository, then
        do the same for `dev` and `satellite/dev`.
 
-git push origin HEAD:master::
+`git push origin HEAD:master`::
        Push the current branch to the remote ref matching `master` in the
        `origin` repository. This form is convenient to push the current
        branch without thinking about its local name.
 
-git push origin master:refs/heads/experimental::
+`git push origin master:refs/heads/experimental`::
        Create the branch `experimental` in the `origin` repository
        by copying the current `master` branch.  This form is only
        needed to create a new branch or tag in the remote repository when
        the local name and the remote name are different; otherwise,
        the ref name on its own will work.
 
-git push origin :experimental::
+`git push origin :experimental`::
        Find a ref that matches `experimental` in the `origin` repository
        (e.g. `refs/heads/experimental`), and delete it.
 
-git push origin {plus}dev:master::
+`git push origin {plus}dev:master`::
        Update the origin repository's master branch with the dev branch,
        allowing non-fast-forward updates.  *This can leave unreferenced
        commits dangling in the origin repository.*  Consider the
index 46a96f2313e0cb48bff9d5de26f403b35d327d2c..5375549820bd6b9fecf3540b1e60ae50e74da0e6 100644 (file)
@@ -8,6 +8,7 @@ git-read-tree - Reads tree information into the index
 
 SYNOPSIS
 --------
+[verse]
 'git read-tree' [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>]
                [-u [--exclude-per-directory=<gitignore>] | -i]]
                [--index-output=<file>] [--no-sparse-checkout]
@@ -46,7 +47,7 @@ OPTIONS
 
 -i::
        Usually a merge requires the index file as well as the
-       files in the working tree are up to date with the
+       files in the working tree to be up to date with the
        current head commit, in order not to lose local
        changes.  This flag disables the check with the working
        tree and is meant to be used when creating a merge of
@@ -70,21 +71,21 @@ OPTIONS
 --aggressive::
        Usually a three-way merge by 'git read-tree' resolves
        the merge for really trivial cases and leaves other
-       cases unresolved in the index, so that Porcelains can
+       cases unresolved in the index, so that porcelains can
        implement different merge policies.  This flag makes the
-       command to resolve a few more cases internally:
+       command resolve a few more cases internally:
 +
 * when one side removes a path and the other side leaves the path
   unmodified.  The resolution is to remove that path.
 * when both sides remove a path.  The resolution is to remove that path.
-* when both sides adds a path identically.  The resolution
+* when both sides add a path identically.  The resolution
   is to add that path.
 
 --prefix=<prefix>/::
        Keep the current index contents, and read the contents
-       of named tree-ish under directory at `<prefix>`.  The
+       of the named tree-ish under the directory at `<prefix>`. The
        original index file cannot have anything at the path
-       `<prefix>` itself, and have nothing in `<prefix>/`
+       `<prefix>` itself, nor anything in the `<prefix>/`
        directory.  Note that the `<prefix>/` value must end
        with a slash.
 
@@ -378,45 +379,45 @@ have finished your work-in-progress), attempt the merge again.
 Sparse checkout
 ---------------
 
-"Sparse checkout" allows to sparsely populate working directory.
-It uses skip-worktree bit (see linkgit:git-update-index[1]) to tell
-Git whether a file on working directory is worth looking at.
+"Sparse checkout" allows populating the working directory sparsely.
+It uses the skip-worktree bit (see linkgit:git-update-index[1]) to tell
+Git whether a file in the working directory is worth looking at.
 
-"git read-tree" and other merge-based commands ("git merge", "git
-checkout"...) can help maintaining skip-worktree bitmap and working
+'git read-tree' and other merge-based commands ('git merge', 'git
+checkout'...) can help maintaining the skip-worktree bitmap and working
 directory update. `$GIT_DIR/info/sparse-checkout` is used to
-define the skip-worktree reference bitmap. When "git read-tree" needs
-to update working directory, it will reset skip-worktree bit in index
+define the skip-worktree reference bitmap. When 'git read-tree' needs
+to update the working directory, it resets the skip-worktree bit in the index
 based on this file, which uses the same syntax as .gitignore files.
-If an entry matches a pattern in this file, skip-worktree will be
-set on that entry. Otherwise, skip-worktree will be unset.
+If an entry matches a pattern in this file, skip-worktree will not be
+set on that entry. Otherwise, skip-worktree will be set.
 
 Then it compares the new skip-worktree value with the previous one. If
-skip-worktree turns from unset to set, it will add the corresponding
-file back. If it turns from set to unset, that file will be removed.
+skip-worktree turns from set to unset, it will add the corresponding
+file back. If it turns from unset to set, that file will be removed.
 
 While `$GIT_DIR/info/sparse-checkout` is usually used to specify what
-files are in. You can also specify what files are _not_ in, using
-negate patterns. For example, to remove file "unwanted":
+files are in, you can also specify what files are _not_ in, using
+negate patterns. For example, to remove the file `unwanted`:
 
 ----------------
-*
+/*
 !unwanted
 ----------------
 
-Another tricky thing is fully repopulating working directory when you
+Another tricky thing is fully repopulating the working directory when you
 no longer want sparse checkout. You cannot just disable "sparse
-checkout" because skip-worktree are still in the index and you working
-directory is still sparsely populated. You should re-populate working
+checkout" because skip-worktree bits are still in the index and your working
+directory is still sparsely populated. You should re-populate the working
 directory with the `$GIT_DIR/info/sparse-checkout` file content as
 follows:
 
 ----------------
-*
+/*
 ----------------
 
-Then you can disable sparse checkout. Sparse checkout support in "git
-read-tree" and similar commands is disabled by default. You need to
+Then you can disable sparse checkout. Sparse checkout support in 'git
+read-tree' and similar commands is disabled by default. You need to
 turn `core.sparseCheckout` on in order to have sparse checkout
 support.
 
index 9a075bc4d24d59c7b585e204e6feb50326c762f3..504945c69153a30252527c16945836fd2aa2ec5a 100644 (file)
@@ -12,7 +12,6 @@ SYNOPSIS
        [<upstream>] [<branch>]
 'git rebase' [-i | --interactive] [options] --onto <newbase>
        --root [<branch>]
-
 'git rebase' --continue | --skip | --abort
 
 DESCRIPTION
@@ -46,7 +45,7 @@ with a different commit message or timestamp will be skipped).
 It is possible that a merge failure will prevent this process from being
 completely automatic.  You will have to resolve any such merge failure
 and run `git rebase --continue`.  Another option is to bypass the commit
-that caused the merge failure with `git rebase --skip`.  To restore the
+that caused the merge failure with `git rebase --skip`.  To check out the
 original <branch> and remove the .git/rebase-apply working files, use the
 command `git rebase --abort` instead.
 
@@ -233,7 +232,11 @@ leave out at most one of A and B, in which case it defaults to HEAD.
        Restart the rebasing process after having resolved a merge conflict.
 
 --abort::
-       Restore the original branch and abort the rebase operation.
+       Abort the rebase operation and reset HEAD to the original
+       branch. If <branch> was provided when the rebase operation was
+       started, then HEAD will be reset to <branch>. Otherwise HEAD
+       will be reset to where it was when the rebase operation was
+       started.
 
 --skip::
        Restart the rebasing process by skipping the current patch.
index f34e0ae1bd4bf9f3d0fa5de1e8b0eba47637f7a6..b1f7dc643a0e9b6b232e1c75b39d7f784ab21b4a 100644 (file)
@@ -8,6 +8,7 @@ git-receive-pack - Receive what is pushed into the repository
 
 SYNOPSIS
 --------
+[verse]
 'git-receive-pack' <directory>
 
 DESCRIPTION
@@ -149,7 +150,7 @@ if the repository is packed and is served via a dumb transport.
 
 SEE ALSO
 --------
-linkgit:git-send-pack[1]
+linkgit:git-send-pack[1], linkgit:gitnamespaces[7]
 
 GIT
 ---
index 09057bf90cbea3a360cf65c212c358d9c848f11a..976dc1493799c46bc2a390fde36063ae4e7a0e11 100644 (file)
@@ -8,6 +8,7 @@ git-reflog - Manage reflog information
 
 SYNOPSIS
 --------
+[verse]
 'git reflog' <subcommand> <options>
 
 DESCRIPTION
index 98933764870fa73d693c636e713fd1f08eb8244d..3b33c995103060e97977e2977d8114389a29ae3c 100644 (file)
@@ -7,6 +7,7 @@ git-relink - Hardlink common objects in local repositories
 
 SYNOPSIS
 --------
+[verse]
 'git relink' [--safe] <dir>... <master_dir>
 
 DESCRIPTION
index 68263a6a538b3d4659ca9dc0afd2253170143fb6..8a8e1d775d3650bc59ea7db2335cad356a8036b4 100644 (file)
@@ -7,6 +7,7 @@ git-remote-ext - Bridge smart transport to external command.
 
 SYNOPSIS
 --------
+[verse]
 git remote add <nick> "ext::<command>[ <arguments>...]"
 
 DESCRIPTION
index 4aecd4d1878e0af6c3e6c87a63df1375606ff65a..f095d57d09f215e48e1f832ca0698e5666119f2b 100644 (file)
@@ -35,19 +35,19 @@ GIT_TRANSLOOP_DEBUG::
 
 EXAMPLES
 --------
-git fetch fd::17 master::
+`git fetch fd::17 master`::
        Fetch master, using file descriptor #17 to communicate with
        git-upload-pack.
 
-git fetch fd::17/foo master::
+`git fetch fd::17/foo master`::
        Same as above.
 
-git push fd::7,8 master (as URL)::
+`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::
+`git push fd::7,8/bar master`::
        Same as above.
 
 Documentation
index 58f6ad4994dae770b1898888d3073fca777743af..674797cd8308801dc3e3b4f3d6f581005a0a0d2a 100644 (file)
@@ -7,6 +7,7 @@ git-remote-helpers - Helper programs to interact with remote repositories
 
 SYNOPSIS
 --------
+[verse]
 'git remote-<transport>' <repository> [<URL>]
 
 DESCRIPTION
@@ -23,22 +24,141 @@ output. Because a remote helper runs as an independent process from
 git, there is no need to re-link git to add a new helper, nor any
 need to link the helper with the implementation of git.
 
-Every helper must support the "capabilities" command, which git will
-use to determine what other commands the helper will accept.  Other
-commands generally concern facilities like discovering and updating
-remote refs, transporting objects between the object database and
-the remote repository, and updating the local object store.
-
-Helpers supporting the 'fetch' capability can discover refs from the
-remote repository and transfer objects reachable from those refs to
-the local object store. Helpers supporting the 'push' capability can
-transfer local objects to the remote repository and update remote refs.
+Every helper must support the "capabilities" command, which git
+uses to determine what other commands the helper will accept.  Those
+other commands can be used to discover and update remote refs,
+transport objects between the object database and the remote repository,
+and update the local object store.
 
 Git comes with a "curl" family of remote helpers, that handle various
 transport protocols, such as 'git-remote-http', 'git-remote-https',
 'git-remote-ftp' and 'git-remote-ftps'. They implement the capabilities
 'fetch', 'option', and 'push'.
 
+INPUT FORMAT
+------------
+
+Git sends the remote helper a list of commands on standard input, one
+per line.  The first command is always the 'capabilities' command, in
+response to which the remote helper must print a list of the
+capabilities it supports (see below) followed by a blank line.  The
+response to the capabilities command determines what commands Git uses
+in the remainder of the command stream.
+
+The command stream is terminated by a blank line.  In some cases
+(indicated in the documentation of the relevant commands), this blank
+line is followed by a payload in some other protocol (e.g., the pack
+protocol), while in others it indicates the end of input.
+
+Capabilities
+~~~~~~~~~~~~
+
+Each remote helper is expected to support only a subset of commands.
+The operations a helper supports are declared to git in the response
+to the `capabilities` command (see COMMANDS, below).
+
+'option'::
+       For specifying settings like `verbosity` (how much output to
+       write to stderr) and `depth` (how much history is wanted in the
+       case of a shallow clone) that affect how other commands are
+       carried out.
+
+'connect'::
+       For fetching and pushing using git's native packfile protocol
+       that requires a bidirectional, full-duplex connection.
+
+'push'::
+       For listing remote refs and pushing specified objects from the
+       local object store to remote refs.
+
+'fetch'::
+       For listing remote refs and fetching the associated history to
+       the local object store.
+
+'import'::
+       For listing remote refs and fetching the associated history as
+       a fast-import stream.
+
+'refspec' <refspec>::
+       This modifies the 'import' capability, allowing the produced
+       fast-import stream to modify refs in a private namespace
+       instead of writing to refs/heads or refs/remotes directly.
+       It is recommended that all importers providing the 'import'
+       capability use this.
++
+A helper advertising the capability
+`refspec refs/heads/{asterisk}:refs/svn/origin/branches/{asterisk}`
+is saying that, when it is asked to `import refs/heads/topic`, the
+stream it outputs will update the `refs/svn/origin/branches/topic`
+ref.
++
+This capability can be advertised multiple times.  The first
+applicable refspec takes precedence.  The left-hand of refspecs
+advertised with this capability must cover all refs reported by
+the list command.  If no 'refspec' capability is advertised,
+there is an implied `refspec {asterisk}:{asterisk}`.
+
+Capabilities for Pushing
+~~~~~~~~~~~~~~~~~~~~~~~~
+'connect'::
+       Can attempt to connect to 'git receive-pack' (for pushing),
+       'git upload-pack', etc for communication using the
+       packfile protocol.
++
+Supported commands: 'connect'.
+
+'push'::
+       Can discover remote refs and push local commits and the
+       history leading up to them to new or existing remote refs.
++
+Supported commands: 'list for-push', 'push'.
+
+If a helper advertises both 'connect' and 'push', git will use
+'connect' if possible and fall back to 'push' if the helper requests
+so when connecting (see the 'connect' command under COMMANDS).
+
+Capabilities for Fetching
+~~~~~~~~~~~~~~~~~~~~~~~~~
+'connect'::
+       Can try to connect to 'git upload-pack' (for fetching),
+       'git receive-pack', etc for communication using the
+       packfile protocol.
++
+Supported commands: 'connect'.
+
+'fetch'::
+       Can discover remote refs and transfer objects reachable from
+       them to the local object store.
++
+Supported commands: 'list', 'fetch'.
+
+'import'::
+       Can discover remote refs and output objects reachable from
+       them as a stream in fast-import format.
++
+Supported commands: 'list', 'import'.
+
+If a helper advertises 'connect', git will use it if possible and
+fall back to another capability if the helper requests so when
+connecting (see the 'connect' command under COMMANDS).
+When choosing between 'fetch' and 'import', git prefers 'fetch'.
+Other frontends may have some other order of preference.
+
+'refspec' <refspec>::
+       This modifies the 'import' capability.
++
+A helper advertising
+`refspec refs/heads/{asterisk}:refs/svn/origin/branches/{asterisk}`
+in its capabilities is saying that, when it handles
+`import refs/heads/topic`, the stream it outputs will update the
+`refs/svn/origin/branches/topic` ref.
++
+This capability can be advertised multiple times.  The first
+applicable refspec takes precedence.  The left-hand of refspecs
+advertised with this capability must cover all refs reported by
+the list command.  If no 'refspec' capability is advertised,
+there is an implied `refspec {asterisk}:{asterisk}`.
+
 INVOCATION
 ----------
 
@@ -47,6 +167,9 @@ arguments. The first argument specifies a remote repository as in git;
 it is either the name of a configured remote or a URL. The second
 argument specifies a URL; it is usually of the form
 '<transport>://<address>', but any arbitrary string is possible.
+The 'GIT_DIR' environment variable is set up for the remote helper
+and can be used to determine where to store additional data or from
+which directory to invoke auxiliary git commands.
 
 When git encounters a URL of the form '<transport>://<address>', where
 '<transport>' is a protocol that it cannot handle natively, it
@@ -118,7 +241,22 @@ Supported if the helper has the "fetch" capability.
 'push' +<src>:<dst>::
        Pushes the given local <src> commit or branch to the
        remote branch described by <dst>.  A batch sequence of
-       one or more push commands is terminated with a blank line.
+       one or more 'push' commands is terminated with a blank line
+       (if there is only one reference to push, a single 'push' command
+       is followed by a blank line). For example, the following would
+       be two batches of 'push', the first asking the remote-helper
+       to push the local ref 'master' to the remote ref 'master' and
+       the local 'HEAD' to the remote 'branch', and the second
+       asking to push ref 'foo' to ref 'bar' (forced update requested
+       by the '+').
++
+------------
+push refs/heads/master:refs/heads/master
+push HEAD:refs/heads/branch
+\n
+push +refs/heads/foo:refs/heads/bar
+\n
+------------
 +
 Zero or more protocol options may be entered after the last 'push'
 command, before the batch's terminating blank line.
@@ -143,6 +281,11 @@ Supported if the helper has the "push" capability.
 Especially useful for interoperability with a foreign versioning
 system.
 +
+Just like 'push', a batch sequence of one or more 'import' is
+terminated with a blank line. For each batch of 'import', the remote
+helper should produce a fast-import stream terminated by a 'done'
+command.
++
 Supported if the helper has the "import" capability.
 
 'connect' <service>::
@@ -167,26 +310,6 @@ completing a valid response for the current command.
 Additional commands may be supported, as may be determined from
 capabilities reported by the helper.
 
-CAPABILITIES
-------------
-
-'fetch'::
-'option'::
-'push'::
-'import'::
-'connect'::
-       This helper supports the corresponding command with the same name.
-
-'refspec' 'spec'::
-       When using the import command, expect the source ref to have
-       been written to the destination ref. The earliest applicable
-       refspec takes precedence. For example
-       "refs/heads/{asterisk}:refs/svn/origin/branches/{asterisk}" means
-       that, after an "import refs/heads/name", the script has written to
-       refs/svn/origin/branches/name. If this capability is used at
-       all, it must cover all refs reported by the list command; if
-       it is not used, it is effectively "{asterisk}:{asterisk}"
-
 REF LIST ATTRIBUTES
 -------------------
 
@@ -239,6 +362,8 @@ SEE ALSO
 --------
 linkgit:git-remote[1]
 
+linkgit:git-remote-testgit[1]
+
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/Documentation/git-remote-testgit.txt b/Documentation/git-remote-testgit.txt
new file mode 100644 (file)
index 0000000..2a67d45
--- /dev/null
@@ -0,0 +1,30 @@
+git-remote-testgit(1)
+=====================
+
+NAME
+----
+git-remote-testgit - Example remote-helper
+
+
+SYNOPSIS
+--------
+[verse]
+git clone testgit::<source-repo> [<destination>]
+
+DESCRIPTION
+-----------
+
+This command is a simple remote-helper, that is used both as a
+testcase for the remote-helper functionality, and as an example to
+show remote-helper authors one possible implementation.
+
+The best way to learn more is to read the comments and source code in
+'git-remote-testgit.py'.
+
+SEE ALSO
+--------
+linkgit:git-remote-helpers[1]
+
+GIT
+---
+Part of the linkgit:git[1] suite
index 528f34a13106836c1eace8c5d8972543b6fcc121..5a8c5061f3701c57bab75b2b4c70ad620f7e536f 100644 (file)
@@ -60,11 +60,11 @@ the remote repository.
 +
 With `-t <branch>` option, instead of the default glob
 refspec for the remote to track all branches under
-`$GIT_DIR/remotes/<name>/`, a refspec to track only `<branch>`
+the `refs/remotes/<name>/` namespace, a refspec to track only `<branch>`
 is created.  You can give more than one `-t <branch>` to track
 multiple branches without grabbing all branches.
 +
-With `-m <master>` option, `$GIT_DIR/remotes/<name>/HEAD` is set
+With `-m <master>` option, a symbolic-ref `refs/remotes/<name>/HEAD` is set
 up to point at remote's `<master>` branch. See also the set-head command.
 +
 When a fetch mirror is created with `\--mirror=fetch`, the refs will not
@@ -92,24 +92,25 @@ configuration settings for the remote are removed.
 
 'set-head'::
 
-Sets or deletes the default branch (`$GIT_DIR/remotes/<name>/HEAD`) for
+Sets or deletes the default branch (i.e. the target of the
+symbolic-ref `refs/remotes/<name>/HEAD`) for
 the named remote. Having a default branch for a remote is not required,
 but allows the name of the remote to be specified in lieu of a specific
 branch. For example, if the default branch for `origin` is set to
 `master`, then `origin` may be specified wherever you would normally
 specify `origin/master`.
 +
-With `-d`, `$GIT_DIR/remotes/<name>/HEAD` is deleted.
+With `-d`, the symbolic ref `refs/remotes/<name>/HEAD` is deleted.
 +
-With `-a`, the remote is queried to determine its `HEAD`, then
-`$GIT_DIR/remotes/<name>/HEAD` is set to the same branch. e.g., if the remote
+With `-a`, the remote is queried to determine its `HEAD`, then the
+symbolic-ref `refs/remotes/<name>/HEAD` is set to the same branch. e.g., if the remote
 `HEAD` is pointed at `next`, "`git remote set-head origin -a`" will set
-`$GIT_DIR/refs/remotes/origin/HEAD` to `refs/remotes/origin/next`. This will
+the symbolic-ref `refs/remotes/origin/HEAD` to `refs/remotes/origin/next`. This will
 only work if `refs/remotes/origin/next` already exists; if not it must be
 fetched first.
 +
-Use `<branch>` to set `$GIT_DIR/remotes/<name>/HEAD` explicitly. e.g., "git
-remote set-head origin master" will set `$GIT_DIR/refs/remotes/origin/HEAD` to
+Use `<branch>` to set the symbolic-ref `refs/remotes/<name>/HEAD` explicitly. e.g., "git
+remote set-head origin master" will set the symbolic-ref `refs/remotes/origin/HEAD` to
 `refs/remotes/origin/master`. This will only work if
 `refs/remotes/origin/master` already exists; if not it must be fetched first.
 +
index 0decee240bfbad5c924de36e1867edca9975f39c..40af321153b85845fc362501d91f2178e0b3e42c 100644 (file)
@@ -8,6 +8,7 @@ git-repack - Pack unpacked objects in a repository
 
 SYNOPSIS
 --------
+[verse]
 'git repack' [-a] [-A] [-d] [-f] [-F] [-l] [-n] [-q] [--window=<n>] [--depth=<n>]
 
 DESCRIPTION
index a0d1fa6594cc4fed65c7ce68a4957e5e611cfa78..9ec115b9e034cee65449eff70f032b7b0c225108 100644 (file)
@@ -8,6 +8,7 @@ git-repo-config - Get and set repository or global options
 
 SYNOPSIS
 --------
+[verse]
 'git repo-config' ...
 
 
index 3521d8e3c88c97f85ab95d82d416256d0debc94c..b99681ce85abc0879f11e99654e1bdb6ba5dd688 100644 (file)
@@ -7,6 +7,7 @@ git-request-pull - Generates a summary of pending changes
 
 SYNOPSIS
 --------
+[verse]
 'git request-pull' [-p] <start> <url> [<end>]
 
 DESCRIPTION
index 52db1d80cfe068f39ae5996a88e95bbfcf095a36..a6253ba617f5ebcd82e4cfb1702999c145fa346c 100644 (file)
@@ -7,6 +7,7 @@ git-rerere - Reuse recorded resolution of conflicted merges
 
 SYNOPSIS
 --------
+[verse]
 'git rerere' ['clear'|'forget' <pathspec>|'diff'|'status'|'gc']
 
 DESCRIPTION
index 02c44c999f361cd7c40a504ae4ccdc2d8b1a18fe..42c9676eaa40f2a3d43514c09509e319258ca8b4 100644 (file)
@@ -8,6 +8,7 @@ git-rev-parse - Pick out and massage parameters
 
 SYNOPSIS
 --------
+[verse]
 'git rev-parse' [ --option ] <args>...
 
 DESCRIPTION
index ac10cfbb14aba460973019d712ee90e8804de34d..b311d59c7c06dd696e3c667c97a5982137bdd87a 100644 (file)
@@ -7,6 +7,7 @@ git-revert - Revert some existing commits
 
 SYNOPSIS
 --------
+[verse]
 'git revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] <commit>...
 
 DESCRIPTION
@@ -23,7 +24,7 @@ throw away all uncommitted changes in your working directory, you
 should see linkgit:git-reset[1], particularly the '--hard' option.  If
 you want to extract specific files as they were in another commit, you
 should see linkgit:git-checkout[1], specifically the `git checkout
-<commit> -- <filename>` syntax.  Take care with these alternatives as
+<commit> \-- <filename>` syntax.  Take care with these alternatives as
 both will discard uncommitted changes in your working directory.
 
 OPTIONS
@@ -92,12 +93,12 @@ effect to your index in a row.
 
 EXAMPLES
 --------
-git revert HEAD~3::
+`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{tilde}5..master{tilde}2::
+`git revert -n master{tilde}5..master{tilde}2`::
 
        Revert the changes done by commits from the fifth last commit
        in master (included) to the third last commit in master
index 8c0554f9716c0d3ca7570a8f893f48189ea0c6f8..665ad4ddab14da173142914407680605dad9dae6 100644 (file)
@@ -7,6 +7,7 @@ git-rm - Remove files from the working tree and from the index
 
 SYNOPSIS
 --------
+[verse]
 'git rm' [-f | --force] [-n] [-r] [--cached] [--ignore-unmatch] [--quiet] [--] <file>...
 
 DESCRIPTION
@@ -136,7 +137,7 @@ git diff --name-only --diff-filter=D -z | xargs -0 git rm --cached
 
 EXAMPLES
 --------
-git rm Documentation/\*.txt::
+`git rm Documentation/\*.txt`::
        Removes all `*.txt` files from the index that are under the
        `Documentation` directory and any of its subdirectories.
 +
@@ -144,7 +145,7 @@ Note that the asterisk `*` is quoted from the shell in this
 example; this lets git, and not the shell, expand the pathnames
 of files and subdirectories under the `Documentation/` directory.
 
-git rm -f git-*.sh::
+`git rm -f git-*.sh`::
        Because this example lets the shell expand the asterisk
        (i.e. you are listing the files explicitly), it
        does not remove `subdir/git-foo.sh`.
index 5a168cfab2206cf67453bb5f1fd93dbe75bb4ef0..327233c85b4cb9af07b8866b055a83d25b21b569 100644 (file)
@@ -8,6 +8,7 @@ git-send-email - Send a collection of patches as emails
 
 SYNOPSIS
 --------
+[verse]
 'git send-email' [options] <file|directory|rev-list options>...
 
 
index 17f8f5552646cc063fdcc3674c0b133ed070aec9..bd3eaa69bfb6e788d297b3e7d2c871d25a478f80 100644 (file)
@@ -8,6 +8,7 @@ git-send-pack - Push objects over git protocol to another repository
 
 SYNOPSIS
 --------
+[verse]
 'git send-pack' [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]
 
 DESCRIPTION
index 61e4c08dacf796853284ee03c3ccdbebddb51a9b..5c3ec327bbc5836c53b0b94f9dc0de4772661e36 100644 (file)
@@ -1,5 +1,5 @@
-git-sh-i18n--envsubst(1)
-========================
+git-sh-i18n{litdd}envsubst(1)
+=============================
 
 NAME
 ----
@@ -10,8 +10,8 @@ SYNOPSIS
 [verse]
 eval_gettext () {
        printf "%s" "$1" | (
-               export PATH $('git sh-i18n--envsubst' --variables "$1");
-               'git sh-i18n--envsubst' "$1"
+               export PATH $('git sh-i18n{litdd}envsubst' --variables "$1");
+               'git sh-i18n{litdd}envsubst' "$1"
        )
 }
 
@@ -22,7 +22,7 @@ This is not a command the end user would want to run.  Ever.
 This documentation is meant for people who are studying the
 plumbing scripts and/or are writing new ones.
 
-git-sh-i18n--envsubst is Git's stripped-down copy of the GNU
+'git sh-i18n{litdd}envsubst' is Git's stripped-down copy of the GNU
 `envsubst(1)` program that comes with the GNU gettext package. It's
 used internally by linkgit:git-sh-i18n[1] to interpolate the variables
 passed to the the `eval_gettext` function.
index 3b1f7ac7b5d82ed6fd8083342f441fa1204aff4b..60cf49cb2a38df99d4526698576acd5cb6c979c4 100644 (file)
@@ -7,6 +7,7 @@ git-sh-i18n - Git's i18n setup code for shell scripts
 
 SYNOPSIS
 --------
+[verse]
 '. "$(git --exec-path)/git-sh-i18n"'
 
 DESCRIPTION
@@ -34,7 +35,7 @@ gettext::
 eval_gettext::
        Currently a dummy fall-through function implemented as a wrapper
        around `printf(1)` with variables expanded by the
-       linkgit:git-sh-i18n--envsubst[1] helper. Will be replaced by a
+       linkgit:git-sh-i18n{litdd}envsubst[1] helper. Will be replaced by a
        real gettext implementation in a later version.
 
 GIT
index 27fd8ba854698dcd31f5348ddc00a607abb64965..a2f346ca710e03a7f5c65574659ac578ab8bf615 100644 (file)
@@ -7,6 +7,7 @@ git-sh-setup - Common git shell script setup code
 
 SYNOPSIS
 --------
+[verse]
 '. "$(git --exec-path)/git-sh-setup"'
 
 DESCRIPTION
index d7d4b92894d143daaf1f0e27fa4bdbcf35aba10d..9b9250600f651eac7d6e2fd6ca7ff320a64de67c 100644 (file)
@@ -8,6 +8,7 @@ git-shell - Restricted login shell for Git-only SSH access
 
 SYNOPSIS
 --------
+[verse]
 'git shell' [-c <command> <argument>]
 
 DESCRIPTION
index ee4559b6f20d6cfe399c2b7e1b8dd9a1e2aafc7a..a8e77b5350c8c6a3cf62c22f811974ea64cff8ae 100644 (file)
@@ -13,7 +13,6 @@ SYNOPSIS
                [--more=<n> | --list | --independent | --merge-base]
                [--no-name | --sha1-name] [--topics]
                [(<rev> | <glob>)...]
-
 'git show-branch' (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>]
 
 DESCRIPTION
index c4d99f10281599ae52f95da455372e8ed759cfe7..2dcbbb245421964d418818878cd05a4eda525513 100644 (file)
@@ -8,6 +8,7 @@ git-show-index - Show packed archive index
 
 SYNOPSIS
 --------
+[verse]
 'git show-index' < idx-file
 
 
index 7f075e84f54e3abc5c16b7cae57af831bd2b5bd7..1e38819e67cda4282ec7ede004ee5668d0a478c8 100644 (file)
@@ -8,6 +8,7 @@ git-show - Show various types of objects
 
 SYNOPSIS
 --------
+[verse]
 'git show' [options] <object>...
 
 DESCRIPTION
@@ -47,23 +48,23 @@ include::pretty-formats.txt[]
 EXAMPLES
 --------
 
-git show v1.0.0::
+`git show v1.0.0`::
        Shows the tag `v1.0.0`, along with the object the tags
        points at.
 
-git show v1.0.0^\{tree\}::
+`git show v1.0.0^\{tree\}`::
        Shows the tree pointed to by the tag `v1.0.0`.
 
-git show -s --format=%s v1.0.0^\{commit\}::
+`git show -s --format=%s v1.0.0^\{commit\}`::
        Shows the subject of the commit pointed to by the
        tag `v1.0.0`.
 
-git show next~10:Documentation/README::
+`git show next~10:Documentation/README`::
        Shows the contents of the file `Documentation/README` as
        they were current in the 10th last commit of the branch
        `next`.
 
-git show master:Makefile master:t/Makefile::
+`git show master:Makefile master:t/Makefile`::
        Concatenates the contents of said Makefiles in the head
        of the branch `master`.
 
index 15f051fa44e4b2e90da8383318c62695359772ce..43af38aa4bd7673bef2127058f171da039732c71 100644 (file)
@@ -13,7 +13,8 @@ SYNOPSIS
 'git stash' drop [-q|--quiet] [<stash>]
 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]
 'git stash' branch <branchname> [<stash>]
-'git stash' [save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]]
+'git stash' [save [--patch] [-k|--[no-]keep-index] [-q|--quiet]
+            [-u|--include-untracked] [-a|--all] [<message>]]
 'git stash' clear
 'git stash' create
 
@@ -42,7 +43,7 @@ is also possible).
 OPTIONS
 -------
 
-save [-p|--patch] [--[no-]keep-index] [-q|--quiet] [<message>]::
+save [-p|--patch] [--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::
 
        Save your local modifications to a new 'stash', and run `git reset
        --hard` to revert them.  The <message> part is optional and gives
@@ -54,6 +55,11 @@ save [-p|--patch] [--[no-]keep-index] [-q|--quiet] [<message>]::
 If the `--keep-index` option is used, all changes already added to the
 index are left intact.
 +
+If the `--include-untracked` option is used, all untracked files are also
+stashed and then cleaned up with `git clean`, leaving the working directory
+in a very clean state. If the `--all` option is used instead then the
+ignored files are stashed and cleaned in addition to the untracked files.
++
 With `--patch`, you can interactively select hunks from the diff
 between HEAD and the working tree to be stashed.  The stash entry is
 constructed such that its index state is the same as the index state
index 38cb741f180e0869a91a61a5f7706eb664b86d91..3d51717bbe84d0201b1c7a38943b2e99643bd89f 100644 (file)
@@ -8,6 +8,7 @@ git-status - Show the working tree status
 
 SYNOPSIS
 --------
+[verse]
 'git status' [<options>...] [--] [<pathspec>...]
 
 DESCRIPTION
@@ -69,6 +70,9 @@ configuration variable documented in linkgit:git-config[1].
        (and suppresses the output of submodule summaries when the config option
        `status.submodulesummary` is set).
 
+--ignored::
+       Show ignored files as well.
+
 -z::
        Terminate entries with NUL, instead of LF.  This implies
        the `--porcelain` output format if no other format is given.
@@ -119,7 +123,8 @@ codes can be interpreted as follows:
 * 'C' = copied
 * 'U' = updated but unmerged
 
-Ignored files are not listed.
+Ignored files are not listed, unless `--ignored` option is in effect,
+in which case `XY` are `!!`.
 
     X          Y     Meaning
     -------------------------------------------------
@@ -142,6 +147,7 @@ Ignored files are not listed.
     U           U    unmerged, both modified
     -------------------------------------------------
     ?           ?    untracked
+    !           !    ignored
     -------------------------------------------------
 
 If -b is used the short-format status is preceded by a line
index 10509cc450d47de35387a547cb79e30177f6304c..b78f031cd4464b21be145d4ffa79ff39dc8bd2bb 100644 (file)
@@ -8,6 +8,7 @@ git-stripspace - Filter out empty lines
 
 SYNOPSIS
 --------
+[verse]
 'git stripspace' [-s | --strip-comments] < <stream>
 
 DESCRIPTION
index 5e7a4130eeec48c27abf92b37834613692446723..67cf5f0f8b8a280891e47ecb7705d260bfc4232b 100644 (file)
@@ -15,7 +15,8 @@ SYNOPSIS
 'git submodule' [--quiet] init [--] [<path>...]
 'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--rebase]
              [--reference <repository>] [--merge] [--recursive] [--] [<path>...]
-'git submodule' [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...]
+'git submodule' [--quiet] summary [--cached|--files] [(-n|--summary-limit) <n>]
+             [commit] [--] [<path>...]
 'git submodule' [--quiet] foreach [--recursive] <command>
 'git submodule' [--quiet] sync [--] [<path>...]
 
@@ -78,7 +79,9 @@ to exist in the superproject. If <path> is not given, the
 <repository> is the URL of the new submodule's origin repository.
 This may be either an absolute URL, or (if it begins with ./
 or ../), the location relative to the superproject's origin
-repository.
+repository. If the superproject doesn't have an origin configured
+the superproject is its own authoritative upstream and the current
+working directory is used instead.
 +
 <path> is the relative location for the cloned submodule to
 exist in the superproject. If <path> does not exist, then the
@@ -106,8 +109,13 @@ status::
        repository and `U` if the submodule has merge conflicts.
        This command is the default command for 'git submodule'.
 +
-If '--recursive' is specified, this command will recurse into nested
+If `--recursive` is specified, this command will recurse into nested
 submodules, and show their status as well.
++
+If you are only interested in changes of the currently initialized
+submodules with respect to the commit recorded in the index or the HEAD,
+linkgit:git-status[1] and linkgit:git-diff[1] will provide that information
+too (and can also report changes to a submodule's work tree).
 
 init::
        Initialize the submodules, i.e. register each submodule name
@@ -123,26 +131,29 @@ init::
 update::
        Update the registered submodules, i.e. clone missing submodules and
        checkout the commit specified in the index of the containing repository.
-       This will make the submodules HEAD be detached unless '--rebase' or
-       '--merge' is specified or the key `submodule.$name.update` is set to
+       This will make the submodules HEAD be detached unless `--rebase` or
+       `--merge` is specified or the key `submodule.$name.update` is set to
        `rebase` or `merge`.
 +
 If the submodule is not yet initialized, and you just want to use the
 setting as stored in .gitmodules, you can automatically initialize the
-submodule with the --init option.
+submodule with the `--init` option.
 +
-If '--recursive' is specified, this command will recurse into the
+If `--recursive` is specified, this command will recurse into the
 registered submodules, and update any nested submodules within.
 
 summary::
        Show commit summary between the given commit (defaults to HEAD) and
        working tree/index. For a submodule in question, a series of commits
        in the submodule between the given super project commit and the
-       index or working tree (switched by --cached) are shown. If the option
-       --files is given, show the series of commits in the submodule between
+       index or working tree (switched by `--cached`) are shown. If the option
+       `--files` is given, show the series of commits in the submodule between
        the index of the super project and the working tree of the submodule
-       (this option doesn't allow to use the --cached option or to provide an
+       (this option doesn't allow to use the `--cached` option or to provide an
        explicit commit).
++
+Using the `--submodule=log` option with linkgit:git-diff[1] will provide that
+information too.
 
 foreach::
        Evaluates an arbitrary shell command in each checked out submodule.
@@ -153,9 +164,9 @@ foreach::
        superproject, $sha1 is the commit as recorded in the superproject,
        and $toplevel is the absolute path to the top-level of the superproject.
        Any submodules defined in the superproject but not checked out are
-       ignored by this command. Unless given --quiet, foreach prints the name
+       ignored by this command. Unless given `--quiet`, foreach prints the name
        of each submodule before evaluating the command.
-       If --recursive is given, submodules are traversed recursively (i.e.
+       If `--recursive` is given, submodules are traversed recursively (i.e.
        the given shell command is evaluated in nested submodules as well).
        A non-zero return from the command in any submodule causes
        the processing to terminate. This can be overridden by adding '|| :'
@@ -167,12 +178,14 @@ commit for each submodule.
 
 sync::
        Synchronizes submodules' remote URL configuration setting
-       to the value specified in .gitmodules.  This is useful when
+       to the value specified in .gitmodules. It will only affect those
+       submodules which already have an url entry in .git/config (that is the
+       case when they are initialized or freshly added). This is useful when
        submodule URLs change upstream and you need to update your local
        repositories accordingly.
 +
 "git submodule sync" synchronizes all submodules while
-"git submodule sync -- A" synchronizes submodule "A" only.
+"git submodule sync \-- A" synchronizes submodule "A" only.
 
 OPTIONS
 -------
@@ -233,13 +246,18 @@ OPTIONS
        If the key `submodule.$name.update` is set to `rebase`, this option is
        implicit.
 
+--init::
+       This option is only valid for the update command.
+       Initialize all submodules for which "git submodule init" has not been
+       called so far before updating.
+
 --reference <repository>::
        This option is only valid for add and update commands.  These
        commands sometimes need to clone a remote repository. In this case,
        this option will be passed to the linkgit:git-clone[1] command.
 +
 *NOTE*: Do *not* use this option unless you have read the note
-for linkgit:git-clone[1]'s --reference and --shared options carefully.
+for linkgit:git-clone[1]'s `--reference` and `--shared` options carefully.
 
 --recursive::
        This option is only valid for foreach, update and status commands.
index 713e523034d5b95071e2285252c1796cd83c6fcd..f977e8780b5e152b18d07a61fde8c2f86f98a0ce 100644 (file)
@@ -7,6 +7,7 @@ git-svn - Bidirectional operation between a Subversion repository and git
 
 SYNOPSIS
 --------
+[verse]
 'git svn' <command> [options] [arguments]
 
 DESCRIPTION
@@ -156,6 +157,17 @@ Skip "branches" and "tags" of first level directories;;
        affecting the working tree; and the 'rebase' command will be
        able to update the working tree with the latest changes.
 
+--preserve-empty-dirs;;
+       Create a placeholder file in the local Git repository for each
+       empty directory fetched from Subversion.  This includes directories
+       that become empty by removing all entries in the Subversion
+       repository (but not the directory itself).  The placeholder files
+       are also tracked and removed when no longer necessary.
+
+--placeholder-filename=<filename>;;
+       Set the name of placeholder files created by --preserve-empty-dirs.
+       Default: ".gitignore"
+
 'rebase'::
        This fetches revisions from the SVN parent of the current HEAD
        and rebases the current (uncommitted to SVN) work against it.
@@ -210,8 +222,17 @@ discouraged.
        Add the given merge information during the dcommit
        (e.g. `--mergeinfo="/branches/foo:1-10"`). All svn server versions can
        store this information (as a property), and svn clients starting from
-       version 1.5 can make use of it. 'git svn' currently does not use it
-       and does not set it automatically.
+       version 1.5 can make use of it. To specify merge information from multiple
+       branches, use a single space character between the branches
+       (`--mergeinfo="/branches/foo:1-10 /branches/bar:3,5-6,8"`)
++
+[verse]
+config key: svn.pushmergeinfo
++
+This option will cause git-svn to attempt to automatically populate the
+svn:mergeinfo property in the SVN repository when possible. Currently, this can
+only be done when dcommitting non-fast-forward merges where all parents but the
+first have already been pushed into SVN.
 
 'branch'::
        Create a branch in the SVN repository.
@@ -297,7 +318,7 @@ Any other arguments are passed directly to 'git log'
        Show what revision and author last modified each line of a file. The
        output of this mode is format-compatible with the output of
        `svn blame' by default. Like the SVN blame command,
-       local uncommitted changes in the working copy are ignored;
+       local uncommitted changes in the working tree are ignored;
        the version of the file in the HEAD revision is annotated. Unknown
        arguments are passed directly to 'git blame'.
 +
index d7795ed65777c18e648ba1b23b1b63330b78c9ce..75b1ae5061b78524383221641aee5edd292028e6 100644 (file)
@@ -7,6 +7,7 @@ git-symbolic-ref - Read and modify symbolic refs
 
 SYNOPSIS
 --------
+[verse]
 'git symbolic-ref' [-q] [-m <reason>] <name> [<ref>]
 
 DESCRIPTION
index d82f62120a21059a31d9c708c39d93b2a630d590..fb1c0ac694bdac20e2fef74710df35c1c4b1e774 100644 (file)
@@ -12,7 +12,7 @@ SYNOPSIS
 'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]
        <tagname> [<commit> | <object>]
 'git tag' -d <tagname>...
-'git tag' [-n[<num>]] -l [--contains <commit>] [<pattern>]
+'git tag' [-n[<num>]] -l [--contains <commit>] [<pattern>...]
 'git tag' -v <tagname>...
 
 DESCRIPTION
@@ -69,8 +69,11 @@ OPTIONS
        If the tag is not annotated, the commit message is displayed instead.
 
 -l <pattern>::
-       List tags with names that match the given pattern (or all if no pattern is given).
-       Typing "git tag" without arguments, also lists all tags.
+       List tags with names that match the given pattern (or all if no
+       pattern is given).  Running "git tag" without arguments also
+       lists all tags. The pattern is a shell wildcard (i.e., matched
+       using fnmatch(3)).  Multiple patterns may be given; if any of
+       them matches, the tag is shown.
 
 --contains <commit>::
        Only list tags which contain the specified commit.
index 5f1575425743118ffb13567472ae81be56698566..346e7a20797c22a29733f09e5fe241c84a46bfa9 100644 (file)
@@ -8,6 +8,7 @@ git-tar-tree - Create a tar archive of the files in the named tree object
 
 SYNOPSIS
 --------
+[verse]
 'git tar-tree' [--remote=<repo>] <tree-ish> [ <base> ]
 
 DESCRIPTION
@@ -52,26 +53,26 @@ tar.umask::
 
 EXAMPLES
 --------
-git tar-tree HEAD junk | (cd /var/tmp/ && tar xf -)::
+`git tar-tree HEAD junk | (cd /var/tmp/ && tar xf -)`::
 
        Create a tar archive that contains the contents of the
        latest commit on the current branch, and extracts it in
        `/var/tmp/junk` directory.
 
-git tar-tree v1.4.0 git-1.4.0 | gzip >git-1.4.0.tar.gz::
+`git tar-tree v1.4.0 git-1.4.0 | gzip >git-1.4.0.tar.gz`::
 
        Create a tarball for v1.4.0 release.
 
-git tar-tree v1.4.0{caret}\{tree\} git-1.4.0 | gzip >git-1.4.0.tar.gz::
+`git tar-tree v1.4.0{caret}\{tree\} git-1.4.0 | gzip >git-1.4.0.tar.gz`::
 
        Create a tarball for v1.4.0 release, but without a
        global extended pax header.
 
-git tar-tree --remote=example.com:git.git v1.4.0 >git-1.4.0.tar::
+`git tar-tree --remote=example.com:git.git v1.4.0 >git-1.4.0.tar`::
 
        Get a tarball v1.4.0 from example.com.
 
-git tar-tree HEAD:Documentation/ git-docs > git-1.4.0-docs.tar::
+`git tar-tree HEAD:Documentation/ git-docs > git-1.4.0-docs.tar`::
 
        Put everything in the current head's Documentation/ directory
        into 'git-1.4.0-docs.tar', with the prefix 'git-docs/'.
index c49d727f7463446c4182807cbd9077db5e34007f..e9f148a00ddbc996e440cd0fd7eecae4eeff3fa8 100644 (file)
@@ -9,6 +9,7 @@ git-unpack-file - Creates a temporary file with a blob's contents
 
 SYNOPSIS
 --------
+[verse]
 'git unpack-file' <blob>
 
 DESCRIPTION
index dd7799095b4dd26a1625015f3fe0fcbd89dd0e70..ff23494e7013c5d491c7a7e2642ec30133906aac 100644 (file)
@@ -8,6 +8,7 @@ git-unpack-objects - Unpack objects from a packed archive
 
 SYNOPSIS
 --------
+[verse]
 'git unpack-objects' [-n] [-q] [-r] [--strict] <pack-file
 
 
index d3931294d174e5c06adb81e428e31e00fbfa3b5e..a3081f4e237747dc858fd105302cbb9a489de41b 100644 (file)
@@ -264,7 +264,9 @@ tree files, you have to explicitly tell git about it by dropping
 "assume unchanged" bit, either before or after you modify them.
 
 In order to set "assume unchanged" bit, use `--assume-unchanged`
-option.  To unset, use `--no-assume-unchanged`.
+option.  To unset, use `--no-assume-unchanged`. To see which files
+have the "assume unchanged" bit set, use `git ls-files -v`
+(see linkgit:git-ls-files[1]).
 
 The command looks at `core.ignorestat` configuration variable.  When
 this is true, paths updated with `git update-index paths...` and
@@ -363,7 +365,8 @@ ctime for marking files processed) (see linkgit:git-config[1]).
 SEE ALSO
 --------
 linkgit:git-config[1],
-linkgit:git-add[1]
+linkgit:git-add[1],
+linkgit:git-ls-files[1]
 
 GIT
 ---
index e25a65a80fafb7143957247e2fd85d64a701ddf5..d377a352433cb38536f42c5b3fc6b80ae2b11f7f 100644 (file)
@@ -7,6 +7,7 @@ git-update-ref - Update the object name stored in a ref safely
 
 SYNOPSIS
 --------
+[verse]
 'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>])
 
 DESCRIPTION
@@ -60,8 +61,9 @@ still contains <oldvalue>.
 
 Logging Updates
 ---------------
-If config parameter "core.logAllRefUpdates" is true or the file
-"$GIT_DIR/logs/<ref>" exists then `git update-ref` will append
+If config parameter "core.logAllRefUpdates" is true and the ref is one under
+"refs/heads/", "refs/remotes/", "refs/notes/", or the symbolic ref HEAD; or
+the file "$GIT_DIR/logs/<ref>" exists then `git update-ref` will append
 a line to the log file "$GIT_DIR/logs/<ref>" (dereferencing all
 symbolic refs before creating the log name) describing the change
 in ref value.  Log lines are formatted as:
index 775024da3ed6ed50e8aa587dc31eb79f00030e84..bd0e36492fa0f7b8a8a4707c2ac7db461fcc74c8 100644 (file)
@@ -8,6 +8,7 @@ git-update-server-info - Update auxiliary info file to help dumb servers
 
 SYNOPSIS
 --------
+[verse]
 'git update-server-info' [--force]
 
 DESCRIPTION
index acbf634f85c72d70bbb7dd7653aded63d67dcbf6..4d52d3833aeaa8621f486bc99dd2114d53708e61 100644 (file)
@@ -8,6 +8,7 @@ git-upload-archive - Send archive back to git-archive
 
 SYNOPSIS
 --------
+[verse]
 'git upload-archive' <directory>
 
 DESCRIPTION
index 4c0ca9ded25a27c11636ef9af5f7c1f2e6970c12..71f16083d6b2c366a54db1d966e17ab2aeda1ef1 100644 (file)
@@ -8,6 +8,7 @@ git-upload-pack - Send objects packed back to git-fetch-pack
 
 SYNOPSIS
 --------
+[verse]
 'git-upload-pack' [--strict] [--timeout=<n>] <directory>
 
 DESCRIPTION
@@ -33,6 +34,10 @@ OPTIONS
 <directory>::
        The repository to sync from.
 
+SEE ALSO
+--------
+linkgit:gitnamespaces[7]
+
 GIT
 ---
 Part of the linkgit:git[1] suite
index 6498f7cb69be3d3fcece285a714679ac7f4f6454..5317cc247454b1a080b2609139befeba487d5ff5 100644 (file)
@@ -8,6 +8,7 @@ git-var - Show a git logical variable
 
 SYNOPSIS
 --------
+[verse]
 'git var' ( -l | <variable> )
 
 DESCRIPTION
index 7c2428d5692ce7e3961821c865c9539c30dca113..cd230769fdc6db2575d274d2a64608fe414c0d2e 100644 (file)
@@ -8,6 +8,7 @@ git-verify-pack - Validate packed git archive files
 
 SYNOPSIS
 --------
+[verse]
 'git verify-pack' [-v|--verbose] [-s|--stat-only] [--] <pack>.idx ...
 
 
index 8c9a71865b45120700c21a52d1e06aad45f3221f..5ff76e892aed19ff7943848edbf32c772b845d3d 100644 (file)
@@ -7,6 +7,7 @@ git-verify-tag - Check the GPG signature of tags
 
 SYNOPSIS
 --------
+[verse]
 'git verify-tag' <tag>...
 
 DESCRIPTION
index 69d92fa00ef91dff0346925ba1c50857132e9c03..c2bc87bc61da28a9eb451ef1cb5b4d811b02e4f7 100644 (file)
@@ -7,6 +7,7 @@ git-web--browse - git helper script to launch a web browser
 
 SYNOPSIS
 --------
+[verse]
 'git web{litdd}browse' [OPTIONS] URL/FILE ...
 
 DESCRIPTION
@@ -68,7 +69,7 @@ browser.<tool>.path
 You can explicitly provide a full path to your preferred browser by
 setting the configuration variable 'browser.<tool>.path'. For example,
 you can configure the absolute path to firefox by setting
-'browser.firefox.path'. Otherwise, 'git web--browse' assumes the tool
+'browser.firefox.path'. Otherwise, 'git web{litdd}browse' assumes the tool
 is available in PATH.
 
 browser.<tool>.cmd
index 31f3663ae7fe2cc97d6e61b85181a02424323fb2..76c7f7eec5bb8ba01262762909939070b55ef5c9 100644 (file)
@@ -8,6 +8,7 @@ git-whatchanged - Show logs with difference each commit introduces
 
 SYNOPSIS
 --------
+[verse]
 'git whatchanged' <option>...
 
 DESCRIPTION
@@ -52,12 +53,12 @@ include::pretty-formats.txt[]
 
 Examples
 --------
-git whatchanged -p v2.6.12.. include/scsi drivers/scsi::
+`git whatchanged -p v2.6.12.. include/scsi drivers/scsi`::
 
        Show as patches the commits since version 'v2.6.12' that changed
        any file in the include/scsi or drivers/scsi subdirectories
 
-git whatchanged --since="2 weeks ago" \-- gitk::
+`git whatchanged --since="2 weeks ago" \-- gitk`::
 
        Show the changes during the last two weeks to the file 'gitk'.
        The "--" is necessary to avoid confusion with the *branch* named
index e8c94c1352a8f1a889225ee063b4c59799cbb7e6..f22041a9dc3965b4a4f704e84ec7700a5993d5c6 100644 (file)
@@ -8,6 +8,7 @@ git-write-tree - Create a tree object from the current index
 
 SYNOPSIS
 --------
+[verse]
 'git write-tree' [--missing-ok] [--prefix=<prefix>/]
 
 DESCRIPTION
index 3c7a832343ea983f7122c5d33429a4129fdba70a..5e80cfd71a5529225b607bd3f1732d19f05d0c1d 100644 (file)
@@ -10,8 +10,8 @@ SYNOPSIS
 --------
 [verse]
 'git' [--version] [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
-    [-p|--paginate|--no-pager] [--no-replace-objects]
-    [--bare] [--git-dir=<path>] [--work-tree=<path>]
+    [-p|--paginate|--no-pager] [--no-replace-objects] [--bare]
+    [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
     [-c <name>=<value>]
     [--help] <command> [<args>]
 
@@ -44,9 +44,19 @@ unreleased) version of git, that is available from 'master'
 branch of the `git.git` repository.
 Documentation for older releases are available here:
 
-* link:v1.7.6/git.html[documentation for release 1.7.6]
+* link:v1.7.7.1/git.html[documentation for release 1.7.7.1]
 
 * release notes for
+  link:RelNotes/1.7.7.1.txt[1.7.7.1],
+  link:RelNotes/1.7.7.txt[1.7.7].
+
+* link:v1.7.6.4/git.html[documentation for release 1.7.6.4]
+
+* release notes for
+  link:RelNotes/1.7.6.4.txt[1.7.6.4],
+  link:RelNotes/1.7.6.3.txt[1.7.6.3],
+  link:RelNotes/1.7.6.2.txt[1.7.6.2],
+  link:RelNotes/1.7.6.1.txt[1.7.6.1],
   link:RelNotes/1.7.6.txt[1.7.6].
 
 * link:v1.7.5.4/git.html[documentation for release 1.7.5.4]
@@ -330,6 +340,11 @@ help ...`.
        variable (see core.worktree in linkgit:git-config[1] for a
        more detailed discussion).
 
+--namespace=<path>::
+       Set the git namespace.  See linkgit:gitnamespaces[7] for more
+       details.  Equivalent to setting the `GIT_NAMESPACE` environment
+       variable.
+
 --bare::
        Treat the repository as a bare repository.  If GIT_DIR
        environment is not set, it is set to the current working
@@ -523,16 +538,15 @@ Any git command accepting any <object> can also use the following
 symbolic notation:
 
 HEAD::
-       indicates the head of the current branch (i.e. the
-       contents of `$GIT_DIR/HEAD`).
+       indicates the head of the current branch.
 
 <tag>::
        a valid tag 'name'
-       (i.e. the contents of `$GIT_DIR/refs/tags/<tag>`).
+       (i.e. a `refs/tags/<tag>` reference).
 
 <head>::
        a valid head 'name'
-       (i.e. the contents of `$GIT_DIR/refs/heads/<head>`).
+       (i.e. a `refs/heads/<head>` reference).
 
 For a more complete list of ways to spell object names, see
 "SPECIFYING REVISIONS" section in linkgit:gitrevisions[7].
@@ -594,6 +608,10 @@ git so take care if using Cogito etc.
        This can also be controlled by the '--work-tree' command line
        option and the core.worktree configuration variable.
 
+'GIT_NAMESPACE'::
+       Set the git namespace; see linkgit:gitnamespaces[7] for details.
+       The '--namespace' command-line option also sets this value.
+
 'GIT_CEILING_DIRECTORIES'::
        This should be a colon-separated list of absolute paths.
        If set, it is a list of directories that git should not chdir
index 412c55b549354471a56a4e56ca5533277c83bc54..25e46aeb7a32287c7dc2666d2633c4787ae1c59a 100644 (file)
@@ -79,7 +79,7 @@ Attributes for all users on a system should be placed in the
 `$(prefix)/etc/gitattributes` file.
 
 Sometimes you would need to override an setting of an attribute
-for a path to `unspecified` state.  This can be done by listing
+for a path to `Unspecified` state.  This can be done by listing
 the name of the attribute prefixed with an exclamation point `!`.
 
 
@@ -868,7 +868,7 @@ If this attribute is not set or has an invalid value, the value of the
 (See linkgit:git-config[1]).
 
 
-USING ATTRIBUTE MACROS
+USING MACRO ATTRIBUTES
 ----------------------
 
 You do not want any end-of-line conversions applied to, nor textual diffs
@@ -879,24 +879,27 @@ produced for, any binary file you track.  You would need to specify e.g.
 ------------
 
 but that may become cumbersome, when you have many attributes.  Using
-attribute macros, you can specify groups of attributes set or unset at
-the same time.  The system knows a built-in attribute macro, `binary`:
+macro attributes, you can define an attribute that, when set, also
+sets or unsets a number of other attributes at the same time.  The
+system knows a built-in macro attribute, `binary`:
 
 ------------
 *.jpg binary
 ------------
 
-which is equivalent to the above.  Note that the attribute macros can only
-be "Set" (see the above example that sets "binary" macro as if it were an
-ordinary attribute --- setting it in turn unsets "text" and "diff").
+Setting the "binary" attribute also unsets the "text" and "diff"
+attributes as above.  Note that macro attributes can only be "Set",
+though setting one might have the effect of setting or unsetting other
+attributes or even returning other attributes to the "Unspecified"
+state.
 
 
-DEFINING ATTRIBUTE MACROS
+DEFINING MACRO ATTRIBUTES
 -------------------------
 
-Custom attribute macros can be defined only in the `.gitattributes` file
-at the toplevel (i.e. not in any subdirectory).  The built-in attribute
-macro "binary" is equivalent to:
+Custom macro attributes can be defined only in the `.gitattributes`
+file at the toplevel (i.e. not in any subdirectory).  The built-in
+macro attribute "binary" is equivalent to:
 
 ------------
 [attr]binary -diff -text
@@ -952,6 +955,9 @@ frotz       unspecified
 ----------------------------------------------------------------
 
 
+SEE ALSO
+--------
+linkgit:git-check-attr[1].
 
 GIT
 ---
index d861ec452f8f115017830422b4b459861f8bd703..aeb0cdc9732593672a4a53a3c177f40f07a86a37 100644 (file)
@@ -7,7 +7,8 @@ gitcvs-migration - git for CVS users
 
 SYNOPSIS
 --------
-git cvsimport *
+[verse]
+'git cvsimport' *
 
 DESCRIPTION
 -----------
index 6af29a4603de57e0040c4bcd819b831dd6649f92..370624c17174ddc67d4d29fd54127096f4f5b070 100644 (file)
@@ -7,6 +7,7 @@ gitdiffcore - Tweaking diff output
 
 SYNOPSIS
 --------
+[verse]
 'git diff' *
 
 DESCRIPTION
index e10ac58cae4f0a825f391a5fa1eab8114aa27a18..a17a3549363ec2813eb0bc8a3087d0bd9dc032f4 100644 (file)
@@ -7,6 +7,7 @@ gitk - The git repository browser
 
 SYNOPSIS
 --------
+[verse]
 'gitk' [<option>...] [<revs>] [--] [<path>...]
 
 DESCRIPTION
diff --git a/Documentation/gitnamespaces.txt b/Documentation/gitnamespaces.txt
new file mode 100644 (file)
index 0000000..c6713cf
--- /dev/null
@@ -0,0 +1,82 @@
+gitnamespaces(7)
+================
+
+NAME
+----
+gitnamespaces - Git namespaces
+
+SYNOPSIS
+--------
+[verse]
+GIT_NAMESPACE=<namespace> 'git upload-pack'
+GIT_NAMESPACE=<namespace> 'git receive-pack'
+
+
+DESCRIPTION
+-----------
+
+Git supports dividing the refs of a single repository into multiple
+namespaces, each of which has its own branches, tags, and HEAD.  Git can
+expose each namespace as an independent repository to pull from and push
+to, while sharing the object store, and exposing all the refs to
+operations such as linkgit:git-gc[1].
+
+Storing multiple repositories as namespaces of a single repository
+avoids storing duplicate copies of the same objects, such as when
+storing multiple branches of the same source.  The alternates mechanism
+provides similar support for avoiding duplicates, but alternates do not
+prevent duplication between new objects added to the repositories
+without ongoing maintenance, while namespaces do.
+
+To specify a namespace, set the `GIT_NAMESPACE` environment variable to
+the namespace.  For each ref namespace, git stores the corresponding
+refs in a directory under `refs/namespaces/`.  For example,
+`GIT_NAMESPACE=foo` will store refs under `refs/namespaces/foo/`.  You
+can also specify namespaces via the `--namespace` option to
+linkgit:git[1].
+
+Note that namespaces which include a `/` will expand to a hierarchy of
+namespaces; for example, `GIT_NAMESPACE=foo/bar` will store refs under
+`refs/namespaces/foo/refs/namespaces/bar/`.  This makes paths in
+`GIT_NAMESPACE` behave hierarchically, so that cloning with
+`GIT_NAMESPACE=foo/bar` produces the same result as cloning with
+`GIT_NAMESPACE=foo` and cloning from that repo with `GIT_NAMESPACE=bar`.  It
+also avoids ambiguity with strange namespace paths such as `foo/refs/heads/`,
+which could otherwise generate directory/file conflicts within the `refs`
+directory.
+
+linkgit:git-upload-pack[1] and linkgit:git-receive-pack[1] rewrite the
+names of refs as specified by `GIT_NAMESPACE`.  git-upload-pack and
+git-receive-pack will ignore all references outside the specified
+namespace.
+
+The smart HTTP server, linkgit:git-http-backend[1], will pass
+GIT_NAMESPACE through to the backend programs; see
+linkgit:git-http-backend[1] for sample configuration to expose
+repository namespaces as repositories.
+
+For a simple local test, you can use linkgit:git-remote-ext[1]:
+
+----------
+git clone ext::'git --namespace=foo %s /tmp/prefixed.git'
+----------
+
+SECURITY
+--------
+
+Anyone with access to any namespace within a repository can potentially
+access objects from any other namespace stored in the same repository.
+You can't directly say "give me object ABCD" if you don't have a ref to
+it, but you can do some other sneaky things like:
+
+. Claiming to push ABCD, at which point the server will optimize out the
+  need for you to actually send it. Now you have a ref to ABCD and can
+  fetch it (claiming not to have it, of course).
+
+. Requesting other refs, claiming that you have ABCD, at which point the
+  server may generate deltas against ABCD.
+
+None of this causes a problem if you only host public repositories, or
+if everyone who may read one namespace may also read everything in every
+other namespace (for instance, if everyone in an organization has read
+permission to every repository).
index eb3d040783e8202f502d05e8b4dc4e3b39736779..5c891f1169b35e53bccc19f0423b5b7e98db720b 100644 (file)
@@ -23,32 +23,25 @@ objects::
        Object store associated with this repository.  Usually
        an object store is self sufficient (i.e. all the objects
        that are referred to by an object found in it are also
-       found in it), but there are couple of ways to violate
-       it.
+       found in it), but there are a few ways to violate it.
 +
-. You could populate the repository by running a commit walker
-without `-a` option.  Depending on which options are given, you
-could have only commit objects without associated blobs and
-trees this way, for example.  A repository with this kind of
-incomplete object store is not suitable to be published to the
-outside world but sometimes useful for private repository.
-. You also could have an incomplete but locally usable repository
-by cloning shallowly.  See linkgit:git-clone[1].
-. You can be using `objects/info/alternates` mechanism, or
-`$GIT_ALTERNATE_OBJECT_DIRECTORIES` mechanism to 'borrow'
+. You could have an incomplete but locally usable repository
+by creating a shallow clone.  See linkgit:git-clone[1].
+. You could be using the `objects/info/alternates` or
+`$GIT_ALTERNATE_OBJECT_DIRECTORIES` mechanisms to 'borrow'
 objects from other object stores.  A repository with this kind
 of incomplete object store is not suitable to be published for
 use with dumb transports but otherwise is OK as long as
-`objects/info/alternates` points at the right object stores
-it borrows from.
+`objects/info/alternates` points at the object stores it
+borrows from.
 
 objects/[0-9a-f][0-9a-f]::
-       Traditionally, each object is stored in its own file.
-       They are split into 256 subdirectories using the first
-       two letters from its object name to keep the number of
-       directory entries `objects` directory itself needs to
-       hold.  Objects found here are often called 'unpacked'
-       (or 'loose') objects.
+       A newly created object is stored in its own file.
+       The objects are splayed over 256 subdirectories using
+       the first two characters of the sha1 object name to
+       keep the number of directory entries in `objects`
+       itself to a manageable number. Objects found
+       here are often called 'unpacked' (or 'loose') objects.
 
 objects/pack::
        Packs (files that store many object in compressed form,
@@ -85,7 +78,7 @@ objects/info/http-alternates::
 
 refs::
        References are stored in subdirectories of this
-       directory.  The 'git prune' command knows to keep
+       directory.  The 'git prune' command knows to preserve
        objects reachable from refs found in this directory and
        its subdirectories.
 
@@ -119,16 +112,17 @@ HEAD::
 +
 HEAD can also record a specific commit directly, instead of
 being a symref to point at the current branch.  Such a state
-is often called 'detached HEAD', and almost all commands work
-identically as normal.  See linkgit:git-checkout[1] for
-details.
+is often called 'detached HEAD.'  See linkgit:git-checkout[1]
+for details.
 
 branches::
        A slightly deprecated way to store shorthands to be used
-       to specify URL to 'git fetch', 'git pull' and 'git push'
-       commands is to store a file in `branches/<name>` and
-       give 'name' to these commands in place of 'repository'
-       argument.
+       to specify a URL to 'git fetch', 'git pull' and 'git push'.
+       A file can be stored as `branches/<name>` and then
+       'name' can be given to these commands in place of
+       'repository' argument.  See the REMOTES section in
+       linkgit:git-fetch[1] for details.  This mechanism is legacy
+       and not likely to be found in modern repositories.
 
 hooks::
        Hooks are customization scripts used by various git
@@ -173,9 +167,11 @@ info/exclude::
        at it.  See also: linkgit:gitignore[5].
 
 remotes::
-       Stores shorthands to be used to give URL and default
-       refnames to interact with remote repository to
-       'git fetch', 'git pull' and 'git push' commands.
+       Stores shorthands for URL and default refnames for use
+       when interacting with remote repositories via 'git fetch',
+       'git pull' and 'git push' commands.  See the REMOTES section
+       in linkgit:git-fetch[1] for details.  This mechanism is legacy
+       and not likely to be found in modern repositories.
 
 logs::
        Records of changes made to refs are stored in this
index 7fe5848d1f7ef77748448c03c4b2b2b33d82f03c..f1e4422acc4ddba515da5617759f818b52cb151d 100644 (file)
@@ -7,6 +7,7 @@ gittutorial-2 - A tutorial introduction to git: part two
 
 SYNOPSIS
 --------
+[verse]
 git *
 
 DESCRIPTION
index 0982f74ef6212577afb74d86079472662e2bffba..dee050567e65301066629c566613b84c6c065169 100644 (file)
@@ -7,6 +7,7 @@ gittutorial - A tutorial introduction to git (for version 1.5.1 or newer)
 
 SYNOPSIS
 --------
+[verse]
 git *
 
 DESCRIPTION
index 1ef55fffcf6815a12a1f9845809592104e208092..5e4f362ff8073bc5fc5f651f5219834d6173517b 100644 (file)
@@ -7,6 +7,7 @@ gitworkflows - An overview of recommended workflows with git
 
 SYNOPSIS
 --------
+[verse]
 git *
 
 
index 8f62d1abeeb9b80fd36aa8ea12120c640a98414e..3595b586bc35d865a08ab538a1908cb2abe8e1a8 100644 (file)
@@ -161,8 +161,8 @@ to point at the new commit.
 
 [[def_head]]head::
        A <<def_ref,named reference>> to the <<def_commit,commit>> at the tip of a
-       <<def_branch,branch>>.  Heads are stored in
-       `$GIT_DIR/refs/heads/`, except when using packed refs. (See
+       <<def_branch,branch>>.  Heads are stored in a file in
+       `$GIT_DIR/refs/heads/` directory, except when using packed refs. (See
        linkgit:git-pack-refs[1].)
 
 [[def_HEAD]]HEAD::
@@ -170,8 +170,8 @@ to point at the new commit.
        working tree>> is normally derived from the state of the tree
        referred to by HEAD.  HEAD is a reference to one of the
        <<def_head,heads>> in your repository, except when using a
-       <<def_detached_HEAD,detached HEAD>>, in which case it may
-       reference an arbitrary commit.
+       <<def_detached_HEAD,detached HEAD>>, in which case it directly
+       references an arbitrary commit.
 
 [[def_head_ref]]head ref::
        A synonym for <<def_head,head>>.
@@ -382,8 +382,9 @@ should not be combined with other pathspec.
 
 [[def_ref]]ref::
        A 40-byte hex representation of a <<def_SHA1,SHA1>> or a name that
-       denotes a particular <<def_object,object>>. These may be stored in
-       `$GIT_DIR/refs/`.
+       denotes a particular <<def_object,object>>. They may be stored in
+       a file under `$GIT_DIR/refs/` directory, or
+       in the `$GIT_DIR/packed-refs` file.
 
 [[def_reflog]]reflog::
        A reflog shows the local "history" of a ref.  In other words,
@@ -459,14 +460,14 @@ should not be combined with other pathspec.
        command.
 
 [[def_tag]]tag::
-       A <<def_ref,ref>> pointing to a <<def_tag_object,tag>> or
-       <<def_commit_object,commit object>>. In contrast to a <<def_head,head>>,
-       a tag is not changed by a <<def_commit,commit>>. Tags (not
-       <<def_tag_object,tag objects>>) are stored in `$GIT_DIR/refs/tags/`. A
-       git tag has nothing to do with a Lisp tag (which would be
-       called an <<def_object_type,object type>> in git's context). A
-       tag is most typically used to mark a particular point in the
-       commit ancestry <<def_chain,chain>>.
+       A <<def_ref,ref>> under `refs/tags/` namespace that points to an
+       object of an arbitrary type (typically a tag points to either a
+       <<def_tag_object,tag>> or a <<def_commit_object,commit object>>).
+       In contrast to a <<def_head,head>>, a tag is not updated by
+       the `commit` command. A git tag has nothing to do with a Lisp
+       tag (which would be called an <<def_object_type,object type>>
+       in git's context). A tag is most typically used to mark a particular
+       point in the commit ancestry <<def_chain,chain>>.
 
 [[def_tag_object]]tag object::
        An <<def_object,object>> containing a <<def_ref,ref>> pointing to
index d527b307707c676e82a08f18cb9fdd7d3abcb228..8823a37067811037487a9f0496736bfafdc8b7ad 100644 (file)
@@ -176,7 +176,7 @@ by doing the following:
  - Update "What's cooking" message to review the updates to
    existing topics, newly added topics and graduated topics.
 
-   This step is helped with Meta/UWC script (where Meta/ contains
+   This step is helped with Meta/cook script (where Meta/ contains
    a checkout of the 'todo' branch).
 
  - Merge topics to 'next'.  For each branch whose tip is not
@@ -197,10 +197,9 @@ by doing the following:
 
    - Nothing is next-worthy; do not do anything.
 
- - Rebase topics that do not have any commit in next yet.  This
-   step is optional but sometimes is worth doing when an old
-   series that is not in next can take advantage of low-level
-   framework change that is merged to 'master' already.
+ - [** OBSOLETE **] Optionally rebase topics that do not have any commit
+   in next yet, when they can take advantage of low-level framework
+   change that is merged to 'master' already.
 
      $ git rebase master ai/topic
 
@@ -209,7 +208,7 @@ by doing the following:
    pre-rebase hook to make sure that topics that are already in
    'next' are not rebased beyond the merged commit.
 
- - Rebuild "pu" to merge the tips of topics not in 'next'.
+ - [** OBSOLETE **] Rebuild "pu" to merge the tips of topics not in 'next'.
 
      $ git checkout pu
      $ git reset --hard next
@@ -241,7 +240,7 @@ by doing the following:
 
  - Fetch html and man branches back from k.org, and push four
    integration branches and the two documentation branches to
-   repo.or.cz
+   repo.or.cz and other mirrors.
 
 
 Some observations to be made.
index 7e7ba68781f98b55f4189fe0fb4dd6e96c47c0af..39e62072691d408519ff377cb6e91e8d95175ec4 100644 (file)
@@ -272,7 +272,7 @@ Default mode::
 
 --full-history::
 
-       As the default mode but does not prune some history.
+       Same as the default mode, but does not prune some history.
 
 --dense::
 
@@ -313,7 +313,7 @@ that you are filtering for a file `foo` in this commit graph:
         \   /   /   /   /
          `-------------'
 -----------------------------------------------------------------------
-The horizontal line of history A--P is taken to be the first parent of
+The horizontal line of history A---P is taken to be the first parent of
 each merge.  The commits are:
 
 * `I` is the initial commit, in which `foo` exists with contents
index 5cb2b0590abb1fc4e4685097ba3cdd3d0683a95f..b0cafe87bee046dc9b2d4e61e0d057e787a6f1f6 100644 (file)
@@ -49,6 +49,8 @@ Additionally, if `foo` is a new command, there are 3 more things to do:
 
 . Add an entry for `git-foo` to `command-list.txt`.
 
+. Add an entry for `/git-foo` to `.gitignore`.
+
 
 How a built-in is called
 ------------------------
index 9d97eaa9dee99eef7e66072c4c51cfff3000bba3..ce363b6305ec117810d66308b0066814871f54e1 100644 (file)
@@ -11,27 +11,15 @@ Data Structure
 `struct git_attr`::
 
        An attribute is an opaque object that is identified by its name.
-       Pass the name and its length to `git_attr()` function to obtain
-       the object of this type.  The internal representation of this
-       structure is of no interest to the calling programs.
+       Pass the name to `git_attr()` function to obtain the object of
+       this type.  The internal representation of this structure is
+       of no interest to the calling programs.  The name of the
+       attribute can be retrieved by calling `git_attr_name()`.
 
 `struct git_attr_check`::
 
        This structure represents a set of attributes to check in a call
-       to `git_checkattr()` function, and receives the results.
-
-
-Calling Sequence
-----------------
-
-* Prepare an array of `struct git_attr_check` to define the list of
-  attributes you would want to check.  To populate this array, you would
-  need to define necessary attributes by calling `git_attr()` function.
-
-* Call git_checkattr() to check the attributes for the path.
-
-* Inspect `git_attr_check` structure to see how each of the attribute in
-  the array is defined for the path.
+       to `git_check_attr()` function, and receives the results.
 
 
 Attribute Values
@@ -57,6 +45,19 @@ If none of the above returns true, `.value` member points at a string
 value of the attribute for the path.
 
 
+Querying Specific Attributes
+----------------------------
+
+* Prepare an array of `struct git_attr_check` to define the list of
+  attributes you would want to check.  To populate this array, you would
+  need to define necessary attributes by calling `git_attr()` function.
+
+* Call `git_check_attr()` to check the attributes for the path.
+
+* Inspect `git_attr_check` structure to see how each of the attribute in
+  the array is defined for the path.
+
+
 Example
 -------
 
@@ -72,18 +73,18 @@ static void setup_check(void)
 {
        if (check[0].attr)
                return; /* already done */
-       check[0].attr = git_attr("crlf", 4);
-       check[1].attr = git_attr("ident", 5);
+       check[0].attr = git_attr("crlf");
+       check[1].attr = git_attr("ident");
 }
 ------------
 
-. Call `git_checkattr()` with the prepared array of `struct git_attr_check`:
+. Call `git_check_attr()` with the prepared array of `struct git_attr_check`:
 
 ------------
        const char *path;
 
        setup_check();
-       git_checkattr(path, ARRAY_SIZE(check), check);
+       git_check_attr(path, ARRAY_SIZE(check), check);
 ------------
 
 . Act on `.value` member of the result, left in `check[]`:
@@ -108,4 +109,20 @@ static void setup_check(void)
        }
 ------------
 
-(JC)
+
+Querying All Attributes
+-----------------------
+
+To get the values of all attributes associated with a file:
+
+* Call `git_all_attrs()`, which returns an array of `git_attr_check`
+  structures.
+
+* Iterate over the `git_attr_check` array to examine the attribute
+  names and values.  The name of the attribute described by a
+  `git_attr_check` object can be retrieved via
+  `git_attr_name(check[i].attr)`.  (Please note that no items will be
+  returned for unset attributes, so `ATTR_UNSET()` will return false
+  for all returned `git_array_check` objects.)
+
+* Free the `git_array_check` array.
diff --git a/Documentation/technical/api-ref-iteration.txt b/Documentation/technical/api-ref-iteration.txt
new file mode 100644 (file)
index 0000000..dbbea95
--- /dev/null
@@ -0,0 +1,81 @@
+ref iteration API
+=================
+
+
+Iteration of refs is done by using an iterate function which will call a
+callback function for every ref. The callback function has this
+signature:
+
+       int handle_one_ref(const char *refname, const unsigned char *sha1,
+                          int flags, void *cb_data);
+
+There are different kinds of iterate functions which all take a
+callback of this type. The callback is then called for each found ref
+until the callback returns nonzero. The returned value is then also
+returned by the iterate function.
+
+Iteration functions
+-------------------
+
+* `head_ref()` just iterates the head ref.
+
+* `for_each_ref()` iterates all refs.
+
+* `for_each_ref_in()` iterates all refs which have a defined prefix and
+  strips that prefix from the passed variable refname.
+
+* `for_each_tag_ref()`, `for_each_branch_ref()`, `for_each_remote_ref()`,
+  `for_each_replace_ref()` iterate refs from the respective area.
+
+* `for_each_glob_ref()` iterates all refs that match the specified glob
+  pattern.
+
+* `for_each_glob_ref_in()` the previous and `for_each_ref_in()` combined.
+
+* `head_ref_submodule()`, `for_each_ref_submodule()`,
+  `for_each_ref_in_submodule()`, `for_each_tag_ref_submodule()`,
+  `for_each_branch_ref_submodule()`, `for_each_remote_ref_submodule()`
+  do the same as the functions descibed above but for a specified
+  submodule.
+
+* `for_each_rawref()` can be used to learn about broken ref and symref.
+
+* `for_each_reflog()` iterates each reflog file.
+
+Submodules
+----------
+
+If you want to iterate the refs of a submodule you first need to add the
+submodules object database. You can do this by a code-snippet like
+this:
+
+       const char *path = "path/to/submodule"
+       if (!add_submodule_odb(path))
+               die("Error submodule '%s' not populated.", path);
+
+`add_submodule_odb()` will return an non-zero value on success. If you
+do not do this you will get an error for each ref that it does not point
+to a valid object.
+
+Note: As a side-effect of this you can not safely assume that all
+objects you lookup are available in superproject. All submodule objects
+will be available the same way as the superprojects objects.
+
+Example:
+--------
+
+----
+static int handle_remote_ref(const char *refname,
+               const unsigned char *sha1, int flags, void *cb_data)
+{
+       struct strbuf *output = cb_data;
+       strbuf_addf(output, "%s\n", refname);
+       return 0;
+}
+
+...
+
+       struct strbuf output = STRBUF_INIT;
+       for_each_remote_ref(handle_remote_ref, &output);
+       printf("%s", output.buf);
+----
index 3f575bdcff3a6b38304710b22371784c2e7443c9..ce24eb96f5efdee579f8600323731368fee4048b 100644 (file)
@@ -29,6 +29,9 @@ member (you need this if you add things later) and you should set the
 
 . Can sort an unsorted list using `sort_string_list`.
 
+. Can remove individual items of an unsorted list using
+  `unsorted_string_list_delete_item`.
+
 . Finally it should free the list using `string_list_clear`.
 
 Example:
@@ -112,6 +115,13 @@ write `string_list_insert(...)->util = ...;`.
 The above two functions need to look through all items, as opposed to their
 counterpart for sorted lists, which performs a binary search.
 
+`unsorted_string_list_delete_item`::
+
+       Remove an item from a string_list. The `string` pointer of the items
+       will be freed in case the `strdup_strings` member of the string_list
+       is set. The third parameter controls if the `util` pointer of the
+       items should be freed or not.
+
 Data structures
 ---------------
 
index 7b233ca19676c2f89b1fb0231d7085e70c7eaaf6..8930b3fabceebfc7e03ddabdf5f7bfb228babf6b 100644 (file)
@@ -147,8 +147,9 @@ GIT index format
   - 160-bit object name for the object that would result from writing
     this span of index as a tree.
 
-  An entry can be in an invalidated state and is represented by having -1
-  in the entry_count field.
+  An entry can be in an invalidated state and is represented by having
+  -1 in the entry_count field. In this case, there is no object name
+  and the next entry starts immediately after the newline.
 
   The entries are written out in the top-down, depth-first order.  The
   first entry represents the root level of the repository, followed by the
index 369f91d3b949b23682c4deda8234f13513f15732..a7004c63e7ff5a4fe237267121a5b7c76a4c5496 100644 (file)
@@ -179,34 +179,36 @@ and descriptions.
 
 Packfile Negotiation
 --------------------
-After reference and capabilities discovery, the client can decide
-to terminate the connection by sending a flush-pkt, telling the
-server it can now gracefully terminate (as happens with the ls-remote
-command) or it can enter the negotiation phase, where the client and
-server determine what the minimal packfile necessary for transport is.
-
-Once the client has the initial list of references that the server
-has, as well as the list of capabilities, it will begin telling the
-server what objects it wants and what objects it has, so the server
-can make a packfile that only contains the objects that the client needs.
-The client will also send a list of the capabilities it wants to be in
-effect, out of what the server said it could do with the first 'want' line.
+After reference and capabilities discovery, the client can decide to
+terminate the connection by sending a flush-pkt, telling the server it can
+now gracefully terminate, and disconnect, when it does not need any pack
+data. This can happen with the ls-remote command, and also can happen when
+the client already is up-to-date.
+
+Otherwise, it enters the negotiation phase, where the client and
+server determine what the minimal packfile necessary for transport is,
+by telling the server what objects it wants, its shallow objects
+(if any), and the maximum commit depth it wants (if any).  The client
+will also send a list of the capabilities it wants to be in effect,
+out of what the server said it could do with the first 'want' line.
 
 ----
   upload-request    =  want-list
-                      have-list
-                      compute-end
+                      *shallow-line
+                      *1depth-request
+                      flush-pkt
 
   want-list         =  first-want
                       *additional-want
-                      flush-pkt
+
+  shallow-line      =  PKT_LINE("shallow" SP obj-id)
+
+  depth-request     =  PKT_LINE("deepen" SP depth)
 
   first-want        =  PKT-LINE("want" SP obj-id SP capability-list LF)
   additional-want   =  PKT-LINE("want" SP obj-id LF)
 
-  have-list         =  *have-line
-  have-line         =  PKT-LINE("have" SP obj-id LF)
-  compute-end       =  flush-pkt / PKT-LINE("done")
+  depth             =  1*DIGIT
 ----
 
 Clients MUST send all the obj-ids it wants from the reference
@@ -215,21 +217,64 @@ discovery phase as 'want' lines. Clients MUST send at least one
 obj-id in a 'want' command which did not appear in the response
 obtained through ref discovery.
 
-If client is requesting a shallow clone, it will now send a 'deepen'
-line with the depth it is requesting.
+The client MUST write all obj-ids which it only has shallow copies
+of (meaning that it does not have the parents of a commit) as
+'shallow' lines so that the server is aware of the limitations of
+the client's history. Clients MUST NOT mention an obj-id which
+it does not know exists on the server.
+
+The client now sends the maximum commit history depth it wants for
+this transaction, which is the number of commits it wants from the
+tip of the history, if any, as a 'deepen' line.  A depth of 0 is the
+same as not making a depth request. The client does not want to receive
+any commits beyond this depth, nor objects needed only to complete
+those commits. Commits whose parents are not received as a result are
+defined as shallow and marked as such in the server. This information
+is sent back to the client in the next step.
+
+Once all the 'want's and 'shallow's (and optional 'deepen') are
+transferred, clients MUST send a flush-pkt, to tell the server side
+that it is done sending the list.
+
+Otherwise, if the client sent a positive depth request, the server
+will determine which commits will and will not be shallow and
+send this information to the client. If the client did not request
+a positive depth, this step is skipped.
 
-Once all the "want"s (and optional 'deepen') are transferred,
-clients MUST send a flush-pkt. If the client has all the references
-on the server, client flushes and disconnects.
+----
+  shallow-update   =  *shallow-line
+                     *unshallow-line
+                     flush-pkt
 
-TODO: shallow/unshallow response and document the deepen command in the ABNF.
+  shallow-line     =  PKT-LINE("shallow" SP obj-id)
+
+  unshallow-line   =  PKT-LINE("unshallow" SP obj-id)
+----
+
+If the client has requested a positive depth, the server will compute
+the set of commits which are no deeper than the desired depth, starting
+at the client's wants. The server writes 'shallow' lines for each
+commit whose parents will not be sent as a result. The server writes
+an 'unshallow' line for each commit which the client has indicated is
+shallow, but is no longer shallow at the currently requested depth
+(that is, its parents will now be sent). The server MUST NOT mark
+as unshallow anything which the client has not indicated was shallow.
 
 Now the client will send a list of the obj-ids it has using 'have'
-lines.  In multi_ack mode, the canonical implementation will send up
-to 32 of these at a time, then will send a flush-pkt.  The canonical
-implementation will skip ahead and send the next 32 immediately,
-so that there is always a block of 32 "in-flight on the wire" at a
-time.
+lines, so the server can make a packfile that only contains the objects
+that the client needs. In multi_ack mode, the canonical implementation
+will send up to 32 of these at a time, then will send a flush-pkt. The
+canonical implementation will skip ahead and send the next 32 immediately,
+so that there is always a block of 32 "in-flight on the wire" at a time.
+
+----
+  upload-haves      =  have-list
+                      compute-end
+
+  have-list         =  *have-line
+  have-line         =  PKT-LINE("have" SP obj-id LF)
+  compute-end       =  flush-pkt / PKT-LINE("done")
+----
 
 If the server reads 'have' lines, it then will respond by ACKing any
 of the obj-ids the client said it had that the server also has. The
index f04e4b138d344155b767a0538321dda34552906e..dbdb4c137f162cc807ee57adcc52a10e067029c9 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.7.6
+DEF_VER=v1.7.7.1
 
 LF='
 '
diff --git a/INSTALL b/INSTALL
index 16e45f114f18d5d6964ab9fa39eee617dfa59bfc..bbb9d4dc9ab37a242d8accb86fa8a0d35d677ab8 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -25,6 +25,19 @@ set up install paths (via config.mak.autogen), so you can write instead
        $ make all doc ;# as yourself
        # make install install-doc install-html;# as root
 
+If you're willing to trade off (much) longer build time for a later
+faster git you can also do a profile feedback build with
+
+       $ make profile-all
+       # make prefix=... install
+
+This will run the complete test suite as training workload and then
+rebuild git with the generated profile feedback. This results in a git
+which is a few percent faster on CPU intensive workloads.  This
+may be a good tradeoff for distribution packagers.
+
+Note that the profile feedback build stage currently generates
+a lot of additional compiler warnings.
 
 Issues of note:
 
index ec7d06c00cf249be01abbf47bc027e6e35e1160b..71b853ffaf740aaba2415a18377326f37c27d584 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -30,15 +30,15 @@ all::
 # Define LIBPCREDIR=/foo/bar if your libpcre header and library files are in
 # /foo/bar/include and /foo/bar/lib directories.
 #
-# Define NO_CURL if you do not have libcurl installed.  git-http-pull and
+# Define NO_CURL if you do not have libcurl installed.  git-http-fetch and
 # git-http-push are not built, and you cannot use http:// and https://
-# transports.
+# transports (neither smart nor dumb).
 #
 # Define CURLDIR=/foo/bar if your curl header and library files are in
 # /foo/bar/include and /foo/bar/lib directories.
 #
 # Define NO_EXPAT if you do not have expat installed.  git-http-push is
-# not built, and you cannot push using http:// and https:// transports.
+# not built, and you cannot push using http:// and https:// transports (dumb).
 #
 # Define EXPATDIR=/foo/bar if your expat header and library files are in
 # /foo/bar/include and /foo/bar/lib directories.
@@ -115,6 +115,10 @@ all::
 #
 # Define NEEDS_SSL_WITH_CRYPTO if you need -lssl when using -lcrypto (Darwin).
 #
+# Define NEEDS_SSL_WITH_CURL if you need -lssl with -lcurl (Minix).
+#
+# Define NEEDS_IDN_WITH_CURL if you need -lidn when using -lcurl (Minix).
+#
 # Define NEEDS_LIBICONV if linking with libc is not enough (Darwin).
 #
 # Define NEEDS_SOCKET if linking with libc is not enough (SunOS,
@@ -153,6 +157,9 @@ all::
 # that tells runtime paths to dynamic libraries;
 # "-Wl,-rpath=/path/lib" is used instead.
 #
+# Define NO_NORETURN if using buggy versions of gcc 4.6+ and profile feedback,
+# as the compiler can crash (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=49299)
+#
 # Define USE_NSEC below if you want git to care about sub-second file mtimes
 # and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and
 # it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely
@@ -295,6 +302,7 @@ bindir = $(prefix)/$(bindir_relative)
 mandir = share/man
 infodir = share/info
 gitexecdir = libexec/git-core
+mergetoolsdir = $(gitexecdir)/mergetools
 sharedir = $(prefix)/share
 gitwebdir = $(sharedir)/gitweb
 template_dir = share/git-core/templates
@@ -508,6 +516,7 @@ LIB_H += commit.h
 LIB_H += compat/bswap.h
 LIB_H += compat/cygwin.h
 LIB_H += compat/mingw.h
+LIB_H += compat/obstack.h
 LIB_H += compat/win32/pthread.h
 LIB_H += compat/win32/syslog.h
 LIB_H += compat/win32/sys/poll.h
@@ -526,6 +535,7 @@ LIB_H += graph.h
 LIB_H += grep.h
 LIB_H += hash.h
 LIB_H += help.h
+LIB_H += kwset.h
 LIB_H += levenshtein.h
 LIB_H += list-objects.h
 LIB_H += ll-merge.h
@@ -557,6 +567,7 @@ LIB_H += sha1-lookup.h
 LIB_H += sideband.h
 LIB_H += sigchain.h
 LIB_H += strbuf.h
+LIB_H += streaming.h
 LIB_H += string-list.h
 LIB_H += submodule.h
 LIB_H += tag.h
@@ -587,6 +598,7 @@ LIB_OBJS += cache-tree.o
 LIB_OBJS += color.o
 LIB_OBJS += combine-diff.o
 LIB_OBJS += commit.o
+LIB_OBJS += compat/obstack.o
 LIB_OBJS += config.o
 LIB_OBJS += connect.o
 LIB_OBJS += convert.o
@@ -616,6 +628,7 @@ LIB_OBJS += hash.o
 LIB_OBJS += help.o
 LIB_OBJS += hex.o
 LIB_OBJS += ident.o
+LIB_OBJS += kwset.o
 LIB_OBJS += levenshtein.o
 LIB_OBJS += list-objects.o
 LIB_OBJS += ll-merge.o
@@ -636,6 +649,7 @@ LIB_OBJS += pack-revindex.o
 LIB_OBJS += pack-write.o
 LIB_OBJS += pager.o
 LIB_OBJS += parse-options.o
+LIB_OBJS += parse-options-cb.o
 LIB_OBJS += patch-delta.o
 LIB_OBJS += patch-ids.o
 LIB_OBJS += path.o
@@ -664,6 +678,7 @@ LIB_OBJS += shallow.o
 LIB_OBJS += sideband.o
 LIB_OBJS += sigchain.o
 LIB_OBJS += strbuf.o
+LIB_OBJS += streaming.o
 LIB_OBJS += string-list.o
 LIB_OBJS += submodule.o
 LIB_OBJS += symlinks.o
@@ -807,6 +822,7 @@ ifeq ($(uname_S),GNU/kFreeBSD)
        NO_STRLCPY = YesPlease
        NO_MKSTEMPS = YesPlease
        HAVE_PATHS_H = YesPlease
+       DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease
 endif
 ifeq ($(uname_S),UnixWare)
        CC = cc
@@ -1126,8 +1142,6 @@ 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
@@ -1138,12 +1152,30 @@ ifeq ($(uname_S),Interix)
        ifeq ($(uname_R),3.5)
                NO_INET_NTOP = YesPlease
                NO_INET_PTON = YesPlease
+               NO_SOCKADDR_STORAGE = YesPlease
+               NO_FNMATCH_CASEFOLD = YesPlease
        endif
        ifeq ($(uname_R),5.2)
                NO_INET_NTOP = YesPlease
                NO_INET_PTON = YesPlease
+               NO_SOCKADDR_STORAGE = YesPlease
+               NO_FNMATCH_CASEFOLD = YesPlease
        endif
 endif
+ifeq ($(uname_S),Minix)
+       NO_IPV6 = YesPlease
+       NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
+       NO_NSEC = YesPlease
+       NEEDS_LIBGEN =
+       NEEDS_CRYPTO_WITH_SSL = YesPlease
+       NEEDS_IDN_WITH_CURL = YesPlease
+       NEEDS_SSL_WITH_CURL = YesPlease
+       NEEDS_RESOLV =
+       NO_HSTRERROR = YesPlease
+       NO_MMAP = YesPlease
+       NO_CURL =
+       NO_EXPAT =
+endif
 ifneq (,$(findstring MINGW,$(uname_S)))
        pathsep = ;
        NO_PREAD = YesPlease
@@ -1288,6 +1320,16 @@ else
        else
                CURL_LIBCURL = -lcurl
        endif
+       ifdef NEEDS_SSL_WITH_CURL
+               CURL_LIBCURL += -lssl
+               ifdef NEEDS_CRYPTO_WITH_SSL
+                       CURL_LIBCURL += -lcrypto
+               endif
+       endif
+       ifdef NEEDS_IDN_WITH_CURL
+               CURL_LIBCURL += -lidn
+       endif
+
        REMOTE_CURL_PRIMARY = git-remote-http$X
        REMOTE_CURL_ALIASES = git-remote-https$X git-remote-ftp$X git-remote-ftps$X
        REMOTE_CURL_NAMES = $(REMOTE_CURL_PRIMARY) $(REMOTE_CURL_ALIASES)
@@ -1324,7 +1366,7 @@ ifndef NO_OPENSSL
                OPENSSL_LINK =
        endif
        ifdef NEEDS_CRYPTO_WITH_SSL
-               OPENSSL_LINK += -lcrypto
+               OPENSSL_LIBSSL += -lcrypto
        endif
 else
        BASIC_CFLAGS += -DNO_OPENSSL
@@ -1376,6 +1418,9 @@ endif
 ifdef USE_ST_TIMESPEC
        BASIC_CFLAGS += -DUSE_ST_TIMESPEC
 endif
+ifdef NO_NORETURN
+       BASIC_CFLAGS += -DNO_NORETURN
+endif
 ifdef NO_NSEC
        BASIC_CFLAGS += -DNO_NSEC
 endif
@@ -1708,7 +1753,7 @@ git.sp git.s git.o: EXTRA_CPPFLAGS = -DGIT_VERSION='"$(GIT_VERSION)"' \
        '-DGIT_MAN_PATH="$(mandir_SQ)"' \
        '-DGIT_INFO_PATH="$(infodir_SQ)"'
 
-git$X: git.o $(BUILTIN_OBJS) $(GITLIBS)
+git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \
                $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS)
 
@@ -1840,7 +1885,7 @@ ifndef NO_CURL
        GIT_OBJS += http.o http-walker.o remote-curl.o
 endif
 XDIFF_OBJS = xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \
-       xdiff/xmerge.o xdiff/xpatience.o
+       xdiff/xmerge.o xdiff/xpatience.o xdiff/xhistogram.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 \
@@ -2006,17 +2051,17 @@ compat/nedmalloc/nedmalloc.sp compat/nedmalloc/nedmalloc.o: EXTRA_CPPFLAGS = \
        -DNDEBUG -DOVERRIDE_STRDUP -DREPLACE_SYSTEM_ALLOCATOR
 endif
 
-git-%$X: %.o $(GITLIBS)
+git-%$X: %.o GIT-LDFLAGS $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
 
-git-imap-send$X: imap-send.o $(GITLIBS)
+git-imap-send$X: imap-send.o GIT-LDFLAGS $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
                $(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL) $(LIB_4_CRYPTO)
 
-git-http-fetch$X: revision.o http.o http-walker.o http-fetch.o $(GITLIBS)
+git-http-fetch$X: revision.o http.o http-walker.o http-fetch.o GIT-LDFLAGS $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
                $(LIBS) $(CURL_LIBCURL)
-git-http-push$X: revision.o http.o http-push.o $(GITLIBS)
+git-http-push$X: revision.o http.o http-push.o GIT-LDFLAGS $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
                $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
 
@@ -2026,7 +2071,7 @@ $(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY)
        ln -s $< $@ 2>/dev/null || \
        cp $< $@
 
-$(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o $(GITLIBS)
+$(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
                $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
 
@@ -2096,6 +2141,15 @@ GIT-CFLAGS: FORCE
                echo "$$FLAGS" >GIT-CFLAGS; \
             fi
 
+TRACK_LDFLAGS = $(subst ','\'',$(ALL_LDFLAGS))
+
+GIT-LDFLAGS: FORCE
+       @FLAGS='$(TRACK_LDFLAGS)'; \
+           if test x"$$FLAGS" != x"`cat GIT-LDFLAGS 2>/dev/null`" ; then \
+               echo 1>&2 "    * new link flags"; \
+               echo "$$FLAGS" >GIT-LDFLAGS; \
+            fi
+
 # We need to apply sq twice, once to protect from the shell
 # that runs GIT-BUILD-OPTIONS, and then again to protect it
 # and the first level quoting from the shell that runs "echo".
@@ -2159,7 +2213,7 @@ test-delta$X: diff-delta.o patch-delta.o
 
 test-line-buffer$X: vcs-svn/lib.a
 
-test-parse-options$X: parse-options.o
+test-parse-options$X: parse-options.o parse-options-cb.o
 
 test-string-pool$X: vcs-svn/lib.a
 
@@ -2167,7 +2221,7 @@ test-svn-fe$X: vcs-svn/lib.a
 
 .PRECIOUS: $(TEST_OBJS)
 
-test-%$X: test-%.o $(GITLIBS)
+test-%$X: test-%.o GIT-LDFLAGS $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)
 
 check-sha1:: test-sha1$X
@@ -2212,6 +2266,13 @@ endif
 gitexec_instdir_SQ = $(subst ','\'',$(gitexec_instdir))
 export gitexec_instdir
 
+ifneq ($(filter /%,$(firstword $(mergetoolsdir))),)
+mergetools_instdir = $(mergetoolsdir)
+else
+mergetools_instdir = $(prefix)/$(mergetoolsdir)
+endif
+mergetools_instdir_SQ = $(subst ','\'',$(mergetools_instdir))
+
 install_bindir_programs := $(patsubst %,%$X,$(BINDIR_PROGRAMS_NEED_X)) $(BINDIR_PROGRAMS_NO_X)
 
 install: all
@@ -2221,6 +2282,9 @@ install: all
        $(INSTALL) -m 644 $(SCRIPT_LIB) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
        $(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)'
        $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
+       $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(mergetools_instdir_SQ)'
+       (cd mergetools && $(TAR) cf - .) | \
+       (cd '$(DESTDIR_SQ)$(mergetools_instdir_SQ)' && umask 022 && $(TAR) xof -)
 ifndef NO_PERL
        $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
        $(MAKE) -C gitweb install
@@ -2377,7 +2441,7 @@ ifndef NO_TCLTK
        $(MAKE) -C gitk-git clean
        $(MAKE) -C git-gui clean
 endif
-       $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-GUI-VARS GIT-BUILD-OPTIONS
+       $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-LDFLAGS GIT-GUI-VARS GIT-BUILD-OPTIONS
 
 .PHONY: all install clean strip
 .PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell
@@ -2488,3 +2552,19 @@ cover_db: coverage-report
 
 cover_db_html: cover_db
        cover -report html -outputdir cover_db_html cover_db
+
+### profile feedback build
+#
+.PHONY: profile-all profile-clean
+
+PROFILE_GEN_CFLAGS := $(CFLAGS) -fprofile-generate -DNO_NORETURN=1
+PROFILE_USE_CFLAGS := $(CFLAGS) -fprofile-use -fprofile-correction -DNO_NORETURN=1
+
+profile-clean:
+       $(RM) $(addsuffix *.gcda,$(object_dirs))
+       $(RM) $(addsuffix *.gcno,$(object_dirs))
+
+profile-all: profile-clean
+       $(MAKE) CFLAGS="$(PROFILE_GEN_CFLAGS)" all
+       $(MAKE) CFLAGS="$(PROFILE_GEN_CFLAGS)" -j1 test
+       $(MAKE) CFLAGS="$(PROFILE_USE_CFLAGS)" all
index 5fcc4ef74770e8587a1469fff5e3f5610dabf0e1..77a28d448a76cb0387565b254d4f886924c315b6 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/1.7.6.txt
\ No newline at end of file
+Documentation/RelNotes/1.7.7.1.txt
\ No newline at end of file
index 3005aedde68b48297aacd2082797b2e2f249b094..f04ac18e33063f7e2cb9ab9eab9a6b86cb089b5a 100644 (file)
--- a/abspath.c
+++ b/abspath.c
@@ -40,7 +40,7 @@ const char *real_path(const char *path)
 
        while (depth--) {
                if (!is_directory(buf)) {
-                       char *last_slash = strrchr(buf, '/');
+                       char *last_slash = find_last_dir_sep(buf);
                        if (last_slash) {
                                *last_slash = '\0';
                                last_elem = xstrdup(last_slash + 1);
@@ -65,7 +65,7 @@ const char *real_path(const char *path)
                        if (len + strlen(last_elem) + 2 > PATH_MAX)
                                die ("Too long path name: '%s/%s'",
                                                buf, last_elem);
-                       if (len && buf[len-1] != '/')
+                       if (len && !is_dir_sep(buf[len-1]))
                                buf[len++] = '/';
                        strcpy(buf + len, last_elem);
                        free(last_elem);
@@ -102,7 +102,8 @@ static const char *get_pwd_cwd(void)
        pwd = getenv("PWD");
        if (pwd && strcmp(pwd, cwd)) {
                stat(cwd, &cwd_stat);
-               if (!stat(pwd, &pwd_stat) &&
+               if ((cwd_stat.st_dev || cwd_stat.st_ino) &&
+                   !stat(pwd, &pwd_stat) &&
                    pwd_stat.st_dev == cwd_stat.st_dev &&
                    pwd_stat.st_ino == cwd_stat.st_ino) {
                        strlcpy(cwd, pwd, PATH_MAX);
@@ -138,3 +139,31 @@ const char *absolute_path(const char *path)
        }
        return buf;
 }
+
+/*
+ * Unlike prefix_path, this should be used if the named file does
+ * not have to interact with index entry; i.e. name of a random file
+ * on the filesystem.
+ */
+const char *prefix_filename(const char *pfx, int pfx_len, const char *arg)
+{
+       static char path[PATH_MAX];
+#ifndef WIN32
+       if (!pfx_len || is_absolute_path(arg))
+               return arg;
+       memcpy(path, pfx, pfx_len);
+       strcpy(path + pfx_len, arg);
+#else
+       char *p;
+       /* don't add prefix to absolute paths, but still replace '\' by '/' */
+       if (is_absolute_path(arg))
+               pfx_len = 0;
+       else if (pfx_len)
+               memcpy(path, pfx, pfx_len);
+       strcpy(path + pfx_len, arg);
+       for (p = path + pfx_len; *p; p++)
+               if (*p == '\\')
+                       *p = '/';
+#endif
+       return path;
+}
index cee06ce3cbc1819008337633ed0753638c423ac5..20af0051a334a1357b055ea10d74f5380117ab68 100644 (file)
@@ -4,6 +4,7 @@
 #include "cache.h"
 #include "tar.h"
 #include "archive.h"
+#include "run-command.h"
 
 #define RECORDSIZE     (512)
 #define BLOCKSIZE      (RECORDSIZE * 20)
@@ -13,6 +14,9 @@ static unsigned long offset;
 
 static int tar_umask = 002;
 
+static int write_tar_filter_archive(const struct archiver *ar,
+                                   struct archiver_args *args);
+
 /* writes out the whole block, but only if it is full */
 static void write_if_needed(void)
 {
@@ -220,6 +224,67 @@ static int write_global_extended_header(struct archiver_args *args)
        return err;
 }
 
+static struct archiver **tar_filters;
+static int nr_tar_filters;
+static int alloc_tar_filters;
+
+static struct archiver *find_tar_filter(const char *name, int len)
+{
+       int i;
+       for (i = 0; i < nr_tar_filters; i++) {
+               struct archiver *ar = tar_filters[i];
+               if (!strncmp(ar->name, name, len) && !ar->name[len])
+                       return ar;
+       }
+       return NULL;
+}
+
+static int tar_filter_config(const char *var, const char *value, void *data)
+{
+       struct archiver *ar;
+       const char *dot;
+       const char *name;
+       const char *type;
+       int namelen;
+
+       if (prefixcmp(var, "tar."))
+               return 0;
+       dot = strrchr(var, '.');
+       if (dot == var + 9)
+               return 0;
+
+       name = var + 4;
+       namelen = dot - name;
+       type = dot + 1;
+
+       ar = find_tar_filter(name, namelen);
+       if (!ar) {
+               ar = xcalloc(1, sizeof(*ar));
+               ar->name = xmemdupz(name, namelen);
+               ar->write_archive = write_tar_filter_archive;
+               ar->flags = ARCHIVER_WANT_COMPRESSION_LEVELS;
+               ALLOC_GROW(tar_filters, nr_tar_filters + 1, alloc_tar_filters);
+               tar_filters[nr_tar_filters++] = ar;
+       }
+
+       if (!strcmp(type, "command")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               free(ar->data);
+               ar->data = xstrdup(value);
+               return 0;
+       }
+       if (!strcmp(type, "remote")) {
+               if (git_config_bool(var, value))
+                       ar->flags |= ARCHIVER_REMOTE;
+               else
+                       ar->flags &= ~ARCHIVER_REMOTE;
+               return 0;
+       }
+
+       return 0;
+}
+
 static int git_tar_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "tar.umask")) {
@@ -231,15 +296,15 @@ static int git_tar_config(const char *var, const char *value, void *cb)
                }
                return 0;
        }
-       return git_default_config(var, value, cb);
+
+       return tar_filter_config(var, value, cb);
 }
 
-int write_tar_archive(struct archiver_args *args)
+static int write_tar_archive(const struct archiver *ar,
+                            struct archiver_args *args)
 {
        int err = 0;
 
-       git_config(git_tar_config, NULL);
-
        if (args->commit_sha1)
                err = write_global_extended_header(args);
        if (!err)
@@ -248,3 +313,65 @@ int write_tar_archive(struct archiver_args *args)
                write_trailer();
        return err;
 }
+
+static int write_tar_filter_archive(const struct archiver *ar,
+                                   struct archiver_args *args)
+{
+       struct strbuf cmd = STRBUF_INIT;
+       struct child_process filter;
+       const char *argv[2];
+       int r;
+
+       if (!ar->data)
+               die("BUG: tar-filter archiver called with no filter defined");
+
+       strbuf_addstr(&cmd, ar->data);
+       if (args->compression_level >= 0)
+               strbuf_addf(&cmd, " -%d", args->compression_level);
+
+       memset(&filter, 0, sizeof(filter));
+       argv[0] = cmd.buf;
+       argv[1] = NULL;
+       filter.argv = argv;
+       filter.use_shell = 1;
+       filter.in = -1;
+
+       if (start_command(&filter) < 0)
+               die_errno("unable to start '%s' filter", argv[0]);
+       close(1);
+       if (dup2(filter.in, 1) < 0)
+               die_errno("unable to redirect descriptor");
+       close(filter.in);
+
+       r = write_tar_archive(ar, args);
+
+       close(1);
+       if (finish_command(&filter) != 0)
+               die("'%s' filter reported error", argv[0]);
+
+       strbuf_release(&cmd);
+       return r;
+}
+
+static struct archiver tar_archiver = {
+       "tar",
+       write_tar_archive,
+       ARCHIVER_REMOTE
+};
+
+void init_tar_archiver(void)
+{
+       int i;
+       register_archiver(&tar_archiver);
+
+       tar_filter_config("tar.tgz.command", "gzip -cn", NULL);
+       tar_filter_config("tar.tgz.remote", "true", NULL);
+       tar_filter_config("tar.tar.gz.command", "gzip -cn", NULL);
+       tar_filter_config("tar.tar.gz.remote", "true", NULL);
+       git_config(git_tar_config, NULL);
+       for (i = 0; i < nr_tar_filters; i++) {
+               /* omit any filters that never had a command configured */
+               if (tar_filters[i]->data)
+                       register_archiver(tar_filters[i]);
+       }
+}
index cf285044e3576d0127c3215cb1253443d67517dc..02d1f3787acd8d6583073458ab7c8946684f9371 100644 (file)
@@ -90,14 +90,14 @@ static void copy_le32(unsigned char *dest, unsigned int n)
 static void *zlib_deflate(void *data, unsigned long size,
                int compression_level, unsigned long *compressed_size)
 {
-       z_stream stream;
+       git_zstream stream;
        unsigned long maxsize;
        void *buffer;
        int result;
 
        memset(&stream, 0, sizeof(stream));
-       deflateInit(&stream, compression_level);
-       maxsize = deflateBound(&stream, size);
+       git_deflate_init(&stream, compression_level);
+       maxsize = git_deflate_bound(&stream, size);
        buffer = xmalloc(maxsize);
 
        stream.next_in = data;
@@ -106,7 +106,7 @@ static void *zlib_deflate(void *data, unsigned long size,
        stream.avail_out = maxsize;
 
        do {
-               result = deflate(&stream, Z_FINISH);
+               result = git_deflate(&stream, Z_FINISH);
        } while (result == Z_OK);
 
        if (result != Z_STREAM_END) {
@@ -114,7 +114,7 @@ static void *zlib_deflate(void *data, unsigned long size,
                return NULL;
        }
 
-       deflateEnd(&stream);
+       git_deflate_end(&stream);
        *compressed_size = stream.total_out;
 
        return buffer;
@@ -261,7 +261,8 @@ static void dos_time(time_t *time, int *dos_date, int *dos_time)
        *dos_time = t->tm_sec / 2 + t->tm_min * 32 + t->tm_hour * 2048;
 }
 
-int write_zip_archive(struct archiver_args *args)
+static int write_zip_archive(const struct archiver *ar,
+                            struct archiver_args *args)
 {
        int err;
 
@@ -278,3 +279,14 @@ int write_zip_archive(struct archiver_args *args)
 
        return err;
 }
+
+static struct archiver zip_archiver = {
+       "zip",
+       write_zip_archive,
+       ARCHIVER_WANT_COMPRESSION_LEVELS|ARCHIVER_REMOTE
+};
+
+void init_zip_archiver(void)
+{
+       register_archiver(&zip_archiver);
+}
index 42f2d2fdc81a7c656e3ec006227b092addd13044..3fd7f475f1d8ff5efe602a10f02ea3e1c3aa9edd 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -14,16 +14,15 @@ static char const * const archive_usage[] = {
        NULL
 };
 
-#define USES_ZLIB_COMPRESSION 1
-
-static const struct archiver {
-       const char *name;
-       write_archive_fn_t write_archive;
-       unsigned int flags;
-} archivers[] = {
-       { "tar", write_tar_archive },
-       { "zip", write_zip_archive, USES_ZLIB_COMPRESSION },
-};
+static const struct archiver **archivers;
+static int nr_archivers;
+static int alloc_archivers;
+
+void register_archiver(struct archiver *ar)
+{
+       ALLOC_GROW(archivers, nr_archivers + 1, alloc_archivers);
+       archivers[nr_archivers++] = ar;
+}
 
 static void format_subst(const struct commit *commit,
                          const char *src, size_t len,
@@ -124,7 +123,7 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,
        path_without_prefix = path.buf + args->baselen;
 
        setup_archive_check(check);
-       if (!git_checkattr(path_without_prefix, ARRAY_SIZE(check), check)) {
+       if (!git_check_attr(path_without_prefix, ARRAY_SIZE(check), check)) {
                if (ATTR_TRUE(check[0].value))
                        return 0;
                convert = ATTR_TRUE(check[1].value);
@@ -208,9 +207,9 @@ static const struct archiver *lookup_archiver(const char *name)
        if (!name)
                return NULL;
 
-       for (i = 0; i < ARRAY_SIZE(archivers); i++) {
-               if (!strcmp(name, archivers[i].name))
-                       return &archivers[i];
+       for (i = 0; i < nr_archivers; i++) {
+               if (!strcmp(name, archivers[i]->name))
+                       return archivers[i];
        }
        return NULL;
 }
@@ -299,9 +298,10 @@ static void parse_treeish_arg(const char **argv,
          PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN, NULL, (p) }
 
 static int parse_archive_args(int argc, const char **argv,
-               const struct archiver **ar, struct archiver_args *args)
+               const struct archiver **ar, struct archiver_args *args,
+               const char *name_hint, int is_remote)
 {
-       const char *format = "tar";
+       const char *format = NULL;
        const char *base = NULL;
        const char *remote = NULL;
        const char *exec = NULL;
@@ -355,21 +355,27 @@ static int parse_archive_args(int argc, const char **argv,
                base = "";
 
        if (list) {
-               for (i = 0; i < ARRAY_SIZE(archivers); i++)
-                       printf("%s\n", archivers[i].name);
+               for (i = 0; i < nr_archivers; i++)
+                       if (!is_remote || archivers[i]->flags & ARCHIVER_REMOTE)
+                               printf("%s\n", archivers[i]->name);
                exit(0);
        }
 
+       if (!format && name_hint)
+               format = archive_format_from_filename(name_hint);
+       if (!format)
+               format = "tar";
+
        /* We need at least one parameter -- tree-ish */
        if (argc < 1)
                usage_with_options(archive_usage, opts);
        *ar = lookup_archiver(format);
-       if (!*ar)
+       if (!*ar || (is_remote && !((*ar)->flags & ARCHIVER_REMOTE)))
                die("Unknown archive format '%s'", format);
 
        args->compression_level = Z_DEFAULT_COMPRESSION;
        if (compression_level != -1) {
-               if ((*ar)->flags & USES_ZLIB_COMPRESSION)
+               if ((*ar)->flags & ARCHIVER_WANT_COMPRESSION_LEVELS)
                        args->compression_level = compression_level;
                else {
                        die("Argument not supported for format '%s': -%d",
@@ -385,19 +391,55 @@ static int parse_archive_args(int argc, const char **argv,
 }
 
 int write_archive(int argc, const char **argv, const char *prefix,
-               int setup_prefix)
+                 int setup_prefix, const char *name_hint, int remote)
 {
+       int nongit = 0;
        const struct archiver *ar = NULL;
        struct archiver_args args;
 
-       argc = parse_archive_args(argc, argv, &ar, &args);
        if (setup_prefix && prefix == NULL)
-               prefix = setup_git_directory();
+               prefix = setup_git_directory_gently(&nongit);
+
+       git_config(git_default_config, NULL);
+       init_tar_archiver();
+       init_zip_archiver();
+
+       argc = parse_archive_args(argc, argv, &ar, &args, name_hint, remote);
+       if (nongit) {
+               /*
+                * We know this will die() with an error, so we could just
+                * die ourselves; but its error message will be more specific
+                * than what we could write here.
+                */
+               setup_git_directory();
+       }
 
        parse_treeish_arg(argv, &args, prefix);
        parse_pathspec_arg(argv + 1, &args);
 
-       git_config(git_default_config, NULL);
+       return ar->write_archive(ar, &args);
+}
 
-       return ar->write_archive(&args);
+static int match_extension(const char *filename, const char *ext)
+{
+       int prefixlen = strlen(filename) - strlen(ext);
+
+       /*
+        * We need 1 character for the '.', and 1 character to ensure that the
+        * prefix is non-empty (k.e., we don't match .tar.gz with no actual
+        * filename).
+        */
+       if (prefixlen < 2 || filename[prefixlen-1] != '.')
+               return 0;
+       return !strcmp(filename + prefixlen, ext);
+}
+
+const char *archive_format_from_filename(const char *filename)
+{
+       int i;
+
+       for (i = 0; i < nr_archivers; i++)
+               if (match_extension(filename, archivers[i]->name))
+                       return archivers[i]->name;
+       return NULL;
 }
index 038ac353d40567029403e3e7a2933af73a130720..2b0884f1ef3123f26d9a7b3ba03e3d14ae1ccd57 100644 (file)
--- a/archive.h
+++ b/archive.h
@@ -14,17 +14,24 @@ struct archiver_args {
        int compression_level;
 };
 
-typedef int (*write_archive_fn_t)(struct archiver_args *);
+#define ARCHIVER_WANT_COMPRESSION_LEVELS 1
+#define ARCHIVER_REMOTE 2
+struct archiver {
+       const char *name;
+       int (*write_archive)(const struct archiver *, struct archiver_args *);
+       unsigned flags;
+       void *data;
+};
+extern void register_archiver(struct archiver *);
 
-typedef int (*write_archive_entry_fn_t)(struct archiver_args *args, const unsigned char *sha1, const char *path, size_t pathlen, unsigned int mode, void *buffer, unsigned long size);
+extern void init_tar_archiver(void);
+extern void init_zip_archiver(void);
 
-/*
- * Archive-format specific backends.
- */
-extern int write_tar_archive(struct archiver_args *);
-extern int write_zip_archive(struct archiver_args *);
+typedef int (*write_archive_entry_fn_t)(struct archiver_args *args, const unsigned char *sha1, const char *path, size_t pathlen, unsigned int mode, void *buffer, unsigned long size);
 
 extern int write_archive_entries(struct archiver_args *args, write_archive_entry_fn_t write_entry);
-extern int write_archive(int argc, const char **argv, const char *prefix, int setup_prefix);
+extern int write_archive(int argc, const char **argv, const char *prefix, int setup_prefix, const char *name_hint, int remote);
+
+const char *archive_format_from_filename(const char *filename);
 
 #endif /* ARCHIVE_H */
diff --git a/attr.c b/attr.c
index f6b3f7e850f9fcf5672031049ef1d6f43e619f63..33cb4e4d113cbb3816ba824cb06bf494a4bd9bc3 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -1,3 +1,12 @@
+/*
+ * Handle git attributes.  See gitattributes(5) for a description of
+ * the file syntax, and Documentation/technical/api-gitattributes.txt
+ * for a description of the API.
+ *
+ * One basic design decision here is that we are not going to support
+ * an insanely large number of attributes.
+ */
+
 #define NO_THE_INDEX_COMPATIBILITY_MACROS
 #include "cache.h"
 #include "exec_cmd.h"
@@ -13,12 +22,7 @@ static const char git_attr__unknown[] = "(builtin)unknown";
 
 static const char *attributes_file;
 
-/*
- * The basic design decision here is that we are not going to have
- * insanely large number of attributes.
- *
- * This is a randomly chosen prime.
- */
+/* This is a randomly chosen prime. */
 #define HASHSIZE 257
 
 #ifndef DEBUG_ATTR
@@ -36,6 +40,11 @@ static int attr_nr;
 static struct git_attr_check *check_all_attr;
 static struct git_attr *(git_attr_hash[HASHSIZE]);
 
+char *git_attr_name(struct git_attr *attr)
+{
+       return attr->name;
+}
+
 static unsigned hash_name(const char *name, int namelen)
 {
        unsigned val = 0, c;
@@ -50,12 +59,10 @@ static unsigned hash_name(const char *name, int namelen)
 static int invalid_attr_name(const char *name, int namelen)
 {
        /*
-        * Attribute name cannot begin with '-' and from
-        * [-A-Za-z0-9_.].  We'd specifically exclude '=' for now,
-        * as we might later want to allow non-binary value for
-        * attributes, e.g. "*.svg      merge=special-merge-program-for-svg"
+        * Attribute name cannot begin with '-' and must consist of
+        * characters from [-A-Za-z0-9_.].
         */
-       if (*name == '-')
+       if (namelen <= 0 || *name == '-')
                return -1;
        while (namelen--) {
                char ch = *name++;
@@ -103,22 +110,26 @@ struct git_attr *git_attr(const char *name)
        return git_attr_internal(name, strlen(name));
 }
 
-/*
- * .gitattributes file is one line per record, each of which is
- *
- * (1) glob pattern.
- * (2) whitespace
- * (3) whitespace separated list of attribute names, each of which
- *     could be prefixed with '-' to mean "set to false", '!' to mean
- *     "unset".
- */
-
 /* What does a matched pattern decide? */
 struct attr_state {
        struct git_attr *attr;
        const char *setto;
 };
 
+/*
+ * One rule, as from a .gitattributes file.
+ *
+ * If is_macro is true, then u.attr is a pointer to the git_attr being
+ * defined.
+ *
+ * If is_macro is false, then u.pattern points at the filename pattern
+ * to which the rule applies.  (The memory pointed to is part of the
+ * memory block allocated for the match_attr instance.)
+ *
+ * In either case, num_attr is the number of attributes affected by
+ * this rule, and state is an array listing them.  The attributes are
+ * listed as they appear in the file (macros unexpanded).
+ */
 struct match_attr {
        union {
                char *pattern;
@@ -131,8 +142,15 @@ struct match_attr {
 
 static const char blank[] = " \t\r\n";
 
+/*
+ * Parse a whitespace-delimited attribute state (i.e., "attr",
+ * "-attr", "!attr", or "attr=value") from the string starting at src.
+ * If e is not NULL, write the results to *e.  Return a pointer to the
+ * remainder of the string (with leading whitespace removed), or NULL
+ * if there was an error.
+ */
 static const char *parse_attr(const char *src, int lineno, const char *cp,
-                             int *num_attr, struct match_attr *res)
+                             struct attr_state *e)
 {
        const char *ep, *equals;
        int len;
@@ -145,7 +163,7 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,
                len = equals - cp;
        else
                len = ep - cp;
-       if (!res) {
+       if (!e) {
                if (*cp == '-' || *cp == '!') {
                        cp++;
                        len--;
@@ -157,9 +175,6 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,
                        return NULL;
                }
        } else {
-               struct attr_state *e;
-
-               e = &(res->state[*num_attr]);
                if (*cp == '-' || *cp == '!') {
                        e->setto = (*cp == '-') ? ATTR__FALSE : ATTR__UNSET;
                        cp++;
@@ -172,7 +187,6 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,
                }
                e->attr = git_attr_internal(cp, len);
        }
-       (*num_attr)++;
        return ep + strspn(ep, blank);
 }
 
@@ -180,10 +194,9 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
                                          int lineno, int macro_ok)
 {
        int namelen;
-       int num_attr;
-       const char *cp, *name;
+       int num_attr, i;
+       const char *cp, *name, *states;
        struct match_attr *res = NULL;
-       int pass;
        int is_macro;
 
        cp = line + strspn(line, blank);
@@ -212,32 +225,35 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
        else
                is_macro = 0;
 
-       for (pass = 0; pass < 2; pass++) {
-               /* pass 0 counts and allocates, pass 1 fills */
-               num_attr = 0;
-               cp = name + namelen;
-               cp = cp + strspn(cp, blank);
-               while (*cp) {
-                       cp = parse_attr(src, lineno, cp, &num_attr, res);
-                       if (!cp)
-                               return NULL;
-               }
-               if (pass)
-                       break;
-               res = xcalloc(1,
-                             sizeof(*res) +
-                             sizeof(struct attr_state) * num_attr +
-                             (is_macro ? 0 : namelen + 1));
-               if (is_macro)
-                       res->u.attr = git_attr_internal(name, namelen);
-               else {
-                       res->u.pattern = (char *)&(res->state[num_attr]);
-                       memcpy(res->u.pattern, name, namelen);
-                       res->u.pattern[namelen] = 0;
-               }
-               res->is_macro = is_macro;
-               res->num_attr = num_attr;
+       states = name + namelen;
+       states += strspn(states, blank);
+
+       /* First pass to count the attr_states */
+       for (cp = states, num_attr = 0; *cp; num_attr++) {
+               cp = parse_attr(src, lineno, cp, NULL);
+               if (!cp)
+                       return NULL;
+       }
+
+       res = xcalloc(1,
+                     sizeof(*res) +
+                     sizeof(struct attr_state) * num_attr +
+                     (is_macro ? 0 : namelen + 1));
+       if (is_macro)
+               res->u.attr = git_attr_internal(name, namelen);
+       else {
+               res->u.pattern = (char *)&(res->state[num_attr]);
+               memcpy(res->u.pattern, name, namelen);
+               res->u.pattern[namelen] = 0;
+       }
+       res->is_macro = is_macro;
+       res->num_attr = num_attr;
+
+       /* Second pass to fill the attr_states */
+       for (cp = states, i = 0; *cp; i++) {
+               cp = parse_attr(src, lineno, cp, &(res->state[i]));
        }
+
        return res;
 }
 
@@ -532,11 +548,18 @@ static void bootstrap_attr_stack(void)
        }
 }
 
-static void prepare_attr_stack(const char *path, int dirlen)
+static void prepare_attr_stack(const char *path)
 {
        struct attr_stack *elem, *info;
-       int len;
+       int dirlen, len;
        struct strbuf pathbuf;
+       const char *cp;
+
+       cp = strrchr(path, '/');
+       if (!cp)
+               dirlen = 0;
+       else
+               dirlen = cp - path;
 
        strbuf_init(&pathbuf, dirlen+2+strlen(GITATTRIBUTES_FILE));
 
@@ -555,8 +578,7 @@ static void prepare_attr_stack(const char *path, int dirlen)
         * .gitattributes in deeper directories to shallower ones,
         * and finally use the built-in set as the default.
         */
-       if (!attr_stack)
-               bootstrap_attr_stack();
+       bootstrap_attr_stack();
 
        /*
         * Pop the "info" one that is always at the top of the stack.
@@ -703,26 +725,30 @@ static int macroexpand_one(int attr_nr, int rem)
        return rem;
 }
 
-int git_checkattr(const char *path, int num, struct git_attr_check *check)
+/*
+ * Collect all attributes for path into the array pointed to by
+ * check_all_attr.
+ */
+static void collect_all_attrs(const char *path)
 {
        struct attr_stack *stk;
-       const char *cp;
-       int dirlen, pathlen, i, rem;
+       int i, pathlen, rem;
 
-       bootstrap_attr_stack();
+       prepare_attr_stack(path);
        for (i = 0; i < attr_nr; i++)
                check_all_attr[i].value = ATTR__UNKNOWN;
 
        pathlen = strlen(path);
-       cp = strrchr(path, '/');
-       if (!cp)
-               dirlen = 0;
-       else
-               dirlen = cp - path;
-       prepare_attr_stack(path, dirlen);
        rem = attr_nr;
        for (stk = attr_stack; 0 < rem && stk; stk = stk->prev)
                rem = fill(path, pathlen, stk, rem);
+}
+
+int git_check_attr(const char *path, int num, struct git_attr_check *check)
+{
+       int i;
+
+       collect_all_attrs(path);
 
        for (i = 0; i < num; i++) {
                const char *value = check_all_attr[check[i].attr->attr_nr].value;
@@ -734,6 +760,34 @@ int git_checkattr(const char *path, int num, struct git_attr_check *check)
        return 0;
 }
 
+int git_all_attrs(const char *path, int *num, struct git_attr_check **check)
+{
+       int i, count, j;
+
+       collect_all_attrs(path);
+
+       /* Count the number of attributes that are set. */
+       count = 0;
+       for (i = 0; i < attr_nr; i++) {
+               const char *value = check_all_attr[i].value;
+               if (value != ATTR__UNSET && value != ATTR__UNKNOWN)
+                       ++count;
+       }
+       *num = count;
+       *check = xmalloc(sizeof(**check) * count);
+       j = 0;
+       for (i = 0; i < attr_nr; i++) {
+               const char *value = check_all_attr[i].value;
+               if (value != ATTR__UNSET && value != ATTR__UNKNOWN) {
+                       (*check)[j].attr = check_all_attr[i].attr;
+                       (*check)[j].value = value;
+                       ++j;
+               }
+       }
+
+       return 0;
+}
+
 void git_attr_set_direction(enum git_attr_direction new, struct index_state *istate)
 {
        enum git_attr_direction old = direction;
diff --git a/attr.h b/attr.h
index 8b3f19be67f17c1fbf6edb37ac3ea27002ca6415..eb8ca0d7c067417a8ff2802179e50bddcdff3367 100644 (file)
--- a/attr.h
+++ b/attr.h
@@ -20,7 +20,7 @@ extern const char git_attr__false[];
 #define ATTR_UNSET(v) ((v) == NULL)
 
 /*
- * Send one or more git_attr_check to git_checkattr(), and
+ * Send one or more git_attr_check to git_check_attr(), and
  * each 'value' member tells what its value is.
  * Unset one is returned as NULL.
  */
@@ -29,7 +29,23 @@ struct git_attr_check {
        const char *value;
 };
 
-int git_checkattr(const char *path, int, struct git_attr_check *);
+/*
+ * Return the name of the attribute represented by the argument.  The
+ * return value is a pointer to a null-delimited string that is part
+ * of the internal data structure; it should not be modified or freed.
+ */
+char *git_attr_name(struct git_attr *);
+
+int git_check_attr(const char *path, int, struct git_attr_check *);
+
+/*
+ * Retrieve all attributes that apply to the specified path.  *num
+ * will be set the the number of attributes on the path; **check will
+ * be set to point at a newly-allocated array of git_attr_check
+ * objects describing the attributes and their values.  *check must be
+ * free()ed by the caller.
+ */
+int git_all_attrs(const char *path, int *num, struct git_attr_check **check);
 
 enum git_attr_direction {
        GIT_ATTR_CHECKIN,
index ef92871e3cf8f29fa4c7f84702a4367610295cae..de05bf824620324e42b7a27571ecd3e763cf7e8f 100644 (file)
--- a/bisect.c
+++ b/bisect.c
@@ -19,6 +19,7 @@ static const unsigned char *current_bad_sha1;
 
 static const char *argv_checkout[] = {"checkout", "-q", NULL, "--", NULL};
 static const char *argv_show_branch[] = {"show-branch", NULL, NULL};
+static const char *argv_update_ref[] = {"update-ref", "--no-deref", "BISECT_HEAD", NULL, NULL};
 
 /* bits #0-15 in revision.h */
 
@@ -681,16 +682,23 @@ static void mark_expected_rev(char *bisect_rev_hex)
                die("closing file %s: %s", filename, strerror(errno));
 }
 
-static int bisect_checkout(char *bisect_rev_hex)
+static int bisect_checkout(char *bisect_rev_hex, int no_checkout)
 {
        int res;
 
        mark_expected_rev(bisect_rev_hex);
 
        argv_checkout[2] = bisect_rev_hex;
-       res = run_command_v_opt(argv_checkout, RUN_GIT_CMD);
-       if (res)
-               exit(res);
+       if (no_checkout) {
+               argv_update_ref[3] = bisect_rev_hex;
+               if (run_command_v_opt(argv_update_ref, RUN_GIT_CMD))
+                       die("update-ref --no-deref HEAD failed on %s",
+                           bisect_rev_hex);
+       } else {
+               res = run_command_v_opt(argv_checkout, RUN_GIT_CMD);
+               if (res)
+                       exit(res);
+       }
 
        argv_show_branch[1] = bisect_rev_hex;
        return run_command_v_opt(argv_show_branch, RUN_GIT_CMD);
@@ -762,7 +770,7 @@ static void handle_skipped_merge_base(const unsigned char *mb)
  * - If one is "skipped", we can't know but we should warn.
  * - If we don't know, we should check it out and ask the user to test.
  */
-static void check_merge_bases(void)
+static void check_merge_bases(int no_checkout)
 {
        struct commit_list *result;
        int rev_nr;
@@ -780,7 +788,7 @@ static void check_merge_bases(void)
                        handle_skipped_merge_base(mb);
                } else {
                        printf("Bisecting: a merge base must be tested\n");
-                       exit(bisect_checkout(sha1_to_hex(mb)));
+                       exit(bisect_checkout(sha1_to_hex(mb), no_checkout));
                }
        }
 
@@ -823,7 +831,7 @@ static int check_ancestors(const char *prefix)
  * If a merge base must be tested by the user, its source code will be
  * checked out to be tested by the user and we will exit.
  */
-static void check_good_are_ancestors_of_bad(const char *prefix)
+static void check_good_are_ancestors_of_bad(const char *prefix, int no_checkout)
 {
        const char *filename = git_path("BISECT_ANCESTORS_OK");
        struct stat st;
@@ -842,7 +850,7 @@ static void check_good_are_ancestors_of_bad(const char *prefix)
 
        /* Check if all good revs are ancestor of the bad rev. */
        if (check_ancestors(prefix))
-               check_merge_bases();
+               check_merge_bases(no_checkout);
 
        /* Create file BISECT_ANCESTORS_OK. */
        fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
@@ -882,8 +890,11 @@ static void show_diff_tree(const char *prefix, struct commit *commit)
  * We use the convention that exiting with an exit code 10 means that
  * the bisection process finished successfully.
  * In this case the calling shell script should exit 0.
+ *
+ * If no_checkout is non-zero, the bisection process does not
+ * checkout the trial commit but instead simply updates BISECT_HEAD.
  */
-int bisect_next_all(const char *prefix)
+int bisect_next_all(const char *prefix, int no_checkout)
 {
        struct rev_info revs;
        struct commit_list *tried;
@@ -894,7 +905,7 @@ int bisect_next_all(const char *prefix)
        if (read_bisect_refs())
                die("reading bisect refs failed");
 
-       check_good_are_ancestors_of_bad(prefix);
+       check_good_are_ancestors_of_bad(prefix, no_checkout);
 
        bisect_rev_setup(&revs, prefix, "%s", "^%s", 1);
        revs.limited = 1;
@@ -940,6 +951,6 @@ int bisect_next_all(const char *prefix)
               "(roughly %d step%s)\n", nr, (nr == 1 ? "" : "s"),
               steps, (steps == 1 ? "" : "s"));
 
-       return bisect_checkout(bisect_rev_hex);
+       return bisect_checkout(bisect_rev_hex, no_checkout);
 }
 
index 0862ce56d76e9b08ab913e6a472fac590974340e..22f2e4db2d1d05184dc2eeec2ff87021a67e01f2 100644 (file)
--- a/bisect.h
+++ b/bisect.h
@@ -27,7 +27,7 @@ struct rev_list_info {
        const char *header_prefix;
 };
 
-extern int bisect_next_all(const char *prefix);
+extern int bisect_next_all(const char *prefix, int no_checkout);
 
 extern int estimate_bisect_steps(int all);
 
index c0c865a4b1b0fca038f32b77f2239d7987438ff5..fecedd3b4635c79adcbe0e26613af079beff64d0 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -135,6 +135,28 @@ static int setup_tracking(const char *new_ref, const char *orig_ref,
        return 0;
 }
 
+int validate_new_branchname(const char *name, struct strbuf *ref,
+                           int force, int attr_only)
+{
+       if (strbuf_check_branch_ref(ref, name))
+               die("'%s' is not a valid branch name.", name);
+
+       if (!ref_exists(ref->buf))
+               return 0;
+       else if (!force && !attr_only)
+               die("A branch named '%s' already exists.", ref->buf + strlen("refs/heads/"));
+
+       if (!attr_only) {
+               const char *head;
+               unsigned char sha1[20];
+
+               head = resolve_ref("HEAD", sha1, 0, NULL);
+               if (!is_bare_repository() && head && !strcmp(head, ref->buf))
+                       die("Cannot force update the current branch.");
+       }
+       return 1;
+}
+
 void create_branch(const char *head,
                   const char *name, const char *start_name,
                   int force, int reflog, enum branch_track track)
@@ -151,17 +173,12 @@ void create_branch(const char *head,
        if (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)
                explicit_tracking = 1;
 
-       if (strbuf_check_branch_ref(&ref, name))
-               die("'%s' is not a valid branch name.", name);
-
-       if (resolve_ref(ref.buf, sha1, 1, NULL)) {
-               if (!force && track == BRANCH_TRACK_OVERRIDE)
+       if (validate_new_branchname(name, &ref, force,
+                                   track == BRANCH_TRACK_OVERRIDE)) {
+               if (!force)
                        dont_change_ref = 1;
-               else if (!force)
-                       die("A branch named '%s' already exists.", name);
-               else if (!is_bare_repository() && head && !strcmp(head, name))
-                       die("Cannot force update the current branch.");
-               forcing = 1;
+               else
+                       forcing = 1;
        }
 
        real_ref = NULL;
@@ -210,7 +227,7 @@ void create_branch(const char *head,
                         start_name);
 
        if (real_ref && track)
-               setup_tracking(name, real_ref, track);
+               setup_tracking(ref.buf+11, real_ref, track);
 
        if (!dont_change_ref)
                if (write_ref_sha1(lock, sha1, msg) < 0)
index 4026e3832b265c4cef6e5bc151976771867b3da9..1285158dd4f26e5bbb0e0d7133055f168fee773f 100644 (file)
--- a/branch.h
+++ b/branch.h
 void create_branch(const char *head, const char *name, const char *start_name,
                   int force, int reflog, enum branch_track track);
 
+/*
+ * Validates that the requested branch may be created, returning the
+ * interpreted ref in ref, force indicates whether (non-head) branches
+ * may be overwritten. A non-zero return value indicates that the force
+ * parameter was non-zero and the branch already exists.
+ *
+ * Contrary to all of the above, when attr_only is 1, the caller is
+ * not interested in verifying if it is Ok to update the named
+ * branch to point at a potentially different commit. It is merely
+ * asking if it is OK to change some attribute for the named branch
+ * (e.g. tracking upstream).
+ *
+ * NEEDSWORK: This needs to be split into two separate functions in the
+ * longer run for sanity.
+ *
+ */
+int validate_new_branchname(const char *name, struct strbuf *ref, int force, int attr_only);
+
 /*
  * Remove information about the state of working on the current
  * branch. (E.g., MERGE_HEAD)
index 530d4bb7e76da8e402f78ab7f981a080d25d6e78..311a94e18195c611b55de7d1619da890aca240af 100644 (file)
@@ -1407,6 +1407,9 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
                                            "%d leading pathname components (line %d)" , p_value, linenr);
                                patch->old_name = patch->new_name = patch->def_name;
                        }
+                       if (!patch->is_delete && !patch->new_name)
+                               die("git diff header lacks filename information "
+                                   "(line %d)", linenr);
                        patch->is_toplevel_relative = 1;
                        *hdrsize = git_hdr_len;
                        return offset;
@@ -1634,7 +1637,7 @@ static inline int metadata_changes(struct patch *patch)
 static char *inflate_it(const void *data, unsigned long size,
                        unsigned long inflated_size)
 {
-       z_stream stream;
+       git_zstream stream;
        void *out;
        int st;
 
@@ -2447,6 +2450,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
        char *old, *oldlines;
        struct strbuf newlines;
        int new_blank_lines_at_end = 0;
+       int found_new_blank_lines_at_end = 0;
+       int hunk_linenr = frag->linenr;
        unsigned long leading, trailing;
        int pos, applied_pos;
        struct image preimage;
@@ -2540,14 +2545,18 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
                                error("invalid start of line: '%c'", first);
                        return -1;
                }
-               if (added_blank_line)
+               if (added_blank_line) {
+                       if (!new_blank_lines_at_end)
+                               found_new_blank_lines_at_end = hunk_linenr;
                        new_blank_lines_at_end++;
+               }
                else if (is_blank_context)
                        ;
                else
                        new_blank_lines_at_end = 0;
                patch += len;
                size -= len;
+               hunk_linenr++;
        }
        if (inaccurate_eof &&
            old > oldlines && old[-1] == '\n' &&
@@ -2629,7 +2638,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
                    preimage.nr + applied_pos >= img->nr &&
                    (ws_rule & WS_BLANK_AT_EOF) &&
                    ws_error_action != nowarn_ws_error) {
-                       record_ws_error(WS_BLANK_AT_EOF, "+", 1, frag->linenr);
+                       record_ws_error(WS_BLANK_AT_EOF, "+", 1,
+                                       found_new_blank_lines_at_end);
                        if (ws_error_action == correct_ws_error) {
                                while (new_blank_lines_at_end--)
                                        remove_last_line(&postimage);
index b14eaba1594bad060e0c881326368b9b2be3b158..931956def986bbdf5b77e163f1b18c961e7be09c 100644 (file)
@@ -24,7 +24,8 @@ static void create_output_file(const char *output_file)
 }
 
 static int run_remote_archiver(int argc, const char **argv,
-                              const char *remote, const char *exec)
+                              const char *remote, const char *exec,
+                              const char *name_hint)
 {
        char buf[LARGE_PACKET_MAX];
        int fd[2], i, len, rv;
@@ -37,6 +38,17 @@ static int run_remote_archiver(int argc, const char **argv,
        transport = transport_get(_remote, _remote->url[0]);
        transport_connect(transport, "git-upload-archive", exec, fd);
 
+       /*
+        * Inject a fake --format field at the beginning of the
+        * arguments, with the format inferred from our output
+        * filename. This way explicit --format options can override
+        * it.
+        */
+       if (name_hint) {
+               const char *format = archive_format_from_filename(name_hint);
+               if (format)
+                       packet_write(fd[1], "argument --format=%s\n", format);
+       }
        for (i = 1; i < argc; i++)
                packet_write(fd[1], "argument %s\n", argv[i]);
        packet_flush(fd[1]);
@@ -49,6 +61,8 @@ static int run_remote_archiver(int argc, const char **argv,
        if (strcmp(buf, "ACK")) {
                if (len > 5 && !prefixcmp(buf, "NACK "))
                        die(_("git archive: NACK %s"), buf + 5);
+               if (len > 4 && !prefixcmp(buf, "ERR "))
+                       die(_("remote error: %s"), buf + 4);
                die(_("git archive: protocol error"));
        }
 
@@ -63,17 +77,6 @@ static int run_remote_archiver(int argc, const char **argv,
        return !!rv;
 }
 
-static const char *format_from_name(const char *filename)
-{
-       const char *ext = strrchr(filename, '.');
-       if (!ext)
-               return NULL;
-       ext++;
-       if (!strcasecmp(ext, "zip"))
-               return "--format=zip";
-       return NULL;
-}
-
 #define PARSE_OPT_KEEP_ALL ( PARSE_OPT_KEEP_DASHDASH |         \
                             PARSE_OPT_KEEP_ARGV0 |     \
                             PARSE_OPT_KEEP_UNKNOWN |   \
@@ -84,7 +87,6 @@ int cmd_archive(int argc, const char **argv, const char *prefix)
        const char *exec = "git-upload-archive";
        const char *output = NULL;
        const char *remote = NULL;
-       const char *format_option = NULL;
        struct option local_opts[] = {
                OPT_STRING('o', "output", &output, "file",
                        "write the archive to this file"),
@@ -98,32 +100,13 @@ int cmd_archive(int argc, const char **argv, const char *prefix)
        argc = parse_options(argc, argv, prefix, local_opts, NULL,
                             PARSE_OPT_KEEP_ALL);
 
-       if (output) {
+       if (output)
                create_output_file(output);
-               format_option = format_from_name(output);
-       }
-
-       /*
-        * We have enough room in argv[] to muck it in place, because
-        * --output must have been given on the original command line
-        * if we get to this point, and parse_options() must have eaten
-        * it, i.e. we can add back one element to the array.
-        *
-        * We add a fake --format option at the beginning, with the
-        * format inferred from our output filename.  This way explicit
-        * --format options can override it, and the fake option is
-        * inserted before any "--" that might have been given.
-        */
-       if (format_option) {
-               memmove(argv + 2, argv + 1, sizeof(*argv) * argc);
-               argv[1] = format_option;
-               argv[++argc] = NULL;
-       }
 
        if (remote)
-               return run_remote_archiver(argc, argv, remote, exec);
+               return run_remote_archiver(argc, argv, remote, exec, output);
 
        setvbuf(stderr, NULL, _IOLBF, BUFSIZ);
 
-       return write_archive(argc, argv, prefix, 1);
+       return write_archive(argc, argv, prefix, 1, output, 0);
 }
index 5b226399e1c30b23a7b5d226a3f45efe9dbedf45..8d325a5179f68d2810b5b2082fe66dc6d00da455 100644 (file)
@@ -4,16 +4,19 @@
 #include "bisect.h"
 
 static const char * const git_bisect_helper_usage[] = {
-       "git bisect--helper --next-all",
+       "git bisect--helper --next-all [--no-checkout]",
        NULL
 };
 
 int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
 {
        int next_all = 0;
+       int no_checkout = 0;
        struct option options[] = {
                OPT_BOOLEAN(0, "next-all", &next_all,
                            "perform 'git bisect next'"),
+               OPT_BOOLEAN(0, "no-checkout", &no_checkout,
+                           "update BISECT_HEAD instead of checking out the current commit"),
                OPT_END()
        };
 
@@ -24,5 +27,5 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
                usage_with_options(git_bisect_helper_usage, options);
 
        /* next-all */
-       return bisect_next_all(prefix);
+       return bisect_next_all(prefix, no_checkout);
 }
index d6ab93bfbb369965e76e384e6984a29b667cdfbe..f49596f826228e22d354194ddbb73ec100d8728e 100644 (file)
@@ -19,7 +19,7 @@
 static const char * const builtin_branch_usage[] = {
        "git branch [options] [-r | -a] [--merged | --no-merged]",
        "git branch [options] [-l] [-f] <branchname> [<start-point>]",
-       "git branch [options] [-r] (-d | -D) <branchname>",
+       "git branch [options] [-r] (-d | -D) <branchname>...",
        "git branch [options] (-m | -M) [<oldbranch>] <newbranch>",
        NULL
 };
@@ -71,7 +71,7 @@ static int parse_branch_color_slot(const char *var, int ofs)
 static int git_branch_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "color.branch")) {
-               branch_use_color = git_config_colorbool(var, value, -1);
+               branch_use_color = git_config_colorbool(var, value);
                return 0;
        }
        if (!prefixcmp(var, "color.branch.")) {
@@ -88,7 +88,7 @@ static int git_branch_config(const char *var, const char *value, void *cb)
 
 static const char *branch_get_color(enum color_branch ix)
 {
-       if (branch_use_color > 0)
+       if (want_color(branch_use_color))
                return branch_colors[ix];
        return "";
 }
@@ -566,11 +566,7 @@ static void rename_branch(const char *oldname, const char *newname, int force)
                        die(_("Invalid branch name: '%s'"), oldname);
        }
 
-       if (strbuf_check_branch_ref(&newref, newname))
-               die(_("Invalid branch name: '%s'"), newname);
-
-       if (resolve_ref(newref.buf, sha1, 1, NULL) && !force)
-               die(_("A branch named '%s' already exists."), newref.buf + 11);
+       validate_new_branchname(newname, &newref, force, 0);
 
        strbuf_addf(&logmsg, "Branch: renamed %s to %s",
                 oldref.buf, newref.buf);
@@ -613,7 +609,7 @@ static int opt_parse_merge_filter(const struct option *opt, const char *arg, int
 int cmd_branch(int argc, const char **argv, const char *prefix)
 {
        int delete = 0, rename = 0, force_create = 0;
-       int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0;
+       int verbose = 0, abbrev = -1, detached = 0;
        int reflog = 0;
        enum branch_track track;
        int kinds = REF_LOCAL_BRANCH;
@@ -673,9 +669,6 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 
        git_config(git_branch_config, NULL);
 
-       if (branch_use_color == -1)
-               branch_use_color = git_use_color_default;
-
        track = git_branch_track;
 
        head = resolve_ref("HEAD", head_sha1, 0, NULL);
@@ -696,6 +689,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        if (!!delete + !!rename + !!force_create > 1)
                usage_with_options(builtin_branch_usage, options);
 
+       if (abbrev == -1)
+               abbrev = DEFAULT_ABBREV;
+
        if (delete)
                return delete_branches(argc, argv, delete > 1, kinds);
        else if (argc == 0)
index 81046a9cb870aab59a56492e175a51c032ad96ce..92a8a6026a9bd6da5394e7b37f07dbe91f73db9c 100644 (file)
@@ -58,7 +58,7 @@ int cmd_bundle(int argc, const char **argv, const char *prefix)
        } else if (!strcmp(cmd, "unbundle")) {
                if (!startup_info->have_repository)
                        die(_("Need a repository to unbundle."));
-               return !!unbundle(&header, bundle_fd) ||
+               return !!unbundle(&header, bundle_fd, 0) ||
                        list_bundle_refs(&header, argc, argv);
        } else
                usage(builtin_bundle_usage);
index 3016d29caa610caf4618e9bc1684a532fd3a18a1..708988a0e1a318fb737624f489d01e0686cd0611 100644 (file)
@@ -4,28 +4,28 @@
 #include "quote.h"
 #include "parse-options.h"
 
+static int all_attrs;
 static int stdin_paths;
 static const char * const check_attr_usage[] = {
-"git check-attr attr... [--] pathname...",
-"git check-attr --stdin attr... < <list-of-paths>",
+"git check-attr [-a | --all | attr...] [--] pathname...",
+"git check-attr --stdin [-a | --all | attr...] < <list-of-paths>",
 NULL
 };
 
 static int null_term_line;
 
 static const struct option check_attr_options[] = {
+       OPT_BOOLEAN('a', "all", &all_attrs, "report all attributes set on file"),
        OPT_BOOLEAN(0 , "stdin", &stdin_paths, "read file names from stdin"),
        OPT_BOOLEAN('z', NULL, &null_term_line,
                "input paths are terminated by a null character"),
        OPT_END()
 };
 
-static void check_attr(int cnt, struct git_attr_check *check,
-       const char** name, const char *file)
+static void output_attr(int cnt, struct git_attr_check *check,
+       const char *file)
 {
        int j;
-       if (git_checkattr(file, cnt, check))
-               die("git_checkattr died");
        for (j = 0; j < cnt; j++) {
                const char *value = check[j].value;
 
@@ -37,12 +37,30 @@ static void check_attr(int cnt, struct git_attr_check *check,
                        value = "unspecified";
 
                quote_c_style(file, NULL, stdout, 0);
-               printf(": %s: %s\n", name[j], value);
+               printf(": %s: %s\n", git_attr_name(check[j].attr), value);
        }
 }
 
-static void check_attr_stdin_paths(int cnt, struct git_attr_check *check,
-       const char** name)
+static void check_attr(const char *prefix, int cnt,
+       struct git_attr_check *check, const char *file)
+{
+       char *full_path =
+               prefix_path(prefix, prefix ? strlen(prefix) : 0, file);
+       if (check != NULL) {
+               if (git_check_attr(full_path, cnt, check))
+                       die("git_check_attr died");
+               output_attr(cnt, check, file);
+       } else {
+               if (git_all_attrs(full_path, &cnt, &check))
+                       die("git_all_attrs died");
+               output_attr(cnt, check, file);
+               free(check);
+       }
+       free(full_path);
+}
+
+static void check_attr_stdin_paths(const char *prefix, int cnt,
+       struct git_attr_check *check)
 {
        struct strbuf buf, nbuf;
        int line_termination = null_term_line ? 0 : '\n';
@@ -56,23 +74,26 @@ static void check_attr_stdin_paths(int cnt, struct git_attr_check *check,
                                die("line is badly quoted");
                        strbuf_swap(&buf, &nbuf);
                }
-               check_attr(cnt, check, name, buf.buf);
+               check_attr(prefix, cnt, check, buf.buf);
                maybe_flush_or_die(stdout, "attribute to stdout");
        }
        strbuf_release(&buf);
        strbuf_release(&nbuf);
 }
 
+static NORETURN void error_with_usage(const char *msg)
+{
+       error("%s", msg);
+       usage_with_options(check_attr_usage, check_attr_options);
+}
+
 int cmd_check_attr(int argc, const char **argv, const char *prefix)
 {
        struct git_attr_check *check;
-       int cnt, i, doubledash;
-       const char *errstr = NULL;
+       int cnt, i, doubledash, filei;
 
        argc = parse_options(argc, argv, prefix, check_attr_options,
                             check_attr_usage, PARSE_OPT_KEEP_DASHDASH);
-       if (!argc)
-               usage_with_options(check_attr_usage, check_attr_options);
 
        if (read_cache() < 0) {
                die("invalid cache");
@@ -84,39 +105,63 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix)
                        doubledash = i;
        }
 
-       /* If there is no double dash, we handle only one attribute */
-       if (doubledash < 0) {
-               cnt = 1;
-               doubledash = 0;
-       } else
+       /* Process --all and/or attribute arguments: */
+       if (all_attrs) {
+               if (doubledash >= 1)
+                       error_with_usage("Attributes and --all both specified");
+
+               cnt = 0;
+               filei = doubledash + 1;
+       } else if (doubledash == 0) {
+               error_with_usage("No attribute specified");
+       } else if (doubledash < 0) {
+               if (!argc)
+                       error_with_usage("No attribute specified");
+
+               if (stdin_paths) {
+                       /* Treat all arguments as attribute names. */
+                       cnt = argc;
+                       filei = argc;
+               } else {
+                       /* Treat exactly one argument as an attribute name. */
+                       cnt = 1;
+                       filei = 1;
+               }
+       } else {
                cnt = doubledash;
-       doubledash++;
-
-       if (cnt <= 0)
-               errstr = "No attribute specified";
-       else if (stdin_paths && doubledash < argc)
-               errstr = "Can't specify files with --stdin";
-       if (errstr) {
-               error("%s", errstr);
-               usage_with_options(check_attr_usage, check_attr_options);
+               filei = doubledash + 1;
        }
 
-       check = xcalloc(cnt, sizeof(*check));
-       for (i = 0; i < cnt; i++) {
-               const char *name;
-               struct git_attr *a;
-               name = argv[i];
-               a = git_attr(name);
-               if (!a)
-                       return error("%s: not a valid attribute name", name);
-               check[i].attr = a;
+       /* Check file argument(s): */
+       if (stdin_paths) {
+               if (filei < argc)
+                       error_with_usage("Can't specify files with --stdin");
+       } else {
+               if (filei >= argc)
+                       error_with_usage("No file specified");
+       }
+
+       if (all_attrs) {
+               check = NULL;
+       } else {
+               check = xcalloc(cnt, sizeof(*check));
+               for (i = 0; i < cnt; i++) {
+                       const char *name;
+                       struct git_attr *a;
+                       name = argv[i];
+                       a = git_attr(name);
+                       if (!a)
+                               return error("%s: not a valid attribute name",
+                                       name);
+                       check[i].attr = a;
+               }
        }
 
        if (stdin_paths)
-               check_attr_stdin_paths(cnt, check, argv);
+               check_attr_stdin_paths(prefix, cnt, check);
        else {
-               for (i = doubledash; i < argc; i++)
-                       check_attr(cnt, check, argv, argv[i]);
+               for (i = filei; i < argc; i++)
+                       check_attr(prefix, cnt, check, argv[i]);
                maybe_flush_or_die(stdout, "attribute to stdout");
        }
        return 0;
index ae3f28115a7a4d65d8bb6f284cd1ececa4f9c2ef..0723cf245e52e6c0ab99cbe92c74010f9f7d7167 100644 (file)
@@ -12,8 +12,8 @@ static const char builtin_check_ref_format_usage[] =
 "   or: git check-ref-format --branch <branchname-shorthand>";
 
 /*
- * Replace each run of adjacent slashes in src with a single slash,
- * and write the result to dst.
+ * Remove leading slashes and replace each run of adjacent slashes in
+ * src with a single slash, and write the result to dst.
  *
  * This function is similar to normalize_path_copy(), but stripped down
  * to meet check_ref_format's simpler needs.
@@ -21,7 +21,7 @@ static const char builtin_check_ref_format_usage[] =
 static void collapse_slashes(char *dst, const char *src)
 {
        char ch;
-       char prev = '\0';
+       char prev = '/';
 
        while ((ch = *src++) != '\0') {
                if (prev == '/' && ch == prev)
index f1fec24745a854e77555a1b4bea3bc8d7ab04367..c16d82b7de652026b70fc3d949ddc849f749eee8 100644 (file)
@@ -3,38 +3,6 @@
  *
  * Copyright (C) 2005 Linus Torvalds
  *
- * Careful: order of argument flags does matter. For example,
- *
- *     git checkout-index -a -f file.c
- *
- * Will first check out all files listed in the cache (but not
- * overwrite any old ones), and then force-checkout "file.c" a
- * second time (ie that one _will_ overwrite any old contents
- * with the same filename).
- *
- * Also, just doing "git checkout-index" does nothing. You probably
- * meant "git checkout-index -a". And if you want to force it, you
- * want "git checkout-index -f -a".
- *
- * Intuitiveness is not the goal here. Repeatability is. The
- * reason for the "no arguments means no work" thing is that
- * from scripts you are supposed to be able to do things like
- *
- *     find . -name '*.h' -print0 | xargs -0 git checkout-index -f --
- *
- * or:
- *
- *     find . -name '*.h' -print0 | git checkout-index -f -z --stdin
- *
- * which will force all existing *.h files to be replaced with
- * their cached copies. If an empty command line implied "all",
- * then this would force-refresh everything in the cache, which
- * was not the point.
- *
- * Oh, and the "--" is just a good idea when you know the rest
- * will be filenames. Just so that you wouldn't have a filename
- * of "-a" causing problems (not possible in the above example,
- * but get used to it in scripting!).
  */
 #include "builtin.h"
 #include "cache.h"
index bb056e43bf96ffb2ad84b2a4d05c75e783345b60..a7a493cecf08b20f32e4161dd5009fd2c3b97448 100644 (file)
@@ -72,7 +72,7 @@ static int update_some(const unsigned char *sha1, const char *base, int baselen,
        hashcpy(ce->sha1, sha1);
        memcpy(ce->name, base, baselen);
        memcpy(ce->name + baselen, pathname, len - baselen);
-       ce->ce_flags = create_ce_flags(len, 0);
+       ce->ce_flags = create_ce_flags(len, 0) | CE_UPDATE;
        ce->ce_mode = create_ce_mode(mode);
        add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
        return 0;
@@ -202,7 +202,7 @@ static int checkout_merged(int pos, struct checkout *state)
 }
 
 static int checkout_paths(struct tree *source_tree, const char **pathspec,
-                         struct checkout_opts *opts)
+                         const char *prefix, struct checkout_opts *opts)
 {
        int pos;
        struct checkout state;
@@ -229,10 +229,12 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
 
        for (pos = 0; pos < active_nr; pos++) {
                struct cache_entry *ce = active_cache[pos];
+               if (source_tree && !(ce->ce_flags & CE_UPDATE))
+                       continue;
                match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, ps_matched);
        }
 
-       if (report_path_error(ps_matched, pathspec, 0))
+       if (report_path_error(ps_matched, pathspec, prefix))
                return 1;
 
        /* "checkout -m path" to recreate conflicted state */
@@ -267,6 +269,8 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
        state.refresh_cache = 1;
        for (pos = 0; pos < active_nr; pos++) {
                struct cache_entry *ce = active_cache[pos];
+               if (source_tree && !(ce->ce_flags & CE_UPDATE))
+                       continue;
                if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
                        if (!ce_stage(ce)) {
                                errs |= checkout_entry(ce, &state, NULL);
@@ -646,24 +650,25 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs)
                "Warning: you are leaving %d commit behind, "
                "not connected to\n"
                "any of your branches:\n\n"
-               "%s\n"
-               "If you want to keep it by creating a new branch, "
-               "this may be a good time\nto do so with:\n\n"
-               " git branch new_branch_name %s\n\n",
+               "%s\n",
                /* The plural version */
                "Warning: you are leaving %d commits behind, "
                "not connected to\n"
                "any of your branches:\n\n"
-               "%s\n"
-               "If you want to keep them by creating a new branch, "
-               "this may be a good time\nto do so with:\n\n"
-               " git branch new_branch_name %s\n\n",
+               "%s\n",
                /* Give ngettext() the count */
                lost),
                lost,
-               sb.buf,
-               sha1_to_hex(commit->object.sha1));
+               sb.buf);
        strbuf_release(&sb);
+
+       if (advice_detached_head)
+               fprintf(stderr,
+                       _(
+                       "If you want to keep them by creating a new branch, "
+                       "this may be a good time\nto do so with:\n\n"
+                       " git branch new_branch_name %s\n\n"),
+                       sha1_to_hex(commit->object.sha1));
 }
 
 /*
@@ -704,10 +709,12 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
        unsigned char rev[20];
        int flag;
        memset(&old, 0, sizeof(old));
-       old.path = resolve_ref("HEAD", rev, 0, &flag);
+       old.path = xstrdup(resolve_ref("HEAD", rev, 0, &flag));
        old.commit = lookup_commit_reference_gently(rev, 1);
-       if (!(flag & REF_ISSYMREF))
+       if (!(flag & REF_ISSYMREF)) {
+               free((char *)old.path);
                old.path = NULL;
+       }
 
        if (old.path && !prefixcmp(old.path, "refs/heads/"))
                old.name = old.path + strlen("refs/heads/");
@@ -730,6 +737,7 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
        update_refs_for_switch(opts, &old, new);
 
        ret = post_checkout_hook(old.commit, new->commit, 1);
+       free((char *)old.path);
        return ret || opts->writeout_error;
 }
 
@@ -1049,7 +1057,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
                        die(_("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index."));
 
-               return checkout_paths(source_tree, pathspec, &opts);
+               return checkout_paths(source_tree, pathspec, prefix, &opts);
        }
 
        if (patch_mode)
@@ -1057,15 +1065,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
        if (opts.new_branch) {
                struct strbuf buf = STRBUF_INIT;
-               if (strbuf_check_branch_ref(&buf, opts.new_branch))
-                       die(_("git checkout: we do not like '%s' as a branch name."),
-                           opts.new_branch);
-               if (!get_sha1(buf.buf, rev)) {
-                       opts.branch_exists = 1;
-                       if (!opts.new_branch_force)
-                               die(_("git checkout: branch %s already exists"),
-                                   opts.new_branch);
-               }
+
+               opts.branch_exists = validate_new_branchname(opts.new_branch, &buf,
+                                                            !!opts.new_branch_force, 0);
+
                strbuf_release(&buf);
        }
 
index 75697f711116e42df1e21e608c21829f49549b08..0c7b3d0f4c28c9e9c721c961e529196751271d11 100644 (file)
@@ -54,7 +54,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
                OPT_BOOLEAN('d', NULL, &remove_directories,
                                "remove whole directories"),
                { OPTION_CALLBACK, 'e', "exclude", &exclude_list, "pattern",
-                 "exclude <pattern>", PARSE_OPT_NONEG, exclude_cb },
+                 "add <pattern> to ignore rules", PARSE_OPT_NONEG, exclude_cb },
                OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"),
                OPT_BOOLEAN('X', NULL, &ignored_only,
                                "remove only ignored files"),
@@ -98,7 +98,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
                setup_standard_excludes(&dir);
 
        for (i = 0; i < exclude_list.nr; i++)
-               add_exclude(exclude_list.items[i].string, "", 0, dir.exclude_list);
+               add_exclude(exclude_list.items[i].string, "", 0,
+                           &dir.exclude_list[EXC_CMDL]);
 
        pathspec = get_pathspec(prefix, argv);
 
index f579794d9a93a0e55289921f20b8f68b85211ca1..488f48e9a571fa9b958cd95b92808b44f4541982 100644 (file)
@@ -39,13 +39,24 @@ static const char * const builtin_clone_usage[] = {
 
 static int option_no_checkout, option_bare, option_mirror;
 static int option_local, option_no_hardlinks, option_shared, option_recursive;
-static char *option_template, *option_reference, *option_depth;
+static char *option_template, *option_depth;
 static char *option_origin = NULL;
 static char *option_branch = NULL;
 static const char *real_git_dir;
 static char *option_upload_pack = "git-upload-pack";
 static int option_verbosity;
 static int option_progress;
+static struct string_list option_config;
+static struct string_list option_reference;
+
+static int opt_parse_reference(const struct option *opt, const char *arg, int unset)
+{
+       struct string_list *option_reference = opt->value;
+       if (!arg)
+               return -1;
+       string_list_append(option_reference, arg);
+       return 0;
+}
 
 static struct option builtin_clone_options[] = {
        OPT__VERBOSITY(&option_verbosity),
@@ -71,8 +82,8 @@ static struct option builtin_clone_options[] = {
                    "initialize submodules in the clone"),
        OPT_STRING(0, "template", &option_template, "template-directory",
                   "directory from which templates will be used"),
-       OPT_STRING(0, "reference", &option_reference, "repo",
-                  "reference repository"),
+       OPT_CALLBACK(0 , "reference", &option_reference, "repo",
+                    "reference repository", &opt_parse_reference),
        OPT_STRING('o', "origin", &option_origin, "branch",
                   "use <branch> instead of 'origin' to track upstream"),
        OPT_STRING('b', "branch", &option_branch, "branch",
@@ -83,7 +94,8 @@ static struct option builtin_clone_options[] = {
                    "create a shallow clone of that depth"),
        OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir",
                   "separate git dir from working tree"),
-
+       OPT_STRING_LIST('c', "config", &option_config, "key=value",
+                       "set config inside the new repository"),
        OPT_END()
 };
 
@@ -101,9 +113,26 @@ static char *get_repo_path(const char *repo, int *is_bundle)
        for (i = 0; i < ARRAY_SIZE(suffix); i++) {
                const char *path;
                path = mkpath("%s%s", repo, suffix[i]);
-               if (is_directory(path)) {
+               if (stat(path, &st))
+                       continue;
+               if (S_ISDIR(st.st_mode)) {
                        *is_bundle = 0;
                        return xstrdup(absolute_path(path));
+               } else if (S_ISREG(st.st_mode) && st.st_size > 8) {
+                       /* Is it a "gitfile"? */
+                       char signature[8];
+                       int len, fd = open(path, O_RDONLY);
+                       if (fd < 0)
+                               continue;
+                       len = read_in_full(fd, signature, 8);
+                       close(fd);
+                       if (len != 8 || strncmp(signature, "gitdir: ", 8))
+                               continue;
+                       path = read_gitfile(path);
+                       if (path) {
+                               *is_bundle = 0;
+                               return xstrdup(absolute_path(path));
+                       }
                }
        }
 
@@ -197,39 +226,80 @@ static void strip_trailing_slashes(char *dir)
        *end = '\0';
 }
 
-static void setup_reference(const char *repo)
+static int add_one_reference(struct string_list_item *item, void *cb_data)
 {
-       const char *ref_git;
-       char *ref_git_copy;
-
+       char *ref_git;
+       struct strbuf alternate = STRBUF_INIT;
        struct remote *remote;
        struct transport *transport;
        const struct ref *extra;
 
-       ref_git = real_path(option_reference);
-
-       if (is_directory(mkpath("%s/.git/objects", ref_git)))
-               ref_git = mkpath("%s/.git", ref_git);
-       else if (!is_directory(mkpath("%s/objects", ref_git)))
+       /* Beware: real_path() and mkpath() return static buffer */
+       ref_git = xstrdup(real_path(item->string));
+       if (is_directory(mkpath("%s/.git/objects", ref_git))) {
+               char *ref_git_git = xstrdup(mkpath("%s/.git", ref_git));
+               free(ref_git);
+               ref_git = ref_git_git;
+       } else if (!is_directory(mkpath("%s/objects", ref_git)))
                die(_("reference repository '%s' is not a local directory."),
-                   option_reference);
+                   item->string);
 
-       ref_git_copy = xstrdup(ref_git);
+       strbuf_addf(&alternate, "%s/objects", ref_git);
+       add_to_alternates_file(alternate.buf);
+       strbuf_release(&alternate);
 
-       add_to_alternates_file(ref_git_copy);
-
-       remote = remote_get(ref_git_copy);
-       transport = transport_get(remote, ref_git_copy);
+       remote = remote_get(ref_git);
+       transport = transport_get(remote, ref_git);
        for (extra = transport_get_remote_refs(transport); extra;
             extra = extra->next)
                add_extra_ref(extra->name, extra->old_sha1, 0);
 
        transport_disconnect(transport);
+       free(ref_git);
+       return 0;
+}
 
-       free(ref_git_copy);
+static void setup_reference(void)
+{
+       for_each_string_list(&option_reference, add_one_reference, NULL);
 }
 
-static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest)
+static void copy_alternates(struct strbuf *src, struct strbuf *dst,
+                           const char *src_repo)
+{
+       /*
+        * Read from the source objects/info/alternates file
+        * and copy the entries to corresponding file in the
+        * destination repository with add_to_alternates_file().
+        * Both src and dst have "$path/objects/info/alternates".
+        *
+        * Instead of copying bit-for-bit from the original,
+        * we need to append to existing one so that the already
+        * created entry via "clone -s" is not lost, and also
+        * to turn entries with paths relative to the original
+        * absolute, so that they can be used in the new repository.
+        */
+       FILE *in = fopen(src->buf, "r");
+       struct strbuf line = STRBUF_INIT;
+
+       while (strbuf_getline(&line, in, '\n') != EOF) {
+               char *abs_path, abs_buf[PATH_MAX];
+               if (!line.len || line.buf[0] == '#')
+                       continue;
+               if (is_absolute_path(line.buf)) {
+                       add_to_alternates_file(line.buf);
+                       continue;
+               }
+               abs_path = mkpath("%s/objects/%s", src_repo, line.buf);
+               normalize_path_copy(abs_buf, abs_path);
+               add_to_alternates_file(abs_buf);
+       }
+       strbuf_release(&line);
+       fclose(in);
+}
+
+static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
+                                  const char *src_repo, int src_baselen)
 {
        struct dirent *de;
        struct stat buf;
@@ -265,7 +335,14 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest)
                }
                if (S_ISDIR(buf.st_mode)) {
                        if (de->d_name[0] != '.')
-                               copy_or_link_directory(src, dest);
+                               copy_or_link_directory(src, dest,
+                                                      src_repo, src_baselen);
+                       continue;
+               }
+
+               /* Files that cannot be copied bit-for-bit... */
+               if (!strcmp(src->buf + src_baselen, "/info/alternates")) {
+                       copy_alternates(src, dest, src_repo);
                        continue;
                }
 
@@ -288,17 +365,20 @@ static const struct ref *clone_local(const char *src_repo,
                                     const char *dest_repo)
 {
        const struct ref *ret;
-       struct strbuf src = STRBUF_INIT;
-       struct strbuf dest = STRBUF_INIT;
        struct remote *remote;
        struct transport *transport;
 
-       if (option_shared)
-               add_to_alternates_file(src_repo);
-       else {
+       if (option_shared) {
+               struct strbuf alt = STRBUF_INIT;
+               strbuf_addf(&alt, "%s/objects", src_repo);
+               add_to_alternates_file(alt.buf);
+               strbuf_release(&alt);
+       } else {
+               struct strbuf src = STRBUF_INIT;
+               struct strbuf dest = STRBUF_INIT;
                strbuf_addf(&src, "%s/objects", src_repo);
                strbuf_addf(&dest, "%s/objects", dest_repo);
-               copy_or_link_directory(&src, &dest);
+               copy_or_link_directory(&src, &dest, src_repo, src.len);
                strbuf_release(&src);
                strbuf_release(&dest);
        }
@@ -343,8 +423,9 @@ static void remove_junk_on_signal(int signo)
 static struct ref *wanted_peer_refs(const struct ref *refs,
                struct refspec *refspec)
 {
-       struct ref *local_refs = NULL;
-       struct ref **tail = &local_refs;
+       struct ref *head = copy_ref(find_ref_by_name(refs, "HEAD"));
+       struct ref *local_refs = head;
+       struct ref **tail = head ? &head->next : &local_refs;
 
        get_fetch_map(refs, refspec, &tail, 0);
        if (!option_mirror)
@@ -357,13 +438,32 @@ static void write_remote_refs(const struct ref *local_refs)
 {
        const struct ref *r;
 
-       for (r = local_refs; r; r = r->next)
+       for (r = local_refs; r; r = r->next) {
+               if (!r->peer_ref)
+                       continue;
                add_extra_ref(r->peer_ref->name, r->old_sha1, 0);
+       }
 
        pack_refs(PACK_REFS_ALL);
        clear_extra_refs();
 }
 
+static int write_one_config(const char *key, const char *value, void *data)
+{
+       return git_config_set_multivar(key, value ? value : "true", "^$", 0);
+}
+
+static void write_config(struct string_list *config)
+{
+       int i;
+
+       for (i = 0; i < config->nr; i++) {
+               if (git_config_parse_parameter(config->items[i].string,
+                                              write_one_config, NULL) < 0)
+                       die("unable to write parameters to config file");
+       }
+}
+
 int cmd_clone(int argc, const char **argv, const char *prefix)
 {
        int is_bundle = 0, is_local;
@@ -482,6 +582,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                        printf(_("Cloning into %s...\n"), dir);
        }
        init_db(option_template, INIT_DB_QUIET);
+       write_config(&option_config);
 
        /*
         * At this point, the config exists, so we do not need the
@@ -521,8 +622,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        git_config_set(key.buf, repo);
        strbuf_reset(&key);
 
-       if (option_reference)
-               setup_reference(git_dir);
+       if (option_reference.nr)
+               setup_reference();
 
        fetch_pattern = value.buf;
        refspec = parse_fetch_refspec(1, &fetch_pattern);
index e1af9b19f0be71484ae9341762dc2bf89cabb70c..66ffe31736933d1ad15fdeb7bb321f10fcab1dd3 100644 (file)
@@ -62,8 +62,6 @@ N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\
 "\n"
 "Otherwise, please use 'git reset'\n");
 
-static unsigned char head_sha1[20];
-
 static const char *use_message_buffer;
 static const char commit_editmsg[] = "COMMIT_EDITMSG";
 static struct lock_file index_lock; /* real index */
@@ -102,7 +100,7 @@ static enum {
 static char *cleanup_arg;
 
 static enum commit_whence whence;
-static int use_editor = 1, initial_commit, include_status = 1;
+static int use_editor = 1, include_status = 1;
 static int show_ignored_in_status;
 static const char *only_include_assumed;
 static struct strbuf message;
@@ -256,8 +254,10 @@ static int list_paths(struct string_list *list, const char *with_tree,
                ;
        m = xcalloc(1, i);
 
-       if (with_tree)
-               overlay_tree_on_cache(with_tree, prefix);
+       if (with_tree) {
+               const char *max_prefix = pathspec_prefix(prefix, pattern);
+               overlay_tree_on_cache(with_tree, max_prefix);
+       }
 
        for (i = 0; i < active_nr; i++) {
                struct cache_entry *ce = active_cache[i];
@@ -272,7 +272,7 @@ static int list_paths(struct string_list *list, const char *with_tree,
                        item->util = item; /* better a valid pointer than a fake one */
        }
 
-       return report_path_error(m, pattern, prefix ? strlen(prefix) : 0);
+       return report_path_error(m, pattern, prefix);
 }
 
 static void add_remove_files(struct string_list *list)
@@ -294,13 +294,13 @@ static void add_remove_files(struct string_list *list)
        }
 }
 
-static void create_base_index(void)
+static void create_base_index(const struct commit *current_head)
 {
        struct tree *tree;
        struct unpack_trees_options opts;
        struct tree_desc t;
 
-       if (initial_commit) {
+       if (!current_head) {
                discard_cache();
                return;
        }
@@ -313,7 +313,7 @@ static void create_base_index(void)
        opts.dst_index = &the_index;
 
        opts.fn = oneway_merge;
-       tree = parse_tree_indirect(head_sha1);
+       tree = parse_tree_indirect(current_head->object.sha1);
        if (!tree)
                die(_("failed to unpack HEAD tree object"));
        parse_tree(tree);
@@ -332,7 +332,8 @@ static void refresh_cache_or_die(int refresh_flags)
                die_resolve_conflict("commit");
 }
 
-static char *prepare_index(int argc, const char **argv, const char *prefix, int is_status)
+static char *prepare_index(int argc, const char **argv, const char *prefix,
+                          const struct commit *current_head, int is_status)
 {
        int fd;
        struct string_list partial;
@@ -448,7 +449,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
 
        memset(&partial, 0, sizeof(partial));
        partial.strdup_strings = 1;
-       if (list_paths(&partial, initial_commit ? NULL : "HEAD", prefix, pathspec))
+       if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, pathspec))
                exit(1);
 
        discard_cache();
@@ -467,7 +468,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
                                                (uintmax_t) getpid()),
                                       LOCK_DIE_ON_ERROR);
 
-       create_base_index();
+       create_base_index(current_head);
        add_remove_files(&partial);
        refresh_cache(REFRESH_QUIET);
 
@@ -516,12 +517,9 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int
        return s->commitable;
 }
 
-static int is_a_merge(const unsigned char *sha1)
+static int is_a_merge(const struct commit *current_head)
 {
-       struct commit *commit = lookup_commit(sha1);
-       if (!commit || parse_commit(commit))
-               die(_("could not parse HEAD commit"));
-       return !!(commit->parents && commit->parents->next);
+       return !!(current_head->parents && current_head->parents->next);
 }
 
 static const char sign_off_header[] = "Signed-off-by: ";
@@ -625,6 +623,7 @@ static char *cut_ident_timestamp_part(char *string)
 }
 
 static int prepare_to_commit(const char *index_file, const char *prefix,
+                            struct commit *current_head,
                             struct wt_status *s,
                             struct strbuf *author_ident)
 {
@@ -846,7 +845,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
         * empty due to conflict resolution, which the user should okay.
         */
        if (!commitable && whence != FROM_MERGE && !allow_empty &&
-           !(amend && is_a_merge(head_sha1))) {
+           !(amend && is_a_merge(current_head))) {
                run_status(stdout, index_file, prefix, 0, s);
                if (amend)
                        fputs(_(empty_amend_advice), stderr);
@@ -1004,6 +1003,7 @@ static const char *read_commit_message(const char *name)
 static int parse_and_validate_options(int argc, const char *argv[],
                                      const char * const usage[],
                                      const char *prefix,
+                                     struct commit *current_head,
                                      struct wt_status *s)
 {
        int f = 0;
@@ -1024,11 +1024,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
        if (!use_editor)
                setenv("GIT_EDITOR", ":", 1);
 
-       if (get_sha1("HEAD", head_sha1))
-               initial_commit = 1;
-
        /* Sanity check options */
-       if (amend && initial_commit)
+       if (amend && !current_head)
                die(_("You have nothing to amend."));
        if (amend && whence != FROM_COMMIT)
                die(_("You are in the middle of a %s -- cannot amend."), whence_s());
@@ -1100,12 +1097,12 @@ static int parse_and_validate_options(int argc, const char *argv[],
 }
 
 static int dry_run_commit(int argc, const char **argv, const char *prefix,
-                         struct wt_status *s)
+                         const struct commit *current_head, struct wt_status *s)
 {
        int commitable;
        const char *index_file;
 
-       index_file = prepare_index(argc, argv, prefix, 1);
+       index_file = prepare_index(argc, argv, prefix, current_head, 1);
        commitable = run_status(stdout, index_file, prefix, 0, s);
        rollback_index_files();
 
@@ -1144,7 +1141,7 @@ static int git_status_config(const char *k, const char *v, void *cb)
                return 0;
        }
        if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
-               s->use_color = git_config_colorbool(k, v, -1);
+               s->use_color = git_config_colorbool(k, v);
                return 0;
        }
        if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) {
@@ -1237,10 +1234,6 @@ int cmd_status(int argc, const char **argv, const char *prefix)
 
        if (s.relative_paths)
                s.prefix = prefix;
-       if (s.use_color == -1)
-               s.use_color = git_use_color_default;
-       if (diff_use_color_default == -1)
-               diff_use_color_default = git_use_color_default;
 
        switch (status_format) {
        case STATUS_FORMAT_SHORT:
@@ -1258,7 +1251,8 @@ int cmd_status(int argc, const char **argv, const char *prefix)
        return 0;
 }
 
-static void print_summary(const char *prefix, const unsigned char *sha1)
+static void print_summary(const char *prefix, const unsigned char *sha1,
+                         int initial_commit)
 {
        struct rev_info rev;
        struct commit *commit;
@@ -1380,12 +1374,13 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
        struct strbuf author_ident = STRBUF_INIT;
        const char *index_file, *reflog_msg;
        char *nl, *p;
-       unsigned char commit_sha1[20];
+       unsigned char sha1[20];
        struct ref_lock *ref_lock;
        struct commit_list *parents = NULL, **pptr = &parents;
        struct stat statbuf;
        int allow_fast_forward = 1;
        struct wt_status s;
+       struct commit *current_head = NULL;
 
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_commit_usage, builtin_commit_options);
@@ -1394,48 +1389,47 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
        git_config(git_commit_config, &s);
        determine_whence(&s);
 
-       if (s.use_color == -1)
-               s.use_color = git_use_color_default;
-       argc = parse_and_validate_options(argc, argv, builtin_commit_usage,
-                                         prefix, &s);
-       if (dry_run) {
-               if (diff_use_color_default == -1)
-                       diff_use_color_default = git_use_color_default;
-               return dry_run_commit(argc, argv, prefix, &s);
+       if (get_sha1("HEAD", sha1))
+               current_head = NULL;
+       else {
+               current_head = lookup_commit_or_die(sha1, "HEAD");
+               if (!current_head || parse_commit(current_head))
+                       die(_("could not parse HEAD commit"));
        }
-       index_file = prepare_index(argc, argv, prefix, 0);
+       argc = parse_and_validate_options(argc, argv, builtin_commit_usage,
+                                         prefix, current_head, &s);
+       if (dry_run)
+               return dry_run_commit(argc, argv, prefix, current_head, &s);
+       index_file = prepare_index(argc, argv, prefix, current_head, 0);
 
        /* Set up everything for writing the commit object.  This includes
           running hooks, writing the trees, and interacting with the user.  */
-       if (!prepare_to_commit(index_file, prefix, &s, &author_ident)) {
+       if (!prepare_to_commit(index_file, prefix,
+                              current_head, &s, &author_ident)) {
                rollback_index_files();
                return 1;
        }
 
        /* Determine parents */
        reflog_msg = getenv("GIT_REFLOG_ACTION");
-       if (initial_commit) {
+       if (!current_head) {
                if (!reflog_msg)
                        reflog_msg = "commit (initial)";
        } else if (amend) {
                struct commit_list *c;
-               struct commit *commit;
 
                if (!reflog_msg)
                        reflog_msg = "commit (amend)";
-               commit = lookup_commit(head_sha1);
-               if (!commit || parse_commit(commit))
-                       die(_("could not parse HEAD commit"));
-
-               for (c = commit->parents; c; c = c->next)
+               for (c = current_head->parents; c; c = c->next)
                        pptr = &commit_list_insert(c->item, pptr)->next;
        } else if (whence == FROM_MERGE) {
                struct strbuf m = STRBUF_INIT;
+               struct commit *commit;
                FILE *fp;
 
                if (!reflog_msg)
                        reflog_msg = "commit (merge)";
-               pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
+               pptr = &commit_list_insert(current_head, pptr)->next;
                fp = fopen(git_path("MERGE_HEAD"), "r");
                if (fp == NULL)
                        die_errno(_("could not open '%s' for reading"),
@@ -1444,7 +1438,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                        unsigned char sha1[20];
                        if (get_sha1_hex(m.buf, sha1) < 0)
                                die(_("Corrupt MERGE_HEAD file (%s)"), m.buf);
-                       pptr = &commit_list_insert(lookup_commit(sha1), pptr)->next;
+                       commit = lookup_commit_or_die(sha1, "MERGE_HEAD");
+                       pptr = &commit_list_insert(commit, pptr)->next;
                }
                fclose(fp);
                strbuf_release(&m);
@@ -1461,7 +1456,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                        reflog_msg = (whence == FROM_CHERRY_PICK)
                                        ? "commit (cherry-pick)"
                                        : "commit";
-               pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
+               pptr = &commit_list_insert(current_head, pptr)->next;
        }
 
        /* Finally, get the commit message */
@@ -1487,7 +1482,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                exit(1);
        }
 
-       if (commit_tree(sb.buf, active_cache_tree->sha1, parents, commit_sha1,
+       if (commit_tree(sb.buf, active_cache_tree->sha1, parents, sha1,
                        author_ident.buf)) {
                rollback_index_files();
                die(_("failed to write commit object"));
@@ -1495,7 +1490,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
        strbuf_release(&author_ident);
 
        ref_lock = lock_any_ref_for_update("HEAD",
-                                          initial_commit ? NULL : head_sha1,
+                                          !current_head
+                                          ? NULL
+                                          : current_head->object.sha1,
                                           0);
 
        nl = strchr(sb.buf, '\n');
@@ -1510,7 +1507,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                rollback_index_files();
                die(_("cannot lock HEAD ref"));
        }
-       if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0) {
+       if (write_ref_sha1(ref_lock, sha1, sb.buf) < 0) {
                rollback_index_files();
                die(_("cannot update HEAD ref"));
        }
@@ -1532,13 +1529,14 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                struct notes_rewrite_cfg *cfg;
                cfg = init_copy_notes_for_rewrite("amend");
                if (cfg) {
-                       copy_note_for_rewrite(cfg, head_sha1, commit_sha1);
+                       /* we are amending, so current_head is not NULL */
+                       copy_note_for_rewrite(cfg, current_head->object.sha1, sha1);
                        finish_copy_notes_for_rewrite(cfg);
                }
-               run_rewrite_hook(head_sha1, commit_sha1);
+               run_rewrite_hook(current_head->object.sha1, sha1);
        }
        if (!quiet)
-               print_summary(prefix, commit_sha1);
+               print_summary(prefix, sha1, !current_head);
 
        return 0;
 }
index 211e118d575411b712ccf0387db2408708576902..0315ad76f881aab9a64084f8d2fdd2019907e4cb 100644 (file)
@@ -99,6 +99,7 @@ static int show_config(const char *key_, const char *value_, void *cb)
        const char *vptr = value;
        int must_free_vptr = 0;
        int dup_error = 0;
+       int must_print_delim = 0;
 
        if (!use_key_regexp && strcmp(key_, key))
                return 0;
@@ -109,10 +110,8 @@ static int show_config(const char *key_, const char *value_, void *cb)
                return 0;
 
        if (show_keys) {
-               if (value_)
-                       printf("%s%c", key_, key_delim);
-               else
-                       printf("%s", key_);
+               printf("%s", key_);
+               must_print_delim = 1;
        }
        if (seen && !do_all)
                dup_error = 1;
@@ -130,16 +129,23 @@ static int show_config(const char *key_, const char *value_, void *cb)
        } else if (types == TYPE_PATH) {
                git_config_pathname(&vptr, key_, value_);
                must_free_vptr = 1;
+       } else if (value_) {
+               vptr = value_;
+       } else {
+               /* Just show the key name */
+               vptr = "";
+               must_print_delim = 0;
        }
-       else
-               vptr = value_?value_:"";
        seen++;
        if (dup_error) {
                error("More than one value for the key %s: %s",
                                key_, vptr);
        }
-       else
+       else {
+               if (must_print_delim)
+                       printf("%c", key_delim);
                printf("%s%c", vptr, term);
+       }
        if (must_free_vptr)
                /* If vptr must be freed, it's a pointer to a
                 * dynamically allocated buffer, it's safe to cast to
@@ -303,24 +309,18 @@ static void get_color(const char *def_color)
        fputs(parsed_color, stdout);
 }
 
-static int stdout_is_tty;
 static int get_colorbool_found;
 static int get_diff_color_found;
+static int get_color_ui_found;
 static int git_get_colorbool_config(const char *var, const char *value,
                void *cb)
 {
-       if (!strcmp(var, get_colorbool_slot)) {
-               get_colorbool_found =
-                       git_config_colorbool(var, value, stdout_is_tty);
-       }
-       if (!strcmp(var, "diff.color")) {
-               get_diff_color_found =
-                       git_config_colorbool(var, value, stdout_is_tty);
-       }
-       if (!strcmp(var, "color.ui")) {
-               git_use_color_default = git_config_colorbool(var, value, stdout_is_tty);
-               return 0;
-       }
+       if (!strcmp(var, get_colorbool_slot))
+               get_colorbool_found = git_config_colorbool(var, value);
+       else if (!strcmp(var, "diff.color"))
+               get_diff_color_found = git_config_colorbool(var, value);
+       else if (!strcmp(var, "color.ui"))
+               get_color_ui_found = git_config_colorbool(var, value);
        return 0;
 }
 
@@ -334,9 +334,11 @@ static int get_colorbool(int print)
                if (!strcmp(get_colorbool_slot, "color.diff"))
                        get_colorbool_found = get_diff_color_found;
                if (get_colorbool_found < 0)
-                       get_colorbool_found = git_use_color_default;
+                       get_colorbool_found = get_color_ui_found;
        }
 
+       get_colorbool_found = want_color(get_colorbool_found);
+
        if (print) {
                printf("%s\n", get_colorbool_found ? "true" : "false");
                return 0;
@@ -510,9 +512,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
        }
        else if (actions == ACTION_GET_COLORBOOL) {
                if (argc == 1)
-                       stdout_is_tty = git_config_bool("command line", argv[0]);
-               else if (argc == 0)
-                       stdout_is_tty = isatty(1);
+                       color_stdout_is_tty = git_config_bool("command line", argv[0]);
                return get_colorbool(argc != 0);
        }
 
index 66fc291c8a81de71dee7b597c9ab0dc9d70e8e29..9f63067f50a6f49d61d40474608535905bec905b 100644 (file)
@@ -462,8 +462,21 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
                die(_("No names found, cannot describe anything."));
 
        if (argc == 0) {
-               if (dirty && !cmd_diff_index(ARRAY_SIZE(diff_index_args) - 1, diff_index_args, prefix))
-                       dirty = NULL;
+               if (dirty) {
+                       static struct lock_file index_lock;
+                       int fd;
+
+                       read_cache_preload(NULL);
+                       refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED,
+                                     NULL, NULL, NULL);
+                       fd = hold_locked_index(&index_lock, 0);
+                       if (0 <= fd)
+                               update_index_if_able(&the_index, &index_lock);
+
+                       if (!cmd_diff_index(ARRAY_SIZE(diff_index_args) - 1,
+                                           diff_index_args, prefix))
+                               dirty = NULL;
+               }
                describe("HEAD", 1);
        } else if (dirty) {
                die(_("--dirty is incompatible with committishes"));
index 69cd5eed78cb402839813e7eca65b5598afa4a90..1118689fb246b864ce758039543327c4304cdaa4 100644 (file)
@@ -277,9 +277,6 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
        gitmodules_config();
        git_config(git_diff_ui_config, NULL);
 
-       if (diff_use_color_default == -1)
-               diff_use_color_default = git_use_color_default;
-
        init_revisions(&rev, prefix);
 
        /* If this is a no-index diff, just run it and exit there. */
index daf19451ba7df541d1314c880679749232ccce35..9836e6b7ca22e254c06d8d766d510ef43a8cbe90 100644 (file)
@@ -16,6 +16,7 @@
 #include "string-list.h"
 #include "utf8.h"
 #include "parse-options.h"
+#include "quote.h"
 
 static const char *fast_export_usage[] = {
        "git fast-export [rev-list-opts]",
@@ -26,6 +27,7 @@ static int progress;
 static enum { ABORT, VERBATIM, WARN, STRIP } signed_tag_mode = ABORT;
 static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ABORT;
 static int fake_missing_tagger;
+static int use_done_feature;
 static int no_data;
 static int full_tree;
 
@@ -178,6 +180,15 @@ static int depth_first(const void *a_, const void *b_)
        return (a->status == 'R') - (b->status == 'R');
 }
 
+static void print_path(const char *path)
+{
+       int need_quote = quote_c_style(path, NULL, NULL, 0);
+       if (need_quote)
+               quote_c_style(path, NULL, stdout, 0);
+       else
+               printf("%s", path);
+}
+
 static void show_filemodify(struct diff_queue_struct *q,
                            struct diff_options *options, void *data)
 {
@@ -195,13 +206,18 @@ static void show_filemodify(struct diff_queue_struct *q,
 
                switch (q->queue[i]->status) {
                case DIFF_STATUS_DELETED:
-                       printf("D %s\n", spec->path);
+                       printf("D ");
+                       print_path(spec->path);
+                       putchar('\n');
                        break;
 
                case DIFF_STATUS_COPIED:
                case DIFF_STATUS_RENAMED:
-                       printf("%c \"%s\" \"%s\"\n", q->queue[i]->status,
-                              ospec->path, spec->path);
+                       printf("%c ", q->queue[i]->status);
+                       print_path(ospec->path);
+                       putchar(' ');
+                       print_path(spec->path);
+                       putchar('\n');
 
                        if (!hashcmp(ospec->sha1, spec->sha1) &&
                            ospec->mode == spec->mode)
@@ -216,13 +232,15 @@ static void show_filemodify(struct diff_queue_struct *q,
                         * output the SHA-1 verbatim.
                         */
                        if (no_data || S_ISGITLINK(spec->mode))
-                               printf("M %06o %s %s\n", spec->mode,
-                                      sha1_to_hex(spec->sha1), spec->path);
+                               printf("M %06o %s ", spec->mode,
+                                      sha1_to_hex(spec->sha1));
                        else {
                                struct object *object = lookup_object(spec->sha1);
-                               printf("M %06o :%d %s\n", spec->mode,
-                                      get_object_mark(object), spec->path);
+                               printf("M %06o :%d ", spec->mode,
+                                      get_object_mark(object));
                        }
+                       print_path(spec->path);
+                       putchar('\n');
                        break;
 
                default:
@@ -627,6 +645,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
                             "Fake a tagger when tags lack one"),
                OPT_BOOLEAN(0, "full-tree", &full_tree,
                             "Output full tree for each commit"),
+               OPT_BOOLEAN(0, "use-done-feature", &use_done_feature,
+                            "Use the done feature to terminate the stream"),
                { OPTION_NEGBIT, 0, "data", &no_data, NULL,
                        "Skip output of blob data",
                        PARSE_OPT_NOARG | PARSE_OPT_NEGHELP, NULL, 1 },
@@ -648,6 +668,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
        if (argc > 1)
                usage_with_options (fast_export_usage, options);
 
+       if (use_done_feature)
+               printf("feature done\n");
+
        if (import_filename)
                import_marks(import_filename);
 
@@ -675,5 +698,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
        if (export_filename)
                export_marks(export_filename);
 
+       if (use_done_feature)
+               printf("done\n");
+
        return 0;
 }
index 436798410210b868cfbe439a1603b0a340a32c50..412bd327b5b396c159c768b25be189e00531c38e 100644 (file)
@@ -185,6 +185,36 @@ static void consume_shallow_list(int fd)
        }
 }
 
+struct write_shallow_data {
+       struct strbuf *out;
+       int use_pack_protocol;
+       int count;
+};
+
+static int write_one_shallow(const struct commit_graft *graft, void *cb_data)
+{
+       struct write_shallow_data *data = cb_data;
+       const char *hex = sha1_to_hex(graft->sha1);
+       data->count++;
+       if (data->use_pack_protocol)
+               packet_buf_write(data->out, "shallow %s", hex);
+       else {
+               strbuf_addstr(data->out, hex);
+               strbuf_addch(data->out, '\n');
+       }
+       return 0;
+}
+
+static int write_shallow_commits(struct strbuf *out, int use_pack_protocol)
+{
+       struct write_shallow_data data;
+       data.out = out;
+       data.use_pack_protocol = use_pack_protocol;
+       data.count = 0;
+       for_each_commit_graft(write_one_shallow, &data);
+       return data.count;
+}
+
 static enum ack_type get_ack(int fd, unsigned char *result_sha1)
 {
        static char line[1000];
@@ -395,6 +425,8 @@ static int find_common(int fd[2], unsigned char *result_sha1,
                                case ACK_continue: {
                                        struct commit *commit =
                                                lookup_commit(result_sha1);
+                                       if (!commit)
+                                               die("invalid commit %s", sha1_to_hex(result_sha1));
                                        if (args.stateless_rpc
                                         && ack == ACK_common
                                         && !(commit->object.flags & COMMON)) {
index 93c99385a95e6ab525fa64c1dbb70d5d4ca103ba..e422ced9299521bb2e7f3c5ff59a434c8403a4ea 100644 (file)
@@ -941,6 +941,15 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
        argc = parse_options(argc, argv, prefix,
                             builtin_fetch_options, builtin_fetch_usage, 0);
 
+       if (recurse_submodules != RECURSE_SUBMODULES_OFF) {
+               if (recurse_submodules_default) {
+                       int arg = parse_fetch_recurse_submodules_arg("--recurse-submodules-default", recurse_submodules_default);
+                       set_config_fetch_recurse_submodules(arg);
+               }
+               gitmodules_config();
+               git_config(submodule_config, NULL);
+       }
+
        if (all) {
                if (argc == 1)
                        die(_("fetch --all does not take a repository argument"));
@@ -976,12 +985,6 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
        if (!result && (recurse_submodules != RECURSE_SUBMODULES_OFF)) {
                const char *options[10];
                int num_options = 0;
-               if (recurse_submodules_default) {
-                       int arg = parse_fetch_recurse_submodules_arg("--recurse-submodules-default", recurse_submodules_default);
-                       set_config_fetch_recurse_submodules(arg);
-               }
-               gitmodules_config();
-               git_config(submodule_config, NULL);
                add_options_to_argv(&num_options, options);
                result = fetch_populated_submodules(num_options, options,
                                                    submodule_prefix,
index 75816329d6153c35e0763b35f70cfaf165f2a4b8..7e2f22589dcb14d5ba95ce1331ef816a458533d0 100644 (file)
@@ -293,7 +293,7 @@ static int do_fmt_merge_msg(int merge_title, struct strbuf *in,
                struct commit *head;
                struct rev_info rev;
 
-               head = lookup_commit(head_sha1);
+               head = lookup_commit_or_die(head_sha1, "HEAD");
                init_revisions(&rev, NULL);
                rev.commit_format = CMIT_FMT_ONELINE;
                rev.ignore_merges = 1;
index 5ae0366bc8cfe52f226c2af151a66d39b9df02aa..df1a88b51ae7773a15276d144113c0002fddb1cd 100644 (file)
@@ -231,12 +231,9 @@ static void check_unreachable_object(struct object *obj)
                                unsigned long size;
                                char *buf = read_sha1_file(obj->sha1,
                                                &type, &size);
-                               if (buf) {
-                                       if (fwrite(buf, size, 1, f) != 1)
-                                               die_errno("Could not write '%s'",
-                                                         filename);
-                                       free(buf);
-                               }
+                               if (buf && fwrite(buf, 1, size, f) != size)
+                                       die_errno("Could not write '%s'", filename);
+                               free(buf);
                        } else
                                fprintf(f, "%s\n", sha1_to_hex(obj->sha1));
                        if (fclose(f))
index 871afaa3c76ea34b67671a6e4a46957a08a946e5..a286692e467710d92346ab6900e98f1126cb967d 100644 (file)
@@ -93,8 +93,7 @@ static pthread_cond_t cond_write;
 /* Signalled when we are finished with everything. */
 static pthread_cond_t cond_result;
 
-static int print_hunk_marks_between_files;
-static int printed_something;
+static int skip_first_line;
 
 static void add_work(enum work_type type, char *name, void *id)
 {
@@ -160,10 +159,20 @@ static void work_done(struct work_item *w)
            todo_done = (todo_done+1) % ARRAY_SIZE(todo)) {
                w = &todo[todo_done];
                if (w->out.len) {
-                       if (print_hunk_marks_between_files && printed_something)
-                               write_or_die(1, "--\n", 3);
-                       write_or_die(1, w->out.buf, w->out.len);
-                       printed_something = 1;
+                       const char *p = w->out.buf;
+                       size_t len = w->out.len;
+
+                       /* Skip the leading hunk mark of the first file. */
+                       if (skip_first_line) {
+                               while (len) {
+                                       len--;
+                                       if (*p++ == '\n')
+                                               break;
+                               }
+                               skip_first_line = 0;
+                       }
+
+                       write_or_die(1, p, len);
                }
                free(w->name);
                free(w->identifier);
@@ -316,7 +325,7 @@ static int grep_config(const char *var, const char *value, void *cb)
        }
 
        if (!strcmp(var, "color.grep"))
-               opt->color = git_config_colorbool(var, value, -1);
+               opt->color = git_config_colorbool(var, value);
        else if (!strcmp(var, "color.grep.context"))
                color = opt->color_context;
        else if (!strcmp(var, "color.grep.filename"))
@@ -589,8 +598,11 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
                struct strbuf base;
                int hit, len;
 
+               read_sha1_lock();
                data = read_object_with_reference(obj->sha1, tree_type,
                                                  &size, NULL);
+               read_sha1_unlock();
+
                if (!data)
                        die(_("unable to read tree (%s)"), sha1_to_hex(obj->sha1));
 
@@ -813,18 +825,24 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                OPT_BOOLEAN('c', "count", &opt.count,
                        "show the number of matches instead of matching lines"),
                OPT__COLOR(&opt.color, "highlight matches"),
+               OPT_BOOLEAN(0, "break", &opt.file_break,
+                       "print empty line between matches from different files"),
+               OPT_BOOLEAN(0, "heading", &opt.heading,
+                       "show filename only once above matches from same file"),
                OPT_GROUP(""),
-               OPT_CALLBACK('C', NULL, &opt, "n",
+               OPT_CALLBACK('C', "context", &opt, "n",
                        "show <n> context lines before and after matches",
                        context_callback),
-               OPT_INTEGER('B', NULL, &opt.pre_context,
+               OPT_INTEGER('B', "before-context", &opt.pre_context,
                        "show <n> context lines before matches"),
-               OPT_INTEGER('A', NULL, &opt.post_context,
+               OPT_INTEGER('A', "after-context", &opt.post_context,
                        "show <n> context lines after matches"),
                OPT_NUMBER_CALLBACK(&opt, "shortcut for -C NUM",
                        context_callback),
                OPT_BOOLEAN('p', "show-function", &opt.funcname,
                        "show a line with the function name before matches"),
+               OPT_BOOLEAN('W', "function-context", &opt.funcbody,
+                       "show the surrounding function"),
                OPT_GROUP(""),
                OPT_CALLBACK('f', NULL, &opt, "file",
                        "read patterns from file", file_callback),
@@ -883,8 +901,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
        strcpy(opt.color_sep, GIT_COLOR_CYAN);
        opt.color = -1;
        git_config(grep_config, &opt);
-       if (opt.color == -1)
-               opt.color = git_use_color_default;
 
        /*
         * If there is no -- then the paths must exist in the working
@@ -967,8 +983,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                use_threads = 0;
 
        if (use_threads) {
-               if (opt.pre_context || opt.post_context)
-                       print_hunk_marks_between_files = 1;
+               if (opt.pre_context || opt.post_context || opt.file_break ||
+                   opt.funcbody)
+                       skip_first_line = 1;
                start_threads(&opt);
        }
 #else
index e40451ffb4ecd31107bfe51817cdce18e1dfe859..0945adbb3bb188b612341c31c8986fabb491928d 100644 (file)
@@ -11,7 +11,7 @@
 #include "exec_cmd.h"
 
 static const char index_pack_usage[] =
-"git index-pack [-v] [-o <index-file>] [ --keep | --keep=<msg> ] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])";
+"git index-pack [-v] [-o <index-file>] [--keep | --keep=<msg>] [--verify] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])";
 
 struct object_entry {
        struct pack_idx_entry idx;
@@ -19,6 +19,8 @@ struct object_entry {
        unsigned int hdr_size;
        enum object_type type;
        enum object_type real_type;
+       unsigned delta_depth;
+       int base_object_no;
 };
 
 union delta_base {
@@ -66,6 +68,7 @@ static struct progress *progress;
 static unsigned char input_buffer[4096];
 static unsigned int input_offset, input_len;
 static off_t consumed_bytes;
+static unsigned deepest_delta;
 static git_SHA_CTX input_ctx;
 static uint32_t input_crc32;
 static int input_fd, output_fd, pack_fd;
@@ -265,7 +268,7 @@ static void unlink_base_data(struct base_data *c)
 static void *unpack_entry_data(unsigned long offset, unsigned long size)
 {
        int status;
-       z_stream stream;
+       git_zstream stream;
        void *buf = xmalloc(size);
 
        memset(&stream, 0, sizeof(stream));
@@ -355,7 +358,7 @@ static void *get_data_from_pack(struct object_entry *obj)
        off_t from = obj[0].idx.offset + obj[0].hdr_size;
        unsigned long len = obj[1].idx.offset - from;
        unsigned char *data, *inbuf;
-       z_stream stream;
+       git_zstream stream;
        int status;
 
        data = xmalloc(obj->size);
@@ -389,7 +392,18 @@ static void *get_data_from_pack(struct object_entry *obj)
        return data;
 }
 
-static int find_delta(const union delta_base *base)
+static int compare_delta_bases(const union delta_base *base1,
+                              const union delta_base *base2,
+                              enum object_type type1,
+                              enum object_type type2)
+{
+       int cmp = type1 - type2;
+       if (cmp)
+               return cmp;
+       return memcmp(base1, base2, UNION_BASE_SZ);
+}
+
+static int find_delta(const union delta_base *base, enum object_type type)
 {
        int first = 0, last = nr_deltas;
 
@@ -398,7 +412,8 @@ static int find_delta(const union delta_base *base)
                 struct delta_entry *delta = &deltas[next];
                 int cmp;
 
-                cmp = memcmp(base, &delta->base, UNION_BASE_SZ);
+               cmp = compare_delta_bases(base, &delta->base,
+                                         type, objects[delta->obj_no].type);
                 if (!cmp)
                         return next;
                 if (cmp < 0) {
@@ -411,9 +426,10 @@ static int find_delta(const union delta_base *base)
 }
 
 static void find_delta_children(const union delta_base *base,
-                               int *first_index, int *last_index)
+                               int *first_index, int *last_index,
+                               enum object_type type)
 {
-       int first = find_delta(base);
+       int first = find_delta(base, type);
        int last = first;
        int end = nr_deltas - 1;
 
@@ -483,12 +499,17 @@ static void sha1_object(const void *data, unsigned long size,
        }
 }
 
+static int is_delta_type(enum object_type type)
+{
+       return (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA);
+}
+
 static void *get_base_data(struct base_data *c)
 {
        if (!c->data) {
                struct object_entry *obj = c->obj;
 
-               if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
+               if (is_delta_type(obj->type)) {
                        void *base = get_base_data(c->base);
                        void *raw = get_data_from_pack(obj);
                        c->data = patch_delta(
@@ -515,6 +536,10 @@ static void resolve_delta(struct object_entry *delta_obj,
        void *base_data, *delta_data;
 
        delta_obj->real_type = base->obj->real_type;
+       delta_obj->delta_depth = base->obj->delta_depth + 1;
+       if (deepest_delta < delta_obj->delta_depth)
+               deepest_delta = delta_obj->delta_depth;
+       delta_obj->base_object_no = base->obj - objects;
        delta_data = get_data_from_pack(delta_obj);
        base_data = get_base_data(base);
        result->obj = delta_obj;
@@ -541,11 +566,13 @@ static void find_unresolved_deltas(struct base_data *base,
                union delta_base base_spec;
 
                hashcpy(base_spec.sha1, base->obj->idx.sha1);
-               find_delta_children(&base_spec, &ref_first, &ref_last);
+               find_delta_children(&base_spec,
+                                   &ref_first, &ref_last, OBJ_REF_DELTA);
 
                memset(&base_spec, 0, sizeof(base_spec));
                base_spec.offset = base->obj->idx.offset;
-               find_delta_children(&base_spec, &ofs_first, &ofs_last);
+               find_delta_children(&base_spec,
+                                   &ofs_first, &ofs_last, OBJ_OFS_DELTA);
        }
 
        if (ref_last == -1 && ofs_last == -1) {
@@ -557,24 +584,24 @@ static void find_unresolved_deltas(struct base_data *base,
 
        for (i = ref_first; i <= ref_last; i++) {
                struct object_entry *child = objects + deltas[i].obj_no;
-               if (child->real_type == OBJ_REF_DELTA) {
-                       struct base_data result;
-                       resolve_delta(child, base, &result);
-                       if (i == ref_last && ofs_last == -1)
-                               free_base_data(base);
-                       find_unresolved_deltas(&result, base);
-               }
+               struct base_data result;
+
+               assert(child->real_type == OBJ_REF_DELTA);
+               resolve_delta(child, base, &result);
+               if (i == ref_last && ofs_last == -1)
+                       free_base_data(base);
+               find_unresolved_deltas(&result, base);
        }
 
        for (i = ofs_first; i <= ofs_last; i++) {
                struct object_entry *child = objects + deltas[i].obj_no;
-               if (child->real_type == OBJ_OFS_DELTA) {
-                       struct base_data result;
-                       resolve_delta(child, base, &result);
-                       if (i == ofs_last)
-                               free_base_data(base);
-                       find_unresolved_deltas(&result, base);
-               }
+               struct base_data result;
+
+               assert(child->real_type == OBJ_OFS_DELTA);
+               resolve_delta(child, base, &result);
+               if (i == ofs_last)
+                       free_base_data(base);
+               find_unresolved_deltas(&result, base);
        }
 
        unlink_base_data(base);
@@ -584,7 +611,11 @@ static int compare_delta_entry(const void *a, const void *b)
 {
        const struct delta_entry *delta_a = a;
        const struct delta_entry *delta_b = b;
-       return memcmp(&delta_a->base, &delta_b->base, UNION_BASE_SZ);
+
+       /* group by type (ref vs ofs) and then by value (sha-1 or offset) */
+       return compare_delta_bases(&delta_a->base, &delta_b->base,
+                                  objects[delta_a->obj_no].type,
+                                  objects[delta_b->obj_no].type);
 }
 
 /* Parse all objects and return the pack content SHA1 hash */
@@ -608,7 +639,7 @@ static void parse_pack_objects(unsigned char *sha1)
                struct object_entry *obj = &objects[i];
                void *data = unpack_raw_entry(obj, &delta->base);
                obj->real_type = obj->type;
-               if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
+               if (is_delta_type(obj->type)) {
                        nr_deltas++;
                        delta->obj_no = i;
                        delta++;
@@ -655,7 +686,7 @@ static void parse_pack_objects(unsigned char *sha1)
                struct object_entry *obj = &objects[i];
                struct base_data base_obj;
 
-               if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA)
+               if (is_delta_type(obj->type))
                        continue;
                base_obj.obj = obj;
                base_obj.data = NULL;
@@ -666,26 +697,26 @@ static void parse_pack_objects(unsigned char *sha1)
 
 static int write_compressed(struct sha1file *f, void *in, unsigned int size)
 {
-       z_stream stream;
+       git_zstream stream;
        int status;
        unsigned char outbuf[4096];
 
        memset(&stream, 0, sizeof(stream));
-       deflateInit(&stream, zlib_compression_level);
+       git_deflate_init(&stream, zlib_compression_level);
        stream.next_in = in;
        stream.avail_in = size;
 
        do {
                stream.next_out = outbuf;
                stream.avail_out = sizeof(outbuf);
-               status = deflate(&stream, Z_FINISH);
+               status = git_deflate(&stream, Z_FINISH);
                sha1write(f, outbuf, sizeof(outbuf) - stream.avail_out);
        } while (status == Z_OK);
 
        if (status != Z_STREAM_END)
                die("unable to deflate appended object (%d)", status);
        size = stream.total_out;
-       deflateEnd(&stream);
+       git_deflate_end(&stream);
        return size;
 }
 
@@ -859,24 +890,137 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
 
 static int git_index_pack_config(const char *k, const char *v, void *cb)
 {
+       struct pack_idx_option *opts = cb;
+
        if (!strcmp(k, "pack.indexversion")) {
-               pack_idx_default_version = git_config_int(k, v);
-               if (pack_idx_default_version > 2)
-                       die("bad pack.indexversion=%"PRIu32,
-                               pack_idx_default_version);
+               opts->version = git_config_int(k, v);
+               if (opts->version > 2)
+                       die("bad pack.indexversion=%"PRIu32, opts->version);
                return 0;
        }
        return git_default_config(k, v, cb);
 }
 
+static int cmp_uint32(const void *a_, const void *b_)
+{
+       uint32_t a = *((uint32_t *)a_);
+       uint32_t b = *((uint32_t *)b_);
+
+       return (a < b) ? -1 : (a != b);
+}
+
+static void read_v2_anomalous_offsets(struct packed_git *p,
+                                     struct pack_idx_option *opts)
+{
+       const uint32_t *idx1, *idx2;
+       uint32_t i;
+
+       /* The address of the 4-byte offset table */
+       idx1 = (((const uint32_t *)p->index_data)
+               + 2 /* 8-byte header */
+               + 256 /* fan out */
+               + 5 * p->num_objects /* 20-byte SHA-1 table */
+               + p->num_objects /* CRC32 table */
+               );
+
+       /* The address of the 8-byte offset table */
+       idx2 = idx1 + p->num_objects;
+
+       for (i = 0; i < p->num_objects; i++) {
+               uint32_t off = ntohl(idx1[i]);
+               if (!(off & 0x80000000))
+                       continue;
+               off = off & 0x7fffffff;
+               if (idx2[off * 2])
+                       continue;
+               /*
+                * The real offset is ntohl(idx2[off * 2]) in high 4
+                * octets, and ntohl(idx2[off * 2 + 1]) in low 4
+                * octets.  But idx2[off * 2] is Zero!!!
+                */
+               ALLOC_GROW(opts->anomaly, opts->anomaly_nr + 1, opts->anomaly_alloc);
+               opts->anomaly[opts->anomaly_nr++] = ntohl(idx2[off * 2 + 1]);
+       }
+
+       if (1 < opts->anomaly_nr)
+               qsort(opts->anomaly, opts->anomaly_nr, sizeof(uint32_t), cmp_uint32);
+}
+
+static void read_idx_option(struct pack_idx_option *opts, const char *pack_name)
+{
+       struct packed_git *p = add_packed_git(pack_name, strlen(pack_name), 1);
+
+       if (!p)
+               die("Cannot open existing pack file '%s'", pack_name);
+       if (open_pack_index(p))
+               die("Cannot open existing pack idx file for '%s'", pack_name);
+
+       /* Read the attributes from the existing idx file */
+       opts->version = p->index_version;
+
+       if (opts->version == 2)
+               read_v2_anomalous_offsets(p, opts);
+
+       /*
+        * Get rid of the idx file as we do not need it anymore.
+        * NEEDSWORK: extract this bit from free_pack_by_name() in
+        * sha1_file.c, perhaps?  It shouldn't matter very much as we
+        * know we haven't installed this pack (hence we never have
+        * read anything from it).
+        */
+       close_pack_index(p);
+       free(p);
+}
+
+static void show_pack_info(int stat_only)
+{
+       int i, baseobjects = nr_objects - nr_deltas;
+       unsigned long *chain_histogram = NULL;
+
+       if (deepest_delta)
+               chain_histogram = xcalloc(deepest_delta, sizeof(unsigned long));
+
+       for (i = 0; i < nr_objects; i++) {
+               struct object_entry *obj = &objects[i];
+
+               if (is_delta_type(obj->type))
+                       chain_histogram[obj->delta_depth - 1]++;
+               if (stat_only)
+                       continue;
+               printf("%s %-6s %lu %lu %"PRIuMAX,
+                      sha1_to_hex(obj->idx.sha1),
+                      typename(obj->real_type), obj->size,
+                      (unsigned long)(obj[1].idx.offset - obj->idx.offset),
+                      (uintmax_t)obj->idx.offset);
+               if (is_delta_type(obj->type)) {
+                       struct object_entry *bobj = &objects[obj->base_object_no];
+                       printf(" %u %s", obj->delta_depth, sha1_to_hex(bobj->idx.sha1));
+               }
+               putchar('\n');
+       }
+
+       if (baseobjects)
+               printf("non delta: %d object%s\n",
+                      baseobjects, baseobjects > 1 ? "s" : "");
+       for (i = 0; i < deepest_delta; i++) {
+               if (!chain_histogram[i])
+                       continue;
+               printf("chain length = %d: %lu object%s\n",
+                      i + 1,
+                      chain_histogram[i],
+                      chain_histogram[i] > 1 ? "s" : "");
+       }
+}
+
 int cmd_index_pack(int argc, const char **argv, const char *prefix)
 {
-       int i, fix_thin_pack = 0;
+       int i, fix_thin_pack = 0, verify = 0, stat_only = 0, stat = 0;
        const char *curr_pack, *curr_index;
        const char *index_name = NULL, *pack_name = NULL;
        const char *keep_name = NULL, *keep_msg = NULL;
        char *index_name_buf = NULL, *keep_name_buf = NULL;
        struct pack_idx_entry **idx_objects;
+       struct pack_idx_option opts;
        unsigned char pack_sha1[20];
 
        if (argc == 2 && !strcmp(argv[1], "-h"))
@@ -884,7 +1028,8 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
 
        read_replace_refs = 0;
 
-       git_config(git_index_pack_config, NULL);
+       reset_pack_idx_option(&opts);
+       git_config(git_index_pack_config, &opts);
        if (prefix && chdir(prefix))
                die("Cannot come back to cwd");
 
@@ -898,6 +1043,15 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
                                fix_thin_pack = 1;
                        } else if (!strcmp(arg, "--strict")) {
                                strict = 1;
+                       } else if (!strcmp(arg, "--verify")) {
+                               verify = 1;
+                       } else if (!strcmp(arg, "--verify-stat")) {
+                               verify = 1;
+                               stat = 1;
+                       } else if (!strcmp(arg, "--verify-stat-only")) {
+                               verify = 1;
+                               stat = 1;
+                               stat_only = 1;
                        } else if (!strcmp(arg, "--keep")) {
                                keep_msg = "";
                        } else if (!prefixcmp(arg, "--keep=")) {
@@ -923,12 +1077,12 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
                                index_name = argv[++i];
                        } else if (!prefixcmp(arg, "--index-version=")) {
                                char *c;
-                               pack_idx_default_version = strtoul(arg + 16, &c, 10);
-                               if (pack_idx_default_version > 2)
+                               opts.version = strtoul(arg + 16, &c, 10);
+                               if (opts.version > 2)
                                        die("bad %s", arg);
                                if (*c == ',')
-                                       pack_idx_off32_limit = strtoul(c+1, &c, 0);
-                               if (*c || pack_idx_off32_limit & 0x80000000)
+                                       opts.off32_limit = strtoul(c+1, &c, 0);
+                               if (*c || opts.off32_limit & 0x80000000)
                                        die("bad %s", arg);
                        } else
                                usage(index_pack_usage);
@@ -964,11 +1118,17 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
                strcpy(keep_name_buf + len - 5, ".keep");
                keep_name = keep_name_buf;
        }
+       if (verify) {
+               if (!index_name)
+                       die("--verify with no packfile name given");
+               read_idx_option(&opts, index_name);
+               opts.flags |= WRITE_IDX_VERIFY;
+       }
 
        curr_pack = open_pack_file(pack_name);
        parse_pack_header();
-       objects = xmalloc((nr_objects + 1) * sizeof(struct object_entry));
-       deltas = xmalloc(nr_objects * sizeof(struct delta_entry));
+       objects = xcalloc(nr_objects + 1, sizeof(struct object_entry));
+       deltas = xcalloc(nr_objects, sizeof(struct delta_entry));
        parse_pack_objects(pack_sha1);
        if (nr_deltas == nr_resolved_deltas) {
                stop_progress(&progress);
@@ -1008,16 +1168,22 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
        if (strict)
                check_objects();
 
+       if (stat)
+               show_pack_info(stat_only);
+
        idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *));
        for (i = 0; i < nr_objects; i++)
                idx_objects[i] = &objects[i].idx;
-       curr_index = write_idx_file(index_name, idx_objects, nr_objects, pack_sha1);
+       curr_index = write_idx_file(index_name, idx_objects, nr_objects, &opts, pack_sha1);
        free(idx_objects);
 
-       final(pack_name, curr_pack,
-               index_name, curr_index,
-               keep_name, keep_msg,
-               pack_sha1);
+       if (!verify)
+               final(pack_name, curr_pack,
+                     index_name, curr_index,
+                     keep_name, keep_msg,
+                     pack_sha1);
+       else
+               close(input_fd);
        free(objects);
        free(index_name_buf);
        free(keep_name_buf);
index 025aa47c804e4400cfa16ae8acd8dc2a5175e7c0..d07554c8844a9b7dd3d4ae2b5efe2cbde623e4af 100644 (file)
@@ -347,7 +347,7 @@ static void separate_git_dir(const char *git_dir)
                const char *src;
 
                if (S_ISREG(st.st_mode))
-                       src = read_gitfile_gently(git_link);
+                       src = read_gitfile(git_link);
                else if (S_ISDIR(st.st_mode))
                        src = git_link;
                else
index 5c2af590047d92554d87acc5d7113a2f1f730a96..f5d49305903911eb7aa0fb3f73e0fd950b896228 100644 (file)
@@ -359,9 +359,6 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix)
 
        git_config(git_log_config, NULL);
 
-       if (diff_use_color_default == -1)
-               diff_use_color_default = git_use_color_default;
-
        init_revisions(&rev, prefix);
        rev.diff = 1;
        rev.simplify_history = 0;
@@ -446,9 +443,6 @@ int cmd_show(int argc, const char **argv, const char *prefix)
 
        git_config(git_log_config, NULL);
 
-       if (diff_use_color_default == -1)
-               diff_use_color_default = git_use_color_default;
-
        init_pathspec(&match_all, NULL);
        init_revisions(&rev, prefix);
        rev.diff = 1;
@@ -524,9 +518,6 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix)
 
        git_config(git_log_config, NULL);
 
-       if (diff_use_color_default == -1)
-               diff_use_color_default = git_use_color_default;
-
        init_revisions(&rev, prefix);
        init_reflog_walk(&rev.reflog_info);
        rev.verbose_header = 1;
@@ -549,9 +540,6 @@ int cmd_log(int argc, const char **argv, const char *prefix)
 
        git_config(git_log_config, NULL);
 
-       if (diff_use_color_default == -1)
-               diff_use_color_default = git_use_color_default;
-
        init_revisions(&rev, prefix);
        rev.always_show_header = 1;
        memset(&opt, 0, sizeof(opt));
@@ -620,7 +608,8 @@ static int git_format_config(const char *var, const char *value, void *cb)
                string_list_append(&extra_cc, value);
                return 0;
        }
-       if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
+       if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff") ||
+           !strcmp(var, "color.ui")) {
                return 0;
        }
        if (!strcmp(var, "format.numbered")) {
index 15701233e29b240cfa1abdb56bd489306e74c677..e8a800d3ac42bfce328bea786d8e1efa4ff695c7 100644 (file)
@@ -276,41 +276,6 @@ static void prune_cache(const char *prefix)
        active_nr = last;
 }
 
-static const char *pathspec_prefix(const char *prefix)
-{
-       const char **p, *n, *prev;
-       unsigned long max;
-
-       if (!pathspec) {
-               max_prefix_len = prefix ? strlen(prefix) : 0;
-               return prefix;
-       }
-
-       prev = NULL;
-       max = PATH_MAX;
-       for (p = pathspec; (n = *p) != NULL; p++) {
-               int i, len = 0;
-               for (i = 0; i < max; i++) {
-                       char c = n[i];
-                       if (prev && prev[i] != c)
-                               break;
-                       if (!c || c == '*' || c == '?')
-                               break;
-                       if (c == '/')
-                               len = i+1;
-               }
-               prev = n;
-               if (len < max) {
-                       max = len;
-                       if (!max)
-                               break;
-               }
-       }
-
-       max_prefix_len = max;
-       return max ? xmemdupz(prev, max) : NULL;
-}
-
 static void strip_trailing_slash_from_submodules(void)
 {
        const char **p;
@@ -388,11 +353,13 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix)
        }
 }
 
-int report_path_error(const char *ps_matched, const char **pathspec, int prefix_len)
+int report_path_error(const char *ps_matched, const char **pathspec, const char *prefix)
 {
        /*
         * Make sure all pathspec matched; otherwise it is an error.
         */
+       struct strbuf sb = STRBUF_INIT;
+       const char *name;
        int num, errors = 0;
        for (num = 0; pathspec[num]; num++) {
                int other, found_dup;
@@ -417,10 +384,12 @@ int report_path_error(const char *ps_matched, const char **pathspec, int prefix_
                if (found_dup)
                        continue;
 
+               name = quote_path_relative(pathspec[num], -1, &sb, prefix);
                error("pathspec '%s' did not match any file(s) known to git.",
-                     pathspec[num] + prefix_len);
+                     name);
                errors++;
        }
+       strbuf_release(&sb);
        return errors;
 }
 
@@ -576,7 +545,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
                strip_trailing_slash_from_submodules();
 
        /* Find common prefix for all pathspec's */
-       max_prefix = pathspec_prefix(prefix);
+       max_prefix = pathspec_prefix(prefix, pathspec);
+       max_prefix_len = max_prefix ? strlen(max_prefix) : 0;
 
        /* Treat unmatching pathspec elements as errors */
        if (pathspec && error_unmatch) {
@@ -611,7 +581,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
 
        if (ps_matched) {
                int bad;
-               bad = report_path_error(ps_matched, pathspec, prefix_len);
+               bad = report_path_error(ps_matched, pathspec, prefix);
                if (bad)
                        fprintf(stderr, "Did you forget to 'git add'?\n");
 
index f08c5b0c942eec58b85ab41e5e5fb1ec216df683..6b666e1e87017f41d2f53c50b157f280b3a5f282 100644 (file)
@@ -173,7 +173,5 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
        tree = parse_tree_indirect(sha1);
        if (!tree)
                die("not a tree object");
-       read_tree_recursive(tree, "", 0, 0, &pathspec, show_tree, NULL);
-
-       return 0;
+       return !!read_tree_recursive(tree, "", 0, 0, &pathspec, show_tree, NULL);
 }
index 325891edb610945d01899b102993202af279bf3f..581f494aee01f97bb87a94dbfd1fba4d14afaa69 100644 (file)
@@ -50,7 +50,6 @@ static int fast_forward_only;
 static int allow_trivial = 1, have_message;
 static struct strbuf merge_msg;
 static struct commit_list *remoteheads;
-static unsigned char head[20], stash[20];
 static struct strategy **use_strategies;
 static size_t use_strategies_nr, use_strategies_alloc;
 static const char **xopts;
@@ -217,7 +216,7 @@ static void drop_save(void)
        unlink(git_path("MERGE_MODE"));
 }
 
-static void save_state(void)
+static int save_state(unsigned char *stash)
 {
        int len;
        struct child_process cp;
@@ -236,11 +235,12 @@ static void save_state(void)
 
        if (finish_command(&cp) || len < 0)
                die(_("stash failed"));
-       else if (!len)
-               return;
+       else if (!len)          /* no changes */
+               return -1;
        strbuf_setlen(&buffer, buffer.len-1);
        if (get_sha1(buffer.buf, stash))
                die(_("not a valid object: %s"), buffer.buf);
+       return 0;
 }
 
 static void read_empty(unsigned const char *sha1, int verbose)
@@ -278,7 +278,8 @@ static void reset_hard(unsigned const char *sha1, int verbose)
                die(_("read-tree failed"));
 }
 
-static void restore_state(void)
+static void restore_state(const unsigned char *head,
+                         const unsigned char *stash)
 {
        struct strbuf sb = STRBUF_INIT;
        const char *args[] = { "stash", "apply", NULL, NULL };
@@ -308,10 +309,9 @@ static void finish_up_to_date(const char *msg)
        drop_save();
 }
 
-static void squash_message(void)
+static void squash_message(struct commit *commit)
 {
        struct rev_info rev;
-       struct commit *commit;
        struct strbuf out = STRBUF_INIT;
        struct commit_list *j;
        int fd;
@@ -326,7 +326,6 @@ static void squash_message(void)
        rev.ignore_merges = 1;
        rev.commit_format = CMIT_FMT_MEDIUM;
 
-       commit = lookup_commit(head);
        commit->object.flags |= UNINTERESTING;
        add_pending_object(&rev, &commit->object, NULL);
 
@@ -355,9 +354,11 @@ static void squash_message(void)
        strbuf_release(&out);
 }
 
-static void finish(const unsigned char *new_head, const char *msg)
+static void finish(struct commit *head_commit,
+                  const unsigned char *new_head, const char *msg)
 {
        struct strbuf reflog_message = STRBUF_INIT;
+       const unsigned char *head = head_commit->object.sha1;
 
        if (!msg)
                strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
@@ -368,7 +369,7 @@ static void finish(const unsigned char *new_head, const char *msg)
                        getenv("GIT_REFLOG_ACTION"), msg);
        }
        if (squash) {
-               squash_message();
+               squash_message(head_commit);
        } else {
                if (verbosity >= 0 && !merge_msg.len)
                        printf(_("No merge message -- not updating HEAD\n"));
@@ -390,8 +391,6 @@ static void finish(const unsigned char *new_head, const char *msg)
                opts.output_format |=
                        DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
                opts.detect_rename = DIFF_DETECT_RENAME;
-               if (diff_use_color_default > 0)
-                       DIFF_OPT_SET(&opts, COLOR_DIFF);
                if (diff_setup_done(&opts) < 0)
                        die(_("diff_setup_done failed"));
                diff_tree_sha1(head, new_head, "", &opts);
@@ -663,7 +662,7 @@ int try_merge_command(const char *strategy, size_t xopts_nr,
 }
 
 static int try_merge_strategy(const char *strategy, struct commit_list *common,
-                             const char *head_arg)
+                             struct commit *head, const char *head_arg)
 {
        int index_fd;
        struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
@@ -709,7 +708,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
                        commit_list_insert(j->item, &reversed);
 
                index_fd = hold_locked_index(lock, 1);
-               clean = merge_recursive(&o, lookup_commit(head),
+               clean = merge_recursive(&o, head,
                                remoteheads->item, reversed, &result);
                if (active_cache_changed &&
                                (write_cache(index_fd, active_cache, active_nr) ||
@@ -860,25 +859,26 @@ static void run_prepare_commit_msg(void)
        read_merge_msg();
 }
 
-static int merge_trivial(void)
+static int merge_trivial(struct commit *head)
 {
        unsigned char result_tree[20], result_commit[20];
        struct commit_list *parent = xmalloc(sizeof(*parent));
 
        write_tree_trivial(result_tree);
        printf(_("Wonderful.\n"));
-       parent->item = lookup_commit(head);
+       parent->item = head;
        parent->next = xmalloc(sizeof(*parent->next));
        parent->next->item = remoteheads->item;
        parent->next->next = NULL;
        run_prepare_commit_msg();
        commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
-       finish(result_commit, "In-index merge");
+       finish(head, result_commit, "In-index merge");
        drop_save();
        return 0;
 }
 
-static int finish_automerge(struct commit_list *common,
+static int finish_automerge(struct commit *head,
+                           struct commit_list *common,
                            unsigned char *result_tree,
                            const char *wt_strategy)
 {
@@ -889,12 +889,12 @@ static int finish_automerge(struct commit_list *common,
        free_commit_list(common);
        if (allow_fast_forward) {
                parents = remoteheads;
-               commit_list_insert(lookup_commit(head), &parents);
+               commit_list_insert(head, &parents);
                parents = reduce_heads(parents);
        } else {
                struct commit_list **pptr = &parents;
 
-               pptr = &commit_list_insert(lookup_commit(head),
+               pptr = &commit_list_insert(head,
                                pptr)->next;
                for (j = remoteheads; j; j = j->next)
                        pptr = &commit_list_insert(j->item, pptr)->next;
@@ -903,8 +903,8 @@ static int finish_automerge(struct commit_list *common,
        strbuf_addch(&merge_msg, '\n');
        run_prepare_commit_msg();
        commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
-       strbuf_addf(&buf, "Merge made by %s.", wt_strategy);
-       finish(result_commit, buf.buf);
+       strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
+       finish(head, result_commit, buf.buf);
        strbuf_release(&buf);
        drop_save();
        return 0;
@@ -938,7 +938,8 @@ static int suggest_conflicts(int renormalizing)
        return 1;
 }
 
-static struct commit *is_old_style_invocation(int argc, const char **argv)
+static struct commit *is_old_style_invocation(int argc, const char **argv,
+                                             const unsigned char *head)
 {
        struct commit *second_token = NULL;
        if (argc > 2) {
@@ -1010,9 +1011,12 @@ static int setup_with_upstream(const char ***argv)
 int cmd_merge(int argc, const char **argv, const char *prefix)
 {
        unsigned char result_tree[20];
+       unsigned char stash[20];
+       unsigned char head_sha1[20];
+       struct commit *head_commit;
        struct strbuf buf = STRBUF_INIT;
        const char *head_arg;
-       int flag, head_invalid = 0, i;
+       int flag, i;
        int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
        struct commit_list *common = NULL;
        const char *best_strategy = NULL, *wt_strategy = NULL;
@@ -1025,18 +1029,16 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
         * Check if we are _not_ on a detached HEAD, i.e. if there is a
         * current branch.
         */
-       branch = resolve_ref("HEAD", head, 0, &flag);
+       branch = resolve_ref("HEAD", head_sha1, 0, &flag);
        if (branch && !prefixcmp(branch, "refs/heads/"))
                branch += 11;
-       if (is_null_sha1(head))
-               head_invalid = 1;
+       if (!branch || is_null_sha1(head_sha1))
+               head_commit = NULL;
+       else
+               head_commit = lookup_commit_or_die(head_sha1, "HEAD");
 
        git_config(git_merge_config, NULL);
 
-       /* for color.ui */
-       if (diff_use_color_default == -1)
-               diff_use_color_default = git_use_color_default;
-
        if (branch_mergeoptions)
                parse_branch_merge_options(branch_mergeoptions);
        argc = parse_options(argc, argv, prefix, builtin_merge_options,
@@ -1110,12 +1112,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
         * additional safety measure to check for it.
         */
 
-       if (!have_message && is_old_style_invocation(argc, argv)) {
+       if (!have_message && head_commit &&
+           is_old_style_invocation(argc, argv, head_commit->object.sha1)) {
                strbuf_addstr(&merge_msg, argv[0]);
                head_arg = argv[1];
                argv += 2;
                argc -= 2;
-       } else if (head_invalid) {
+       } else if (!head_commit) {
                struct object *remote_head;
                /*
                 * If the merged head is a valid one there is no reason
@@ -1162,7 +1165,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                }
        }
 
-       if (head_invalid || !argc)
+       if (!head_commit || !argc)
                usage_with_options(builtin_merge_usage,
                        builtin_merge_options);
 
@@ -1203,17 +1206,16 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
        }
 
        if (!remoteheads->next)
-               common = get_merge_bases(lookup_commit(head),
-                               remoteheads->item, 1);
+               common = get_merge_bases(head_commit, remoteheads->item, 1);
        else {
                struct commit_list *list = remoteheads;
-               commit_list_insert(lookup_commit(head), &list);
+               commit_list_insert(head_commit, &list);
                common = get_octopus_merge_bases(list);
                free(list);
        }
 
-       update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
-               DIE_ON_ERR);
+       update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.sha1,
+                  NULL, 0, DIE_ON_ERR);
 
        if (!common)
                ; /* No common ancestors found. We need a real merge. */
@@ -1227,13 +1229,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                return 0;
        } else if (allow_fast_forward && !remoteheads->next &&
                        !common->next &&
-                       !hashcmp(common->item->object.sha1, head)) {
+                       !hashcmp(common->item->object.sha1, head_commit->object.sha1)) {
                /* Again the most common case of merging one remote. */
                struct strbuf msg = STRBUF_INIT;
                struct object *o;
                char hex[41];
 
-               strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
+               strcpy(hex, find_unique_abbrev(head_commit->object.sha1, DEFAULT_ABBREV));
 
                if (verbosity >= 0)
                        printf(_("Updating %s..%s\n"),
@@ -1249,10 +1251,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                if (!o)
                        return 1;
 
-               if (checkout_fast_forward(head, remoteheads->item->object.sha1))
+               if (checkout_fast_forward(head_commit->object.sha1, remoteheads->item->object.sha1))
                        return 1;
 
-               finish(o->sha1, msg.buf);
+               finish(head_commit, o->sha1, msg.buf);
                drop_save();
                return 0;
        } else if (!remoteheads->next && common->next)
@@ -1272,8 +1274,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                        git_committer_info(IDENT_ERROR_ON_NO_NAME);
                        printf(_("Trying really trivial in-index merge...\n"));
                        if (!read_tree_trivial(common->item->object.sha1,
-                                       head, remoteheads->item->object.sha1))
-                               return merge_trivial();
+                                       head_commit->object.sha1, remoteheads->item->object.sha1))
+                               return merge_trivial(head_commit);
                        printf(_("Nope.\n"));
                }
        } else {
@@ -1292,8 +1294,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                         * merge_bases again, otherwise "git merge HEAD^
                         * HEAD^^" would be missed.
                         */
-                       common_one = get_merge_bases(lookup_commit(head),
-                               j->item, 1);
+                       common_one = get_merge_bases(head_commit, j->item, 1);
                        if (hashcmp(common_one->item->object.sha1,
                                j->item->object.sha1)) {
                                up_to_date = 0;
@@ -1320,21 +1321,18 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
         * sync with the head commit.  The strategies are responsible
         * to ensure this.
         */
-       if (use_strategies_nr != 1) {
-               /*
-                * Stash away the local changes so that we can try more
-                * than one.
-                */
-               save_state();
-       } else {
-               memcpy(stash, null_sha1, 20);
-       }
+       if (use_strategies_nr == 1 ||
+           /*
+            * Stash away the local changes so that we can try more than one.
+            */
+           save_state(stash))
+               hashcpy(stash, null_sha1);
 
        for (i = 0; i < use_strategies_nr; i++) {
                int ret;
                if (i) {
                        printf(_("Rewinding the tree to pristine...\n"));
-                       restore_state();
+                       restore_state(head_commit->object.sha1, stash);
                }
                if (use_strategies_nr != 1)
                        printf(_("Trying merge strategy %s...\n"),
@@ -1346,7 +1344,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                wt_strategy = use_strategies[i]->name;
 
                ret = try_merge_strategy(use_strategies[i]->name,
-                       common, head_arg);
+                                        common, head_commit, head_arg);
                if (!option_commit && !ret) {
                        merge_was_ok = 1;
                        /*
@@ -1388,14 +1386,15 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
         * auto resolved the merge cleanly.
         */
        if (automerge_was_ok)
-               return finish_automerge(common, result_tree, wt_strategy);
+               return finish_automerge(head_commit, common, result_tree,
+                                       wt_strategy);
 
        /*
         * Pick the result from the best strategy and have the user fix
         * it up.
         */
        if (!best_strategy) {
-               restore_state();
+               restore_state(head_commit->object.sha1, stash);
                if (use_strategies_nr > 1)
                        fprintf(stderr,
                                _("No merge strategy handled the merge.\n"));
@@ -1407,14 +1406,14 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                ; /* We already have its result in the working tree. */
        else {
                printf(_("Rewinding the tree to pristine...\n"));
-               restore_state();
+               restore_state(head_commit->object.sha1, stash);
                printf(_("Using the %s to prepare resolving by hand.\n"),
                        best_strategy);
-               try_merge_strategy(best_strategy, common, head_arg);
+               try_merge_strategy(best_strategy, common, head_commit, head_arg);
        }
 
        if (squash)
-               finish(NULL, NULL);
+               finish(head_commit, NULL, NULL);
        else {
                int fd;
                struct commit_list *j;
index f402a843bb5ee23360d29bdbbc6cc9c5b9c72008..a9c67c18ba159c8f04fa6bfff52ed9718965190a 100644 (file)
@@ -51,6 +51,8 @@ struct object_entry {
                                       * objects against.
                                       */
        unsigned char no_try_delta;
+       unsigned char tagged; /* near the very tip of refs */
+       unsigned char filled; /* assigned write-order */
 };
 
 /*
@@ -70,6 +72,7 @@ static int local;
 static int incremental;
 static int ignore_packed_keep;
 static int allow_ofs_delta;
+static struct pack_idx_option pack_idx_opts;
 static const char *base_name;
 static int progress = 1;
 static int window = 10;
@@ -95,6 +98,7 @@ static unsigned long window_memory_limit = 0;
  */
 static int *object_ix;
 static int object_ix_hashsz;
+static struct object_entry *locate_object_entry(const unsigned char *sha1);
 
 /*
  * stats
@@ -126,13 +130,13 @@ static void *get_delta(struct object_entry *entry)
 
 static unsigned long do_compress(void **pptr, unsigned long size)
 {
-       z_stream stream;
+       git_zstream stream;
        void *in, *out;
        unsigned long maxsize;
 
        memset(&stream, 0, sizeof(stream));
-       deflateInit(&stream, pack_compression_level);
-       maxsize = deflateBound(&stream, size);
+       git_deflate_init(&stream, pack_compression_level);
+       maxsize = git_deflate_bound(&stream, size);
 
        in = *pptr;
        out = xmalloc(maxsize);
@@ -142,9 +146,9 @@ static unsigned long do_compress(void **pptr, unsigned long size)
        stream.avail_in = size;
        stream.next_out = out;
        stream.avail_out = maxsize;
-       while (deflate(&stream, Z_FINISH) == Z_OK)
+       while (git_deflate(&stream, Z_FINISH) == Z_OK)
                ; /* nothing */
-       deflateEnd(&stream);
+       git_deflate_end(&stream);
 
        free(in);
        return stream.total_out;
@@ -160,7 +164,7 @@ static int check_pack_inflate(struct packed_git *p,
                off_t len,
                unsigned long expect)
 {
-       z_stream stream;
+       git_zstream stream;
        unsigned char fakebuf[4096], *in;
        int st;
 
@@ -187,18 +191,19 @@ static void copy_pack_data(struct sha1file *f,
                off_t len)
 {
        unsigned char *in;
-       unsigned int avail;
+       unsigned long avail;
 
        while (len) {
                in = use_pack(p, w_curs, offset, &avail);
                if (avail > len)
-                       avail = (unsigned int)len;
+                       avail = (unsigned long)len;
                sha1write(f, in, avail);
                offset += avail;
                len -= avail;
        }
 }
 
+/* Return 0 if we will bust the pack-size limit */
 static unsigned long write_object(struct sha1file *f,
                                  struct object_entry *entry,
                                  off_t write_offset)
@@ -433,6 +438,134 @@ static int write_one(struct sha1file *f,
        return 1;
 }
 
+static int mark_tagged(const char *path, const unsigned char *sha1, int flag,
+                      void *cb_data)
+{
+       unsigned char peeled[20];
+       struct object_entry *entry = locate_object_entry(sha1);
+
+       if (entry)
+               entry->tagged = 1;
+       if (!peel_ref(path, peeled)) {
+               entry = locate_object_entry(peeled);
+               if (entry)
+                       entry->tagged = 1;
+       }
+       return 0;
+}
+
+static void add_to_write_order(struct object_entry **wo,
+                              int *endp,
+                              struct object_entry *e)
+{
+       if (e->filled)
+               return;
+       wo[(*endp)++] = e;
+       e->filled = 1;
+}
+
+static void add_descendants_to_write_order(struct object_entry **wo,
+                                          int *endp,
+                                          struct object_entry *e)
+{
+       struct object_entry *child;
+
+       for (child = e->delta_child; child; child = child->delta_sibling)
+               add_to_write_order(wo, endp, child);
+       for (child = e->delta_child; child; child = child->delta_sibling)
+               add_descendants_to_write_order(wo, endp, child);
+}
+
+static void add_family_to_write_order(struct object_entry **wo,
+                                     int *endp,
+                                     struct object_entry *e)
+{
+       struct object_entry *root;
+
+       for (root = e; root->delta; root = root->delta)
+               ; /* nothing */
+       add_to_write_order(wo, endp, root);
+       add_descendants_to_write_order(wo, endp, root);
+}
+
+static struct object_entry **compute_write_order(void)
+{
+       int i, wo_end;
+
+       struct object_entry **wo = xmalloc(nr_objects * sizeof(*wo));
+
+       for (i = 0; i < nr_objects; i++) {
+               objects[i].tagged = 0;
+               objects[i].filled = 0;
+               objects[i].delta_child = NULL;
+               objects[i].delta_sibling = NULL;
+       }
+
+       /*
+        * Fully connect delta_child/delta_sibling network.
+        * Make sure delta_sibling is sorted in the original
+        * recency order.
+        */
+       for (i = nr_objects - 1; 0 <= i; i--) {
+               struct object_entry *e = &objects[i];
+               if (!e->delta)
+                       continue;
+               /* Mark me as the first child */
+               e->delta_sibling = e->delta->delta_child;
+               e->delta->delta_child = e;
+       }
+
+       /*
+        * Mark objects that are at the tip of tags.
+        */
+       for_each_tag_ref(mark_tagged, NULL);
+
+       /*
+        * Give the commits in the original recency order until
+        * we see a tagged tip.
+        */
+       for (i = wo_end = 0; i < nr_objects; i++) {
+               if (objects[i].tagged)
+                       break;
+               add_to_write_order(wo, &wo_end, &objects[i]);
+       }
+
+       /*
+        * Then fill all the tagged tips.
+        */
+       for (; i < nr_objects; i++) {
+               if (objects[i].tagged)
+                       add_to_write_order(wo, &wo_end, &objects[i]);
+       }
+
+       /*
+        * And then all remaining commits and tags.
+        */
+       for (i = 0; i < nr_objects; i++) {
+               if (objects[i].type != OBJ_COMMIT &&
+                   objects[i].type != OBJ_TAG)
+                       continue;
+               add_to_write_order(wo, &wo_end, &objects[i]);
+       }
+
+       /*
+        * And then all the trees.
+        */
+       for (i = 0; i < nr_objects; i++) {
+               if (objects[i].type != OBJ_TREE)
+                       continue;
+               add_to_write_order(wo, &wo_end, &objects[i]);
+       }
+
+       /*
+        * Finally all the rest in really tight order
+        */
+       for (i = 0; i < nr_objects; i++)
+               add_family_to_write_order(wo, &wo_end, &objects[i]);
+
+       return wo;
+}
+
 static void write_pack_file(void)
 {
        uint32_t i = 0, j;
@@ -441,10 +574,12 @@ static void write_pack_file(void)
        struct pack_header hdr;
        uint32_t nr_remaining = nr_result;
        time_t last_mtime = 0;
+       struct object_entry **write_order;
 
        if (progress > pack_to_stdout)
                progress_state = start_progress("Writing objects", nr_result);
        written_list = xmalloc(nr_objects * sizeof(*written_list));
+       write_order = compute_write_order();
 
        do {
                unsigned char sha1[20];
@@ -468,7 +603,8 @@ static void write_pack_file(void)
                offset = sizeof(hdr);
                nr_written = 0;
                for (; i < nr_objects; i++) {
-                       if (!write_one(f, objects + i, &offset))
+                       struct object_entry *e = write_order[i];
+                       if (!write_one(f, e, &offset))
                                break;
                        display_progress(progress_state, written);
                }
@@ -493,8 +629,8 @@ static void write_pack_file(void)
                        const char *idx_tmp_name;
                        char tmpname[PATH_MAX];
 
-                       idx_tmp_name = write_idx_file(NULL, written_list,
-                                                     nr_written, sha1);
+                       idx_tmp_name = write_idx_file(NULL, written_list, nr_written,
+                                                     &pack_idx_opts, sha1);
 
                        snprintf(tmpname, sizeof(tmpname), "%s-%s.pack",
                                 base_name, sha1_to_hex(sha1));
@@ -545,6 +681,7 @@ static void write_pack_file(void)
        } while (nr_remaining && i < nr_objects);
 
        free(written_list);
+       free(write_order);
        stop_progress(&progress_state);
        if (written != nr_result)
                die("wrote %"PRIu32" objects while expecting %"PRIu32,
@@ -633,7 +770,7 @@ static int no_try_delta(const char *path)
        struct git_attr_check check[1];
 
        setup_delta_attr_check(check);
-       if (git_checkattr(path, ARRAY_SIZE(check), check))
+       if (git_check_attr(path, ARRAY_SIZE(check), check))
                return 0;
        if (ATTR_FALSE(check->value))
                return 1;
@@ -994,7 +1131,7 @@ static void check_object(struct object_entry *entry)
                const unsigned char *base_ref = NULL;
                struct object_entry *base_entry;
                unsigned long used, used_0;
-               unsigned int avail;
+               unsigned long avail;
                off_t ofs;
                unsigned char *buf, c;
 
@@ -1884,10 +2021,10 @@ static int git_pack_config(const char *k, const char *v, void *cb)
                return 0;
        }
        if (!strcmp(k, "pack.indexversion")) {
-               pack_idx_default_version = git_config_int(k, v);
-               if (pack_idx_default_version > 2)
+               pack_idx_opts.version = git_config_int(k, v);
+               if (pack_idx_opts.version > 2)
                        die("bad pack.indexversion=%"PRIu32,
-                               pack_idx_default_version);
+                           pack_idx_opts.version);
                return 0;
        }
        if (!strcmp(k, "pack.packsizelimit")) {
@@ -2134,6 +2271,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
        rp_av[1] = "--objects"; /* --thin will make it --objects-edge */
        rp_ac = 2;
 
+       reset_pack_idx_option(&pack_idx_opts);
        git_config(git_pack_config, NULL);
        if (!pack_compression_seen && core_compression_seen)
                pack_compression_level = core_compression_level;
@@ -2278,12 +2416,12 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                }
                if (!prefixcmp(arg, "--index-version=")) {
                        char *c;
-                       pack_idx_default_version = strtoul(arg + 16, &c, 10);
-                       if (pack_idx_default_version > 2)
+                       pack_idx_opts.version = strtoul(arg + 16, &c, 10);
+                       if (pack_idx_opts.version > 2)
                                die("bad %s", arg);
                        if (*c == ',')
-                               pack_idx_off32_limit = strtoul(c+1, &c, 0);
-                       if (*c || pack_idx_off32_limit & 0x80000000)
+                               pack_idx_opts.off32_limit = strtoul(c+1, &c, 0);
+                       if (*c || pack_idx_opts.off32_limit & 0x80000000)
                                die("bad %s", arg);
                        continue;
                }
index f821eb3f0b864c26d5e822cc7c5b0747340abee5..3cfe02d5a5716de1a3aaa5edc1ca0e3d74c03b03 100644 (file)
@@ -56,13 +56,13 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after)
        return 1;
 }
 
-static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx)
+static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx, struct strbuf *line_buf)
 {
-       static char line[1000];
        int patchlen = 0, found_next = 0;
        int before = -1, after = -1;
 
-       while (fgets(line, sizeof(line), stdin) != NULL) {
+       while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) {
+               char *line = line_buf->buf;
                char *p = line;
                int len;
 
@@ -133,14 +133,16 @@ static void generate_id_list(void)
        unsigned char sha1[20], n[20];
        git_SHA_CTX ctx;
        int patchlen;
+       struct strbuf line_buf = STRBUF_INIT;
 
        git_SHA1_Init(&ctx);
        hashclr(sha1);
        while (!feof(stdin)) {
-               patchlen = get_one_patchid(n, &ctx);
+               patchlen = get_one_patchid(n, &ctx, &line_buf);
                flush_current_id(patchlen, sha1, &ctx);
                hashcpy(sha1, n);
        }
+       strbuf_release(&line_buf);
 }
 
 static const char patch_id_usage[] = "git patch-id < patch";
index 9cebf9ea234d968eab638f7dcdc65370f77c0047..35cce532f2bb632e01c0de0a8e6f9e1395eece88 100644 (file)
@@ -8,6 +8,7 @@
 #include "remote.h"
 #include "transport.h"
 #include "parse-options.h"
+#include "submodule.h"
 
 static const char * const push_usage[] = {
        "git push [<options>] [<repository> [<refspec>...]]",
@@ -219,6 +220,21 @@ static int do_push(const char *repo, int flags)
        return !!errs;
 }
 
+static int option_parse_recurse_submodules(const struct option *opt,
+                                  const char *arg, int unset)
+{
+       int *flags = opt->value;
+       if (arg) {
+               if (!strcmp(arg, "check"))
+                       *flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK;
+               else
+                       die("bad %s argument: %s", opt->long_name, arg);
+       } else
+               die("option %s needs an argument (check)", opt->long_name);
+
+       return 0;
+}
+
 int cmd_push(int argc, const char **argv, const char *prefix)
 {
        int flags = 0;
@@ -236,6 +252,9 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN),
                OPT_BIT( 0,  "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN),
                OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE),
+               { OPTION_CALLBACK, 0, "recurse-submodules", &flags, "check",
+                       "controls recursive pushing of submodules",
+                       PARSE_OPT_OPTARG, option_parse_recurse_submodules },
                OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"),
                OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive pack program"),
                OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"),
index e1a687ad0761e46f6ec95659bb585b9014d4abf4..af429e101777f1c5df09734433278cfb075e978c 100644 (file)
@@ -120,9 +120,25 @@ static int show_ref(const char *path, const unsigned char *sha1, int flag, void
        return 0;
 }
 
+static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+{
+       path = strip_namespace(path);
+       /*
+        * Advertise refs outside our current namespace as ".have"
+        * refs, so that the client can use them to minimize data
+        * transfer but will otherwise ignore them. This happens to
+        * cover ".have" that are thrown in by add_one_alternate_ref()
+        * to mark histories that are complete in our alternates as
+        * well.
+        */
+       if (!path)
+               path = ".have";
+       return show_ref(path, sha1, flag, cb_data);
+}
+
 static void write_head_info(void)
 {
-       for_each_ref(show_ref, NULL);
+       for_each_ref(show_ref_cb, NULL);
        if (!sent_capabilities)
                show_ref("capabilities^{}", null_sha1, 0, NULL);
 
@@ -189,21 +205,15 @@ static int copy_to_sideband(int in, int out, void *arg)
        return 0;
 }
 
-static int run_receive_hook(struct command *commands, const char *hook_name)
+typedef int (*feed_fn)(void *, const char **, size_t *);
+static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_state)
 {
-       static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4];
-       struct command *cmd;
        struct child_process proc;
        struct async muxer;
        const char *argv[2];
-       int have_input = 0, code;
-
-       for (cmd = commands; !have_input && cmd; cmd = cmd->next) {
-               if (!cmd->error_string)
-                       have_input = 1;
-       }
+       int code;
 
-       if (!have_input || access(hook_name, X_OK) < 0)
+       if (access(hook_name, X_OK) < 0)
                return 0;
 
        argv[0] = hook_name;
@@ -231,15 +241,13 @@ static int run_receive_hook(struct command *commands, const char *hook_name)
                return code;
        }
 
-       for (cmd = commands; cmd; cmd = cmd->next) {
-               if (!cmd->error_string) {
-                       size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n",
-                               sha1_to_hex(cmd->old_sha1),
-                               sha1_to_hex(cmd->new_sha1),
-                               cmd->ref_name);
-                       if (write_in_full(proc.in, buf, n) != n)
-                               break;
-               }
+       while (1) {
+               const char *buf;
+               size_t n;
+               if (feed(feed_state, &buf, &n))
+                       break;
+               if (write_in_full(proc.in, buf, n) != n)
+                       break;
        }
        close(proc.in);
        if (use_sideband)
@@ -247,6 +255,47 @@ static int run_receive_hook(struct command *commands, const char *hook_name)
        return finish_command(&proc);
 }
 
+struct receive_hook_feed_state {
+       struct command *cmd;
+       struct strbuf buf;
+};
+
+static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
+{
+       struct receive_hook_feed_state *state = state_;
+       struct command *cmd = state->cmd;
+
+       while (cmd && cmd->error_string)
+               cmd = cmd->next;
+       if (!cmd)
+               return -1; /* EOF */
+       strbuf_reset(&state->buf);
+       strbuf_addf(&state->buf, "%s %s %s\n",
+                   sha1_to_hex(cmd->old_sha1), sha1_to_hex(cmd->new_sha1),
+                   cmd->ref_name);
+       state->cmd = cmd->next;
+       if (bufp) {
+               *bufp = state->buf.buf;
+               *sizep = state->buf.len;
+       }
+       return 0;
+}
+
+static int run_receive_hook(struct command *commands, const char *hook_name)
+{
+       struct receive_hook_feed_state state;
+       int status;
+
+       strbuf_init(&state.buf, 0);
+       state.cmd = commands;
+       if (feed_receive_hook(&state, NULL, NULL))
+               return 0;
+       state.cmd = commands;
+       status = run_and_feed_hook(hook_name, feed_receive_hook, &state);
+       strbuf_release(&state.buf);
+       return status;
+}
+
 static int run_update_hook(struct command *cmd)
 {
        static const char update_hook[] = "hooks/update";
@@ -333,6 +382,8 @@ static void refuse_unconfigured_deny_delete_current(void)
 static const char *update(struct command *cmd)
 {
        const char *name = cmd->ref_name;
+       struct strbuf namespaced_name_buf = STRBUF_INIT;
+       const char *namespaced_name;
        unsigned char *old_sha1 = cmd->old_sha1;
        unsigned char *new_sha1 = cmd->new_sha1;
        struct ref_lock *lock;
@@ -343,7 +394,10 @@ static const char *update(struct command *cmd)
                return "funny refname";
        }
 
-       if (is_ref_checked_out(name)) {
+       strbuf_addf(&namespaced_name_buf, "%s%s", get_git_namespace(), name);
+       namespaced_name = strbuf_detach(&namespaced_name_buf, NULL);
+
+       if (is_ref_checked_out(namespaced_name)) {
                switch (deny_current_branch) {
                case DENY_IGNORE:
                        break;
@@ -371,7 +425,7 @@ static const char *update(struct command *cmd)
                        return "deletion prohibited";
                }
 
-               if (!strcmp(name, head_name)) {
+               if (!strcmp(namespaced_name, head_name)) {
                        switch (deny_delete_current) {
                        case DENY_IGNORE:
                                break;
@@ -427,14 +481,14 @@ static const char *update(struct command *cmd)
                        rp_warning("Allowing deletion of corrupt ref.");
                        old_sha1 = NULL;
                }
-               if (delete_ref(name, old_sha1, 0)) {
+               if (delete_ref(namespaced_name, old_sha1, 0)) {
                        rp_error("failed to delete %s", name);
                        return "failed to delete";
                }
                return NULL; /* good */
        }
        else {
-               lock = lock_any_ref_for_update(name, old_sha1, 0);
+               lock = lock_any_ref_for_update(namespaced_name, old_sha1, 0);
                if (!lock) {
                        rp_error("failed to lock %s", name);
                        return "failed to lock";
@@ -491,17 +545,29 @@ static void run_update_post_hook(struct command *commands)
 
 static void check_aliased_update(struct command *cmd, struct string_list *list)
 {
+       struct strbuf buf = STRBUF_INIT;
+       const char *dst_name;
        struct string_list_item *item;
        struct command *dst_cmd;
        unsigned char sha1[20];
        char cmd_oldh[41], cmd_newh[41], dst_oldh[41], dst_newh[41];
        int flag;
 
-       const char *dst_name = resolve_ref(cmd->ref_name, sha1, 0, &flag);
+       strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name);
+       dst_name = resolve_ref(buf.buf, sha1, 0, &flag);
+       strbuf_release(&buf);
 
        if (!(flag & REF_ISSYMREF))
                return;
 
+       dst_name = strip_namespace(dst_name);
+       if (!dst_name) {
+               rp_error("refusing update to broken symref '%s'", cmd->ref_name);
+               cmd->skip_update = 1;
+               cmd->error_string = "broken symref";
+               return;
+       }
+
        if ((item = string_list_lookup(list, dst_name)) == NULL)
                return;
 
index ebf610e64a267c5ca769d70203b282fb8f96a434..3a9c80f3dbfe26d5623c118fc0fcaa257e01b973 100644 (file)
@@ -777,6 +777,5 @@ int cmd_reflog(int argc, const char **argv, const char *prefix)
        if (!strcmp(argv[1], "delete"))
                return cmd_reflog_delete(argc - 1, argv + 1, prefix);
 
-       /* Not a recognized reflog command..*/
-       usage(reflog_usage);
+       return cmd_log_reflog(argc, argv, prefix);
 }
index 9ff1cac69b9fd21c5f7dfc06b5859fedccc67644..b25dfb44227c42a5d8dadb72c04e5aa694d2ace2 100644 (file)
@@ -88,16 +88,6 @@ static inline int postfixcmp(const char *string, const char *postfix)
        return strcmp(string + len1 - len2, postfix);
 }
 
-static int opt_parse_track(const struct option *opt, const char *arg, int not)
-{
-       struct string_list *list = opt->value;
-       if (not)
-               string_list_clear(list, 0);
-       else
-               string_list_append(list, arg);
-       return 0;
-}
-
 static int fetch_remote(const char *name)
 {
        const char *argv[] = { "fetch", name, NULL, NULL };
@@ -176,8 +166,8 @@ static int add(int argc, const char **argv)
                            TAGS_SET),
                OPT_SET_INT(0, NULL, &fetch_tags,
                            "or do not fetch any tag at all (--no-tags)", TAGS_UNSET),
-               OPT_CALLBACK('t', "track", &track, "branch",
-                       "branch(es) to track", opt_parse_track),
+               OPT_STRING_LIST('t', "track", &track, "branch",
+                               "branch(es) to track"),
                OPT_STRING('m', "master", &master, "branch", "master branch"),
                { OPTION_CALLBACK, 0, "mirror", &mirror, "push|fetch",
                        "set up remote as a mirror to push to or fetch from",
@@ -580,7 +570,7 @@ static int read_remote_branches(const char *refname,
        unsigned char orig_sha1[20];
        const char *symref;
 
-       strbuf_addf(&buf, "refs/remotes/%s", rename->old);
+       strbuf_addf(&buf, "refs/remotes/%s/", rename->old);
        if (!prefixcmp(refname, buf.buf)) {
                item = string_list_append(rename->remote_branches, xstrdup(refname));
                symref = resolve_ref(refname, orig_sha1, 1, &flag);
@@ -631,10 +621,11 @@ static int mv(int argc, const char **argv)
                OPT_END()
        };
        struct remote *oldremote, *newremote;
-       struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT;
+       struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT,
+               old_remote_context = STRBUF_INIT;
        struct string_list remote_branches = STRING_LIST_INIT_NODUP;
        struct rename_info rename;
-       int i;
+       int i, refspec_updated = 0;
 
        if (argc != 3)
                usage_with_options(builtin_remote_rename_usage, options);
@@ -669,15 +660,25 @@ static int mv(int argc, const char **argv)
        strbuf_addf(&buf, "remote.%s.fetch", rename.new);
        if (git_config_set_multivar(buf.buf, NULL, NULL, 1))
                return error("Could not remove config section '%s'", buf.buf);
+       strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old);
        for (i = 0; i < oldremote->fetch_refspec_nr; i++) {
                char *ptr;
 
                strbuf_reset(&buf2);
                strbuf_addstr(&buf2, oldremote->fetch_refspec[i]);
-               ptr = strstr(buf2.buf, rename.old);
-               if (ptr)
-                       strbuf_splice(&buf2, ptr-buf2.buf, strlen(rename.old),
-                                       rename.new, strlen(rename.new));
+               ptr = strstr(buf2.buf, old_remote_context.buf);
+               if (ptr) {
+                       refspec_updated = 1;
+                       strbuf_splice(&buf2,
+                                     ptr-buf2.buf + strlen(":refs/remotes/"),
+                                     strlen(rename.old), rename.new,
+                                     strlen(rename.new));
+               } else
+                       warning("Not updating non-default fetch respec\n"
+                               "\t%s\n"
+                               "\tPlease update the configuration manually if necessary.",
+                               buf2.buf);
+
                if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0))
                        return error("Could not append '%s'", buf.buf);
        }
@@ -695,6 +696,9 @@ static int mv(int argc, const char **argv)
                }
        }
 
+       if (!refspec_updated)
+               return 0;
+
        /*
         * First remove symrefs, then rename the rest, finally create
         * the new symrefs.
@@ -1113,7 +1117,7 @@ static int show(int argc, const char **argv)
                        url = states.remote->url;
                        url_nr = states.remote->url_nr;
                }
-               for (i=0; i < url_nr; i++)
+               for (i = 0; i < url_nr; i++)
                        printf("  Push  URL: %s\n", url[i]);
                if (!i)
                        printf("  Push  URL: %s\n", "(no URL)");
index 98bca044c1267fa63cb084a45aaf621e85f7d9b6..811e8e252c1c6a54e65179557203daf2bc42bdb9 100644 (file)
@@ -33,25 +33,6 @@ static const char *reset_type_names[] = {
        N_("mixed"), N_("soft"), N_("hard"), N_("merge"), N_("keep"), NULL
 };
 
-static char *args_to_str(const char **argv)
-{
-       char *buf = NULL;
-       unsigned long len, space = 0, nr = 0;
-
-       for (; *argv; argv++) {
-               len = strlen(*argv);
-               ALLOC_GROW(buf, nr + 1 + len, space);
-               if (nr)
-                       buf[nr++] = ' ';
-               memcpy(buf + nr, *argv, len);
-               nr += len;
-       }
-       ALLOC_GROW(buf, nr + 1, space);
-       buf[nr] = '\0';
-
-       return buf;
-}
-
 static inline int is_merge(void)
 {
        return !access(git_path("MERGE_HEAD"), F_OK);
@@ -162,7 +143,7 @@ static void update_index_from_diff(struct diff_queue_struct *q,
 
        for (i = 0; i < q->nr; i++) {
                struct diff_filespec *one = q->queue[i]->one;
-               if (one->mode) {
+               if (one->mode && !is_null_sha1(one->sha1)) {
                        struct cache_entry *ce;
                        ce = make_cache_entry(one->mode, one->sha1, one->path,
                                0, 0);
@@ -215,14 +196,18 @@ static int read_from_tree(const char *prefix, const char **argv,
        return update_index_refresh(index_fd, lock, refresh_flags);
 }
 
-static void prepend_reflog_action(const char *action, char *buf, size_t size)
+static void set_reflog_message(struct strbuf *sb, const char *action,
+                              const char *rev)
 {
-       const char *sep = ": ";
        const char *rla = getenv("GIT_REFLOG_ACTION");
-       if (!rla)
-               rla = sep = "";
-       if (snprintf(buf, size, "%s%s%s", rla, sep, action) >= size)
-               warning(_("Reflog action message too long: %.*s..."), 50, buf);
+
+       strbuf_reset(sb);
+       if (rla)
+               strbuf_addf(sb, "%s: %s", rla, action);
+       else if (rev)
+               strbuf_addf(sb, "reset: moving to %s", rev);
+       else
+               strbuf_addf(sb, "reset: %s", action);
 }
 
 static void die_if_unmerged_cache(int reset_type)
@@ -241,7 +226,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
        unsigned char sha1[20], *orig = NULL, sha1_orig[20],
                                *old_orig = NULL, sha1_old_orig[20];
        struct commit *commit;
-       char *reflog_action, msg[1024];
+       struct strbuf msg = STRBUF_INIT;
        const struct option options[] = {
                OPT__QUIET(&quiet, "be quiet, only report errors"),
                OPT_SET_INT(0, "mixed", &reset_type,
@@ -261,8 +246,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 
        argc = parse_options(argc, argv, prefix, options, git_reset_usage,
                                                PARSE_OPT_KEEP_DASHDASH);
-       reflog_action = args_to_str(argv);
-       setenv("GIT_REFLOG_ACTION", reflog_action, 0);
 
        /*
         * Possible arguments are:
@@ -357,13 +340,13 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                old_orig = sha1_old_orig;
        if (!get_sha1("HEAD", sha1_orig)) {
                orig = sha1_orig;
-               prepend_reflog_action("updating ORIG_HEAD", msg, sizeof(msg));
-               update_ref(msg, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR);
+               set_reflog_message(&msg, "updating ORIG_HEAD", NULL);
+               update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR);
        }
        else if (old_orig)
                delete_ref("ORIG_HEAD", old_orig, 0);
-       prepend_reflog_action("updating HEAD", msg, sizeof(msg));
-       update_ref_status = update_ref(msg, "HEAD", sha1, orig, 0, MSG_ON_ERR);
+       set_reflog_message(&msg, "updating HEAD", rev);
+       update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0, MSG_ON_ERR);
 
        switch (reset_type) {
        case HARD:
@@ -380,7 +363,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 
        remove_branch_state();
 
-       free(reflog_action);
+       strbuf_release(&msg);
 
        return update_ref_status;
 }
index 1f27c63343904a969e60ab19408495007f59d133..3117776c2c030bec03563f043c4dc8bb34eb17cd 100644 (file)
@@ -258,12 +258,7 @@ static void write_message(struct strbuf *msgbuf, const char *filename)
 
 static struct tree *empty_tree(void)
 {
-       struct tree *tree = xcalloc(1, sizeof(struct tree));
-
-       tree->object.parsed = 1;
-       tree->object.type = OBJ_TREE;
-       pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1);
-       return tree;
+       return lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN);
 }
 
 static NORETURN void die_dirty_index(const char *me)
index facc63a79ec0c86fd9df59792cd4f20030af5db1..4b480d7c7ca6c6258a5cd82cfc88df62cd0d218f 100644 (file)
@@ -26,14 +26,14 @@ static const char **default_arg;
 
 static const char *get_color_code(int idx)
 {
-       if (showbranch_use_color)
+       if (want_color(showbranch_use_color))
                return column_colors_ansi[idx % column_colors_ansi_max];
        return "";
 }
 
 static const char *get_color_reset_code(void)
 {
-       if (showbranch_use_color)
+       if (want_color(showbranch_use_color))
                return GIT_COLOR_RESET;
        return "";
 }
@@ -573,7 +573,7 @@ static int git_show_branch_config(const char *var, const char *value, void *cb)
        }
 
        if (!strcmp(var, "color.showbranch")) {
-               showbranch_use_color = git_config_colorbool(var, value, -1);
+               showbranch_use_color = git_config_colorbool(var, value);
                return 0;
        }
 
@@ -685,9 +685,6 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
 
        git_config(git_show_branch_config, NULL);
 
-       if (showbranch_use_color == -1)
-               showbranch_use_color = git_use_color_default;
-
        /* If nothing is specified, try the default first */
        if (ac == 1 && default_num) {
                ac = default_num;
index ec926fc8ee4512abddc67309421b4394706329f5..667515e5278d22548fb83507347ab652533a67b2 100644 (file)
 #include "tag.h"
 #include "run-command.h"
 #include "parse-options.h"
+#include "diff.h"
+#include "revision.h"
 
 static const char * const git_tag_usage[] = {
        "git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]",
        "git tag -d <tagname>...",
-       "git tag -l [-n[<num>]] [<pattern>]",
+       "git tag -l [-n[<num>]] [<pattern>...]",
        "git tag -v <tagname>...",
        NULL
 };
@@ -24,17 +26,70 @@ static const char * const git_tag_usage[] = {
 static char signingkey[1000];
 
 struct tag_filter {
-       const char *pattern;
+       const char **patterns;
        int lines;
        struct commit_list *with_commit;
 };
 
+static int match_pattern(const char **patterns, const char *ref)
+{
+       /* no pattern means match everything */
+       if (!*patterns)
+               return 1;
+       for (; *patterns; patterns++)
+               if (!fnmatch(*patterns, ref, 0))
+                       return 1;
+       return 0;
+}
+
+static int in_commit_list(const struct commit_list *want, struct commit *c)
+{
+       for (; want; want = want->next)
+               if (!hashcmp(want->item->object.sha1, c->object.sha1))
+                       return 1;
+       return 0;
+}
+
+static int contains_recurse(struct commit *candidate,
+                           const struct commit_list *want)
+{
+       struct commit_list *p;
+
+       /* was it previously marked as containing a want commit? */
+       if (candidate->object.flags & TMP_MARK)
+               return 1;
+       /* or marked as not possibly containing a want commit? */
+       if (candidate->object.flags & UNINTERESTING)
+               return 0;
+       /* or are we it? */
+       if (in_commit_list(want, candidate))
+               return 1;
+
+       if (parse_commit(candidate) < 0)
+               return 0;
+
+       /* Otherwise recurse and mark ourselves for future traversals. */
+       for (p = candidate->parents; p; p = p->next) {
+               if (contains_recurse(p->item, want)) {
+                       candidate->object.flags |= TMP_MARK;
+                       return 1;
+               }
+       }
+       candidate->object.flags |= UNINTERESTING;
+       return 0;
+}
+
+static int contains(struct commit *candidate, const struct commit_list *want)
+{
+       return contains_recurse(candidate, want);
+}
+
 static int show_reference(const char *refname, const unsigned char *sha1,
                          int flag, void *cb_data)
 {
        struct tag_filter *filter = cb_data;
 
-       if (!fnmatch(filter->pattern, refname, 0)) {
+       if (match_pattern(filter->patterns, refname)) {
                int i;
                unsigned long size;
                enum object_type type;
@@ -47,7 +102,7 @@ static int show_reference(const char *refname, const unsigned char *sha1,
                        commit = lookup_commit_reference_gently(sha1, 1);
                        if (!commit)
                                return 0;
-                       if (!is_descendant_of(commit, filter->with_commit))
+                       if (!contains(commit, filter->with_commit))
                                return 0;
                }
 
@@ -88,15 +143,12 @@ static int show_reference(const char *refname, const unsigned char *sha1,
        return 0;
 }
 
-static int list_tags(const char *pattern, int lines,
+static int list_tags(const char **patterns, int lines,
                        struct commit_list *with_commit)
 {
        struct tag_filter filter;
 
-       if (pattern == NULL)
-               pattern = "*";
-
-       filter.pattern = pattern;
+       filter.patterns = patterns;
        filter.lines = lines;
        filter.with_commit = with_commit;
 
@@ -425,7 +477,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        if (list + delete + verify > 1)
                usage_with_options(git_tag_usage, options);
        if (list)
-               return list_tags(argv[0], lines == -1 ? 0 : lines,
+               return list_tags(argv, lines == -1 ? 0 : lines,
                                 with_commit);
        if (lines != -1)
                die(_("-n option is only allowed with -l."));
index f63973c9143c4109a3d1e40df9ec66a32498fe00..14e04e6795bd514fb1fec506073d8a9a71668cfa 100644 (file)
@@ -90,7 +90,7 @@ static void use(int bytes)
 
 static void *get_data(unsigned long size)
 {
-       z_stream stream;
+       git_zstream stream;
        void *buf = xmalloc(size);
 
        memset(&stream, 0, sizeof(stream));
index 76ba1d5881b3cddc527bd363dec340c83dde605f..835c62ab15c742af2f75e7bf66e35501701ed15a 100644 (file)
@@ -11,7 +11,7 @@ static const char * const git_update_ref_usage[] = {
 
 int cmd_update_ref(int argc, const char **argv, const char *prefix)
 {
-       const char *refname, *oldval, *msg=NULL;
+       const char *refname, *oldval, *msg = NULL;
        unsigned char sha1[20], oldsha1[20];
        int delete = 0, no_deref = 0, flags = 0;
        struct option options[] = {
index 73f788ef247febabbabe9bc0d21a890afa198cff..2d0b38333eadef0a5c6501e5a3046ebc2afa048e 100644 (file)
@@ -64,7 +64,7 @@ static int run_upload_archive(int argc, const char **argv, const char *prefix)
        sent_argv[sent_argc] = NULL;
 
        /* parse all options sent by the client */
-       return write_archive(sent_argc, sent_argv, prefix, 0);
+       return write_archive(sent_argc, sent_argv, prefix, 0, NULL, 1);
 }
 
 __attribute__((format (printf, 1, 2)))
index b6079ae6cb03c7f3112c6eebc8c9a012d690a125..e841b4a38d2b47c39d95683a5747cbc19da1f48b 100644 (file)
 #include "builtin.h"
 #include "cache.h"
-#include "pack.h"
-#include "pack-revindex.h"
+#include "run-command.h"
 #include "parse-options.h"
 
-#define MAX_CHAIN 50
-
 #define VERIFY_PACK_VERBOSE 01
 #define VERIFY_PACK_STAT_ONLY 02
 
-static void show_pack_info(struct packed_git *p, unsigned int flags)
-{
-       uint32_t nr_objects, i;
-       int cnt;
-       int stat_only = flags & VERIFY_PACK_STAT_ONLY;
-       unsigned long chain_histogram[MAX_CHAIN+1], baseobjects;
-
-       nr_objects = p->num_objects;
-       memset(chain_histogram, 0, sizeof(chain_histogram));
-       baseobjects = 0;
-
-       for (i = 0; i < nr_objects; i++) {
-               const unsigned char *sha1;
-               unsigned char base_sha1[20];
-               const char *type;
-               unsigned long size;
-               unsigned long store_size;
-               off_t offset;
-               unsigned int delta_chain_length;
-
-               sha1 = nth_packed_object_sha1(p, i);
-               if (!sha1)
-                       die("internal error pack-check nth-packed-object");
-               offset = nth_packed_object_offset(p, i);
-               type = packed_object_info_detail(p, offset, &size, &store_size,
-                                                &delta_chain_length,
-                                                base_sha1);
-               if (!stat_only)
-                       printf("%s ", sha1_to_hex(sha1));
-               if (!delta_chain_length) {
-                       if (!stat_only)
-                               printf("%-6s %lu %lu %"PRIuMAX"\n",
-                                      type, size, store_size, (uintmax_t)offset);
-                       baseobjects++;
-               }
-               else {
-                       if (!stat_only)
-                               printf("%-6s %lu %lu %"PRIuMAX" %u %s\n",
-                                      type, size, store_size, (uintmax_t)offset,
-                                      delta_chain_length, sha1_to_hex(base_sha1));
-                       if (delta_chain_length <= MAX_CHAIN)
-                               chain_histogram[delta_chain_length]++;
-                       else
-                               chain_histogram[0]++;
-               }
-       }
-
-       if (baseobjects)
-               printf("non delta: %lu object%s\n",
-                      baseobjects, baseobjects > 1 ? "s" : "");
-
-       for (cnt = 1; cnt <= MAX_CHAIN; cnt++) {
-               if (!chain_histogram[cnt])
-                       continue;
-               printf("chain length = %d: %lu object%s\n", cnt,
-                      chain_histogram[cnt],
-                      chain_histogram[cnt] > 1 ? "s" : "");
-       }
-       if (chain_histogram[0])
-               printf("chain length > %d: %lu object%s\n", MAX_CHAIN,
-                      chain_histogram[0],
-                      chain_histogram[0] > 1 ? "s" : "");
-}
-
 static int verify_one_pack(const char *path, unsigned int flags)
 {
-       char arg[PATH_MAX];
-       int len;
+       struct child_process index_pack;
+       const char *argv[] = {"index-pack", NULL, NULL, NULL };
+       struct strbuf arg = STRBUF_INIT;
        int verbose = flags & VERIFY_PACK_VERBOSE;
        int stat_only = flags & VERIFY_PACK_STAT_ONLY;
-       struct packed_git *pack;
        int err;
 
-       len = strlcpy(arg, path, PATH_MAX);
-       if (len >= PATH_MAX)
-               return error("name too long: %s", path);
-
-       /*
-        * In addition to "foo.idx" we accept "foo.pack" and "foo";
-        * normalize these forms to "foo.idx" for add_packed_git().
-        */
-       if (has_extension(arg, ".pack")) {
-               strcpy(arg + len - 5, ".idx");
-               len--;
-       } else if (!has_extension(arg, ".idx")) {
-               if (len + 4 >= PATH_MAX)
-                       return error("name too long: %s.idx", arg);
-               strcpy(arg + len, ".idx");
-               len += 4;
-       }
+       if (stat_only)
+               argv[1] = "--verify-stat-only";
+       else if (verbose)
+               argv[1] = "--verify-stat";
+       else
+               argv[1] = "--verify";
 
        /*
-        * add_packed_git() uses our buffer (containing "foo.idx") to
-        * build the pack filename ("foo.pack").  Make sure it fits.
+        * In addition to "foo.pack" we accept "foo.idx" and "foo";
+        * normalize these forms to "foo.pack" for "index-pack --verify".
         */
-       if (len + 1 >= PATH_MAX) {
-               arg[len - 4] = '\0';
-               return error("name too long: %s.pack", arg);
-       }
-
-       pack = add_packed_git(arg, len, 1);
-       if (!pack)
-               return error("packfile %s not found.", arg);
+       strbuf_addstr(&arg, path);
+       if (has_extension(arg.buf, ".idx"))
+               strbuf_splice(&arg, arg.len - 3, 3, "pack", 4);
+       else if (!has_extension(arg.buf, ".pack"))
+               strbuf_add(&arg, ".pack", 5);
+       argv[2] = arg.buf;
 
-       install_packed_git(pack);
+       memset(&index_pack, 0, sizeof(index_pack));
+       index_pack.argv = argv;
+       index_pack.git_cmd = 1;
 
-       if (!stat_only)
-               err = verify_pack(pack);
-       else
-               err = open_pack_index(pack);
+       err = run_command(&index_pack);
 
        if (verbose || stat_only) {
                if (err)
-                       printf("%s: bad\n", pack->pack_name);
+                       printf("%s: bad\n", arg.buf);
                else {
-                       show_pack_info(pack, flags);
                        if (!stat_only)
-                               printf("%s: ok\n", pack->pack_name);
+                               printf("%s: ok\n", arg.buf);
                }
        }
+       strbuf_release(&arg);
 
        return err;
 }
@@ -159,7 +78,6 @@ int cmd_verify_pack(int argc, const char **argv, const char *prefix)
        for (i = 0; i < argc; i++) {
                if (verify_one_pack(argv[i], flags))
                        err = 1;
-               discard_revindex();
        }
 
        return err;
index f48fd7d4c1c706b68e879e4dfabdd3fb8597a328..6bf849740c6de45fe8ca11eca2a06a52c7ebc0bb 100644 (file)
--- a/bundle.c
+++ b/bundle.c
@@ -380,12 +380,15 @@ int create_bundle(struct bundle_header *header, const char *path,
        return 0;
 }
 
-int unbundle(struct bundle_header *header, int bundle_fd)
+int unbundle(struct bundle_header *header, int bundle_fd, int flags)
 {
        const char *argv_index_pack[] = {"index-pack",
-               "--fix-thin", "--stdin", NULL};
+                                        "--fix-thin", "--stdin", NULL, NULL};
        struct child_process ip;
 
+       if (flags & BUNDLE_VERBOSE)
+               argv_index_pack[3] = "-v";
+
        if (verify_bundle(header, 0))
                return -1;
        memset(&ip, 0, sizeof(ip));
index e2aedd60d6ad1482bb6da173c853e6ba4805c8d7..c5a22c8d10c7bf5e27536a3b84033b77f3542445 100644 (file)
--- a/bundle.h
+++ b/bundle.h
@@ -18,7 +18,8 @@ int read_bundle_header(const char *path, struct bundle_header *header);
 int create_bundle(struct bundle_header *header, const char *path,
                int argc, const char **argv);
 int verify_bundle(struct bundle_header *header, int verbose);
-int unbundle(struct bundle_header *header, int bundle_fd);
+#define BUNDLE_VERBOSE 1
+int unbundle(struct bundle_header *header, int bundle_fd, int flags);
 int list_bundle_refs(struct bundle_header *header,
                int argc, const char **argv);
 
diff --git a/cache.h b/cache.h
index e11cf6ab1c73ac97c94e76e8c8699d55af95b978..607c2ea612889c46e81ae375b6985db18e529a8e 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -6,6 +6,7 @@
 #include "hash.h"
 #include "advice.h"
 #include "gettext.h"
+#include "convert.h"
 
 #include SHA1_HEADER
 #ifndef git_SHA_CTX
 #endif
 
 #include <zlib.h>
-#if defined(NO_DEFLATE_BOUND) || ZLIB_VERNUM < 0x1200
-#define deflateBound(c,s)  ((s) + (((s) + 7) >> 3) + (((s) + 63) >> 6) + 11)
-#endif
-
-void git_inflate_init(z_streamp strm);
-void git_inflate_end(z_streamp strm);
-int git_inflate(z_streamp strm, int flush);
+typedef struct git_zstream {
+       z_stream z;
+       unsigned long avail_in;
+       unsigned long avail_out;
+       unsigned long total_in;
+       unsigned long total_out;
+       unsigned char *next_in;
+       unsigned char *next_out;
+} git_zstream;
+
+void git_inflate_init(git_zstream *);
+void git_inflate_init_gzip_only(git_zstream *);
+void git_inflate_end(git_zstream *);
+int git_inflate(git_zstream *, int flush);
+
+void git_deflate_init(git_zstream *, int level);
+void git_deflate_init_gzip(git_zstream *, int level);
+void git_deflate_end(git_zstream *);
+int git_deflate_end_gently(git_zstream *);
+int git_deflate(git_zstream *, int flush);
+unsigned long git_deflate_bound(git_zstream *, unsigned long);
 
 #if defined(DT_UNKNOWN) && !defined(NO_D_TYPE_IN_DIRENT)
 #define DTYPE(de)      ((de)->d_type)
@@ -379,6 +394,7 @@ static inline enum object_type object_type(unsigned int mode)
 }
 
 #define GIT_DIR_ENVIRONMENT "GIT_DIR"
+#define GIT_NAMESPACE_ENVIRONMENT "GIT_NAMESPACE"
 #define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
 #define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
 #define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
@@ -419,13 +435,16 @@ extern char *get_object_directory(void);
 extern char *get_index_file(void);
 extern char *get_graft_file(void);
 extern int set_git_dir(const char *path);
+extern const char *get_git_namespace(void);
+extern const char *strip_namespace(const char *namespaced_ref);
 extern const char *get_git_work_tree(void);
-extern const char *read_gitfile_gently(const char *path);
+extern const char *read_gitfile(const char *path);
 extern void set_git_work_tree(const char *tree);
 
 #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
 
 extern const char **get_pathspec(const char *prefix, const char **pathspec);
+extern const char *pathspec_prefix(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);
@@ -582,35 +601,6 @@ extern int fsync_object_files;
 extern int core_preload_index;
 extern int core_apply_sparse_checkout;
 
-enum safe_crlf {
-       SAFE_CRLF_FALSE = 0,
-       SAFE_CRLF_FAIL = 1,
-       SAFE_CRLF_WARN = 2
-};
-
-extern enum safe_crlf safe_crlf;
-
-enum auto_crlf {
-       AUTO_CRLF_FALSE = 0,
-       AUTO_CRLF_TRUE = 1,
-       AUTO_CRLF_INPUT = -1
-};
-
-extern enum auto_crlf auto_crlf;
-
-enum eol {
-       EOL_UNSET,
-       EOL_CRLF,
-       EOL_LF,
-#ifdef NATIVE_CRLF
-       EOL_NATIVE = EOL_CRLF
-#else
-       EOL_NATIVE = EOL_LF
-#endif
-};
-
-extern enum eol core_eol;
-
 enum branch_track {
        BRANCH_TRACK_UNSPECIFIED = -1,
        BRANCH_TRACK_NEVER = 0,
@@ -747,7 +737,7 @@ extern char *expand_user_path(const char *path);
 char *enter_repo(char *path, int strict);
 static inline int is_absolute_path(const char *path)
 {
-       return path[0] == '/' || has_dos_drive_prefix(path);
+       return is_dir_sep(path[0]) || has_dos_drive_prefix(path);
 }
 int is_directory(const char *);
 const char *real_path(const char *path);
@@ -780,10 +770,16 @@ extern int hash_sha1_file(const void *buf, unsigned long len, const char *type,
 extern int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *return_sha1);
 extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *);
 extern int force_object_loose(const unsigned char *sha1, time_t mtime);
+extern void *map_sha1_file(const unsigned char *sha1, unsigned long *size);
+extern int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz);
+extern int parse_sha1_header(const char *hdr, unsigned long *sizep);
 
 /* global flag to enable extra checks when accessing packed objects */
 extern int do_check_packed_object_crc;
 
+/* for development: log offset of pack access */
+extern const char *log_pack_access;
+
 extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
 
 extern int move_temp_to_file(const char *tmpfile, const char *filename);
@@ -1009,7 +1005,7 @@ extern struct packed_git *find_sha1_pack(const unsigned char *sha1,
 extern void pack_report(void);
 extern int open_pack_index(struct packed_git *);
 extern void close_pack_index(struct packed_git *);
-extern unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned int *);
+extern unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned long *);
 extern void close_pack_windows(struct packed_git *);
 extern void unuse_pack(struct pack_window **);
 extern void free_pack_by_name(const char *);
@@ -1021,7 +1017,36 @@ extern off_t find_pack_entry_one(const unsigned char *, struct packed_git *);
 extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsigned long *);
 extern unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep);
 extern unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t);
-extern const char *packed_object_info_detail(struct packed_git *, off_t, unsigned long *, unsigned long *, unsigned int *, unsigned char *);
+extern int unpack_object_header(struct packed_git *, struct pack_window **, off_t *, unsigned long *);
+
+struct object_info {
+       /* Request */
+       unsigned long *sizep;
+
+       /* Response */
+       enum {
+               OI_CACHED,
+               OI_LOOSE,
+               OI_PACKED,
+               OI_DBCACHED
+       } whence;
+       union {
+               /*
+                * struct {
+                *      ... Nothing to expose in this case
+                * } cached;
+                * struct {
+                *      ... Nothing to expose in this case
+                * } loose;
+                */
+               struct {
+                       struct packed_git *pack;
+                       off_t offset;
+                       unsigned int is_delta;
+               } packed;
+       } u;
+};
+extern int sha1_object_info_extended(const unsigned char *, struct object_info *);
 
 /* Dumb servers support */
 extern int update_server_info(int);
@@ -1063,6 +1088,8 @@ extern int config_error_nonbool(const char *);
 extern const char *get_log_output_encoding(void);
 extern const char *get_commit_output_encoding(void);
 
+extern int git_config_parse_parameter(const char *, config_fn_t fn, void *data);
+
 extern const char *config_exclusive_filename;
 
 #define MAX_GITNAME (1000)
@@ -1129,13 +1156,6 @@ extern void trace_strbuf(const char *key, const struct strbuf *buf);
 
 void packet_trace_identity(const char *prog);
 
-/* convert.c */
-/* returns 1 if *dst was used */
-extern int convert_to_git(const char *path, const char *src, size_t len,
-                          struct strbuf *dst, enum safe_crlf checksafe);
-extern int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst);
-extern int renormalize_buffer(const char *path, const char *src, size_t len, struct strbuf *dst);
-
 /* add */
 /*
  * return 0 if success, 1 - if addition of a file failed and
@@ -1175,7 +1195,7 @@ extern int ws_blank_line(const char *line, int len, unsigned ws_rule);
 #define ws_tab_width(rule)     ((rule) & WS_TAB_WIDTH_MASK)
 
 /* ls-files */
-int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset);
+int report_path_error(const char *ps_matched, const char **pathspec, const char *prefix);
 void overlay_tree_on_cache(const char *tree_name, const char *prefix);
 
 char *alias_lookup(const char *alias);
diff --git a/color.c b/color.c
index 3db214c24720496f13481b718e46810c21f53b8d..e8e26818b3b1f2ffce1374e2edf88b40c575c3dd 100644 (file)
--- a/color.c
+++ b/color.c
@@ -1,7 +1,8 @@
 #include "cache.h"
 #include "color.h"
 
-int git_use_color_default = 0;
+static int git_use_color_default = 0;
+int color_stdout_is_tty = -1;
 
 /*
  * The list of available column colors.
@@ -157,7 +158,7 @@ bad:
        die("bad color value '%.*s' for variable '%s'", value_len, value, var);
 }
 
-int git_config_colorbool(const char *var, const char *value, int stdout_is_tty)
+int git_config_colorbool(const char *var, const char *value)
 {
        if (value) {
                if (!strcasecmp(value, "never"))
@@ -165,7 +166,7 @@ int git_config_colorbool(const char *var, const char *value, int stdout_is_tty)
                if (!strcasecmp(value, "always"))
                        return 1;
                if (!strcasecmp(value, "auto"))
-                       goto auto_color;
+                       return GIT_COLOR_AUTO;
        }
 
        if (!var)
@@ -176,10 +177,14 @@ int git_config_colorbool(const char *var, const char *value, int stdout_is_tty)
                return 0;
 
        /* any normal truth value defaults to 'auto' */
- auto_color:
-       if (stdout_is_tty < 0)
-               stdout_is_tty = isatty(1);
-       if (stdout_is_tty || (pager_in_use() && pager_use_color)) {
+       return GIT_COLOR_AUTO;
+}
+
+static int check_auto_color(void)
+{
+       if (color_stdout_is_tty < 0)
+               color_stdout_is_tty = isatty(1);
+       if (color_stdout_is_tty || (pager_in_use() && pager_use_color)) {
                char *term = getenv("TERM");
                if (term && strcmp(term, "dumb"))
                        return 1;
@@ -187,13 +192,36 @@ int git_config_colorbool(const char *var, const char *value, int stdout_is_tty)
        return 0;
 }
 
-int git_color_default_config(const char *var, const char *value, void *cb)
+int want_color(int var)
+{
+       static int want_auto = -1;
+
+       if (var < 0)
+               var = git_use_color_default;
+
+       if (var == GIT_COLOR_AUTO) {
+               if (want_auto < 0)
+                       want_auto = check_auto_color();
+               return want_auto;
+       }
+       return var;
+}
+
+int git_color_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "color.ui")) {
-               git_use_color_default = git_config_colorbool(var, value, -1);
+               git_use_color_default = git_config_colorbool(var, value);
                return 0;
        }
 
+       return 0;
+}
+
+int git_color_default_config(const char *var, const char *value, void *cb)
+{
+       if (git_color_config(var, value, cb) < 0)
+               return -1;
+
        return git_default_config(var, value, cb);
 }
 
diff --git a/color.h b/color.h
index 68a926a2cdfb870ae0da0bfbc4c5b36681609911..9a8495bb7ff06eb4e94e190d902b48c23fc021f9 100644 (file)
--- a/color.h
+++ b/color.h
@@ -49,20 +49,34 @@ struct strbuf;
 #define GIT_COLOR_NIL "NIL"
 
 /*
- * This variable stores the value of color.ui
+ * The first three are chosen to match common usage in the code, and what is
+ * returned from git_config_colorbool. The "auto" value can be returned from
+ * config_colorbool, and will be converted by want_color() into either 0 or 1.
  */
-extern int git_use_color_default;
+#define GIT_COLOR_UNKNOWN -1
+#define GIT_COLOR_NEVER  0
+#define GIT_COLOR_ALWAYS 1
+#define GIT_COLOR_AUTO   2
 
 /* A default list of colors to use for commit graphs and show-branch output */
 extern const char *column_colors_ansi[];
 extern const int column_colors_ansi_max;
 
 /*
- * Use this instead of git_default_config if you need the value of color.ui.
+ * Generally the color code will lazily figure this out itself, but
+ * this provides a mechanism for callers to override autodetection.
  */
+extern int color_stdout_is_tty;
+
+/*
+ * Use the first one if you need only color config; the second is a convenience
+ * if you are just going to change to git_default_config, too.
+ */
+int git_color_config(const char *var, const char *value, void *cb);
 int git_color_default_config(const char *var, const char *value, void *cb);
 
-int git_config_colorbool(const char *var, const char *value, int stdout_is_tty);
+int git_config_colorbool(const char *var, const char *value);
+int want_color(int var);
 void color_parse(const char *value, const char *var, char *dst);
 void color_parse_mem(const char *value, int len, const char *var, char *dst);
 __attribute__((format (printf, 3, 4)))
index 655fa89d8a7ffe3c3823080f5af3e5e385e95440..214014dc645e43ae257e46f1046c8ca0d21d3472 100644 (file)
@@ -7,6 +7,7 @@
 #include "xdiff-interface.h"
 #include "log-tree.h"
 #include "refs.h"
+#include "userdiff.h"
 
 static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent)
 {
@@ -92,7 +93,9 @@ struct sline {
        unsigned long *p_lno;
 };
 
-static char *grab_blob(const unsigned char *sha1, unsigned int mode, unsigned long *size)
+static char *grab_blob(const unsigned char *sha1, unsigned int mode,
+                      unsigned long *size, struct userdiff_driver *textconv,
+                      const char *path)
 {
        char *blob;
        enum object_type type;
@@ -105,6 +108,11 @@ static char *grab_blob(const unsigned char *sha1, unsigned int mode, unsigned lo
                /* deleted blob */
                *size = 0;
                return xcalloc(1, 1);
+       } else if (textconv) {
+               struct diff_filespec *df = alloc_filespec(path);
+               fill_filespec(df, sha1, mode);
+               *size = fill_textconv(textconv, df, &blob);
+               free_filespec(df);
        } else {
                blob = read_sha1_file(sha1, &type, size);
                if (type != OBJ_BLOB)
@@ -204,7 +212,9 @@ static void consume_line(void *state_, char *line, unsigned long len)
 static void combine_diff(const unsigned char *parent, unsigned int mode,
                         mmfile_t *result_file,
                         struct sline *sline, unsigned int cnt, int n,
-                        int num_parent, int result_deleted)
+                        int num_parent, int result_deleted,
+                        struct userdiff_driver *textconv,
+                        const char *path)
 {
        unsigned int p_lno, lno;
        unsigned long nmask = (1UL << n);
@@ -217,7 +227,7 @@ static void combine_diff(const unsigned char *parent, unsigned int mode,
        if (result_deleted)
                return; /* result deleted */
 
-       parent_file.ptr = grab_blob(parent, mode, &sz);
+       parent_file.ptr = grab_blob(parent, mode, &sz, textconv, path);
        parent_file.size = sz;
        memset(&xpp, 0, sizeof(xpp));
        xpp.flags = 0;
@@ -681,8 +691,84 @@ static void dump_quoted_path(const char *head,
        puts(buf.buf);
 }
 
+static void show_combined_header(struct combine_diff_path *elem,
+                                int num_parent,
+                                int dense,
+                                struct rev_info *rev,
+                                int mode_differs,
+                                int show_file_header)
+{
+       struct diff_options *opt = &rev->diffopt;
+       int abbrev = DIFF_OPT_TST(opt, FULL_INDEX) ? 40 : DEFAULT_ABBREV;
+       const char *a_prefix = opt->a_prefix ? opt->a_prefix : "a/";
+       const char *b_prefix = opt->b_prefix ? opt->b_prefix : "b/";
+       const char *c_meta = diff_get_color_opt(opt, DIFF_METAINFO);
+       const char *c_reset = diff_get_color_opt(opt, DIFF_RESET);
+       const char *abb;
+       int added = 0;
+       int deleted = 0;
+       int i;
+
+       if (rev->loginfo && !rev->no_commit_id)
+               show_log(rev);
+
+       dump_quoted_path(dense ? "diff --cc " : "diff --combined ",
+                        "", elem->path, c_meta, c_reset);
+       printf("%sindex ", c_meta);
+       for (i = 0; i < num_parent; i++) {
+               abb = find_unique_abbrev(elem->parent[i].sha1,
+                                        abbrev);
+               printf("%s%s", i ? "," : "", abb);
+       }
+       abb = find_unique_abbrev(elem->sha1, abbrev);
+       printf("..%s%s\n", abb, c_reset);
+
+       if (mode_differs) {
+               deleted = !elem->mode;
+
+               /* We say it was added if nobody had it */
+               added = !deleted;
+               for (i = 0; added && i < num_parent; i++)
+                       if (elem->parent[i].status !=
+                           DIFF_STATUS_ADDED)
+                               added = 0;
+               if (added)
+                       printf("%snew file mode %06o",
+                              c_meta, elem->mode);
+               else {
+                       if (deleted)
+                               printf("%sdeleted file ", c_meta);
+                       printf("mode ");
+                       for (i = 0; i < num_parent; i++) {
+                               printf("%s%06o", i ? "," : "",
+                                      elem->parent[i].mode);
+                       }
+                       if (elem->mode)
+                               printf("..%06o", elem->mode);
+               }
+               printf("%s\n", c_reset);
+       }
+
+       if (!show_file_header)
+               return;
+
+       if (added)
+               dump_quoted_path("--- ", "", "/dev/null",
+                                c_meta, c_reset);
+       else
+               dump_quoted_path("--- ", a_prefix, elem->path,
+                                c_meta, c_reset);
+       if (deleted)
+               dump_quoted_path("+++ ", "", "/dev/null",
+                                c_meta, c_reset);
+       else
+               dump_quoted_path("+++ ", b_prefix, elem->path,
+                                c_meta, c_reset);
+}
+
 static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
-                           int dense, struct rev_info *rev)
+                           int dense, int working_tree_file,
+                           struct rev_info *rev)
 {
        struct diff_options *opt = &rev->diffopt;
        unsigned long result_size, cnt, lno;
@@ -691,18 +777,22 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
        struct sline *sline; /* survived lines */
        int mode_differs = 0;
        int i, show_hunks;
-       int working_tree_file = is_null_sha1(elem->sha1);
-       int abbrev = DIFF_OPT_TST(opt, FULL_INDEX) ? 40 : DEFAULT_ABBREV;
-       const char *a_prefix, *b_prefix;
        mmfile_t result_file;
+       struct userdiff_driver *userdiff;
+       struct userdiff_driver *textconv = NULL;
+       int is_binary;
 
        context = opt->context;
-       a_prefix = opt->a_prefix ? opt->a_prefix : "a/";
-       b_prefix = opt->b_prefix ? opt->b_prefix : "b/";
+       userdiff = userdiff_find_by_path(elem->path);
+       if (!userdiff)
+               userdiff = userdiff_find_by_name("default");
+       if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV))
+               textconv = userdiff_get_textconv(userdiff);
 
        /* Read the result of merge first */
        if (!working_tree_file)
-               result = grab_blob(elem->sha1, elem->mode, &result_size);
+               result = grab_blob(elem->sha1, elem->mode, &result_size,
+                                  textconv, elem->path);
        else {
                /* Used by diff-tree to read from the working tree */
                struct stat st;
@@ -725,9 +815,16 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
                } else if (S_ISDIR(st.st_mode)) {
                        unsigned char sha1[20];
                        if (resolve_gitlink_ref(elem->path, "HEAD", sha1) < 0)
-                               result = grab_blob(elem->sha1, elem->mode, &result_size);
+                               result = grab_blob(elem->sha1, elem->mode,
+                                                  &result_size, NULL, NULL);
                        else
-                               result = grab_blob(sha1, elem->mode, &result_size);
+                               result = grab_blob(sha1, elem->mode,
+                                                  &result_size, NULL, NULL);
+               } else if (textconv) {
+                       struct diff_filespec *df = alloc_filespec(elem->path);
+                       fill_filespec(df, null_sha1, st.st_mode);
+                       result_size = fill_textconv(textconv, df, &result);
+                       free_filespec(df);
                } else if (0 <= (fd = open(elem->path, O_RDONLY))) {
                        size_t len = xsize_t(st.st_size);
                        ssize_t done;
@@ -777,6 +874,38 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
                        close(fd);
        }
 
+       for (i = 0; i < num_parent; i++) {
+               if (elem->parent[i].mode != elem->mode) {
+                       mode_differs = 1;
+                       break;
+               }
+       }
+
+       if (textconv)
+               is_binary = 0;
+       else if (userdiff->binary != -1)
+               is_binary = userdiff->binary;
+       else {
+               is_binary = buffer_is_binary(result, result_size);
+               for (i = 0; !is_binary && i < num_parent; i++) {
+                       char *buf;
+                       unsigned long size;
+                       buf = grab_blob(elem->parent[i].sha1,
+                                       elem->parent[i].mode,
+                                       &size, NULL, NULL);
+                       if (buffer_is_binary(buf, size))
+                               is_binary = 1;
+                       free(buf);
+               }
+       }
+       if (is_binary) {
+               show_combined_header(elem, num_parent, dense, rev,
+                                    mode_differs, 0);
+               printf("Binary files differ\n");
+               free(result);
+               return;
+       }
+
        for (cnt = 0, cp = result; cp < result + result_size; cp++) {
                if (*cp == '\n')
                        cnt++;
@@ -824,73 +953,17 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
                        combine_diff(elem->parent[i].sha1,
                                     elem->parent[i].mode,
                                     &result_file, sline,
-                                    cnt, i, num_parent, result_deleted);
-               if (elem->parent[i].mode != elem->mode)
-                       mode_differs = 1;
+                                    cnt, i, num_parent, result_deleted,
+                                    textconv, elem->path);
        }
 
        show_hunks = make_hunks(sline, cnt, num_parent, dense);
 
        if (show_hunks || mode_differs || working_tree_file) {
-               const char *abb;
-               int use_color = DIFF_OPT_TST(opt, COLOR_DIFF);
-               const char *c_meta = diff_get_color(use_color, DIFF_METAINFO);
-               const char *c_reset = diff_get_color(use_color, DIFF_RESET);
-               int added = 0;
-               int deleted = 0;
-
-               if (rev->loginfo && !rev->no_commit_id)
-                       show_log(rev);
-               dump_quoted_path(dense ? "diff --cc " : "diff --combined ",
-                                "", elem->path, c_meta, c_reset);
-               printf("%sindex ", c_meta);
-               for (i = 0; i < num_parent; i++) {
-                       abb = find_unique_abbrev(elem->parent[i].sha1,
-                                                abbrev);
-                       printf("%s%s", i ? "," : "", abb);
-               }
-               abb = find_unique_abbrev(elem->sha1, abbrev);
-               printf("..%s%s\n", abb, c_reset);
-
-               if (mode_differs) {
-                       deleted = !elem->mode;
-
-                       /* We say it was added if nobody had it */
-                       added = !deleted;
-                       for (i = 0; added && i < num_parent; i++)
-                               if (elem->parent[i].status !=
-                                   DIFF_STATUS_ADDED)
-                                       added = 0;
-                       if (added)
-                               printf("%snew file mode %06o",
-                                      c_meta, elem->mode);
-                       else {
-                               if (deleted)
-                                       printf("%sdeleted file ", c_meta);
-                               printf("mode ");
-                               for (i = 0; i < num_parent; i++) {
-                                       printf("%s%06o", i ? "," : "",
-                                              elem->parent[i].mode);
-                               }
-                               if (elem->mode)
-                                       printf("..%06o", elem->mode);
-                       }
-                       printf("%s\n", c_reset);
-               }
-               if (added)
-                       dump_quoted_path("--- ", "", "/dev/null",
-                                        c_meta, c_reset);
-               else
-                       dump_quoted_path("--- ", a_prefix, elem->path,
-                                        c_meta, c_reset);
-               if (deleted)
-                       dump_quoted_path("+++ ", "", "/dev/null",
-                                        c_meta, c_reset);
-               else
-                       dump_quoted_path("+++ ", b_prefix, elem->path,
-                                        c_meta, c_reset);
+               show_combined_header(elem, num_parent, dense, rev,
+                                    mode_differs, 1);
                dump_sline(sline, cnt, num_parent,
-                          DIFF_OPT_TST(opt, COLOR_DIFF), result_deleted);
+                          opt->use_color, result_deleted);
        }
        free(result);
 
@@ -954,6 +1027,12 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct re
        write_name_quoted(p->path, stdout, line_termination);
 }
 
+/*
+ * The result (p->elem) is from the working tree and their
+ * parents are typically from multiple stages during a merge
+ * (i.e. diff-files) or the state in HEAD and in the index
+ * (i.e. diff-index).
+ */
 void show_combined_diff(struct combine_diff_path *p,
                       int num_parent,
                       int dense,
@@ -967,7 +1046,73 @@ void show_combined_diff(struct combine_diff_path *p,
                                  DIFF_FORMAT_NAME_STATUS))
                show_raw_diff(p, num_parent, rev);
        else if (opt->output_format & DIFF_FORMAT_PATCH)
-               show_patch_diff(p, num_parent, dense, rev);
+               show_patch_diff(p, num_parent, dense, 1, rev);
+}
+
+static void free_combined_pair(struct diff_filepair *pair)
+{
+       free(pair->two);
+       free(pair);
+}
+
+/*
+ * A combine_diff_path expresses N parents on the LHS against 1 merge
+ * result. Synthesize a diff_filepair that has N entries on the "one"
+ * side and 1 entry on the "two" side.
+ *
+ * In the future, we might want to add more data to combine_diff_path
+ * so that we can fill fields we are ignoring (most notably, size) here,
+ * but currently nobody uses it, so this should suffice for now.
+ */
+static struct diff_filepair *combined_pair(struct combine_diff_path *p,
+                                          int num_parent)
+{
+       int i;
+       struct diff_filepair *pair;
+       struct diff_filespec *pool;
+
+       pair = xmalloc(sizeof(*pair));
+       pool = xcalloc(num_parent + 1, sizeof(struct diff_filespec));
+       pair->one = pool + 1;
+       pair->two = pool;
+
+       for (i = 0; i < num_parent; i++) {
+               pair->one[i].path = p->path;
+               pair->one[i].mode = p->parent[i].mode;
+               hashcpy(pair->one[i].sha1, p->parent[i].sha1);
+               pair->one[i].sha1_valid = !is_null_sha1(p->parent[i].sha1);
+               pair->one[i].has_more_entries = 1;
+       }
+       pair->one[num_parent - 1].has_more_entries = 0;
+
+       pair->two->path = p->path;
+       pair->two->mode = p->mode;
+       hashcpy(pair->two->sha1, p->sha1);
+       pair->two->sha1_valid = !is_null_sha1(p->sha1);
+       return pair;
+}
+
+static void handle_combined_callback(struct diff_options *opt,
+                                    struct combine_diff_path *paths,
+                                    int num_parent,
+                                    int num_paths)
+{
+       struct combine_diff_path *p;
+       struct diff_queue_struct q;
+       int i;
+
+       q.queue = xcalloc(num_paths, sizeof(struct diff_filepair *));
+       q.alloc = num_paths;
+       q.nr = num_paths;
+       for (i = 0, p = paths; p; p = p->next) {
+               if (!p->len)
+                       continue;
+               q.queue[i++] = combined_pair(p, num_parent);
+       }
+       opt->format_callback(&q, opt, opt->format_callback_data);
+       for (i = 0; i < num_paths; i++)
+               free_combined_pair(q.queue[i]);
+       free(q.queue);
 }
 
 void diff_tree_combined(const unsigned char *sha1,
@@ -1029,13 +1174,16 @@ void diff_tree_combined(const unsigned char *sha1,
                else if (opt->output_format &
                         (DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT))
                        needsep = 1;
+               else if (opt->output_format & DIFF_FORMAT_CALLBACK)
+                       handle_combined_callback(opt, paths, num_parent, num_paths);
+
                if (opt->output_format & DIFF_FORMAT_PATCH) {
                        if (needsep)
                                putchar(opt->line_termination);
                        for (p = paths; p; p = p->next) {
                                if (p->len)
                                        show_patch_diff(p, num_parent, dense,
-                                                       rev);
+                                                       0, rev);
                        }
                }
        }
index ac337c7d7dc1724fa918f9340816d3102edb10bd..9f4cc636dd74e99a1311500e15501bdb4875cb9d 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -39,6 +39,18 @@ struct commit *lookup_commit_reference(const unsigned char *sha1)
        return lookup_commit_reference_gently(sha1, 0);
 }
 
+struct commit *lookup_commit_or_die(const unsigned char *sha1, const char *ref_name)
+{
+       struct commit *c = lookup_commit_reference(sha1);
+       if (!c)
+               die(_("could not parse %s"), ref_name);
+       if (hashcmp(sha1, c->object.sha1)) {
+               warning(_("%s %s is not a commit!"),
+                       ref_name, sha1_to_hex(sha1));
+       }
+       return c;
+}
+
 struct commit *lookup_commit(const unsigned char *sha1)
 {
        struct object *obj = lookup_object(sha1);
@@ -214,22 +226,12 @@ struct commit_graft *lookup_commit_graft(const unsigned char *sha1)
        return commit_graft[pos];
 }
 
-int write_shallow_commits(struct strbuf *out, int use_pack_protocol)
+int for_each_commit_graft(each_commit_graft_fn fn, void *cb_data)
 {
-       int i, count = 0;
-       for (i = 0; i < commit_graft_nr; i++)
-               if (commit_graft[i]->nr_parent < 0) {
-                       const char *hex =
-                               sha1_to_hex(commit_graft[i]->sha1);
-                       count++;
-                       if (use_pack_protocol)
-                               packet_buf_write(out, "shallow %s", hex);
-                       else {
-                               strbuf_addstr(out, hex);
-                               strbuf_addch(out, '\n');
-                       }
-               }
-       return count;
+       int i, ret;
+       for (i = ret = 0; i < commit_graft_nr && !ret; i++)
+               ret = fn(commit_graft[i], cb_data);
+       return ret;
 }
 
 int unregister_shallow(const unsigned char *sha1)
@@ -515,7 +517,7 @@ void sort_in_topological_order(struct commit_list ** list, int lifo)
 
                commit = work_item->item;
                for (parents = commit->parents; parents ; parents = parents->next) {
-                       struct commit *parent=parents->item;
+                       struct commit *parent = parents->item;
 
                        if (!parent->indegree)
                                continue;
index a2d571b97410fa857b4c177325c4556dac50fe3f..14f6a5a2ed91e2176cce4543798774f88a8628b1 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -38,6 +38,13 @@ struct commit *lookup_commit_reference_gently(const unsigned char *sha1,
                                              int quiet);
 struct commit *lookup_commit_reference_by_name(const char *name);
 
+/*
+ * Look up object named by "sha1", dereference tag as necessary,
+ * get a commit and return it. If "sha1" does not dereference to
+ * a commit, use ref_name to report an error and die.
+ */
+struct commit *lookup_commit_or_die(const unsigned char *sha1, const char *ref_name);
+
 int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long size);
 int parse_commit(struct commit *item);
 
@@ -142,6 +149,7 @@ struct commit_graft {
        int nr_parent; /* < 0 if shallow commit */
        unsigned char parent[FLEX_ARRAY][20]; /* more */
 };
+typedef int (*each_commit_graft_fn)(const struct commit_graft *, void *);
 
 struct commit_graft *read_graft_line(char *buf, int len);
 int register_commit_graft(struct commit_graft *, int);
@@ -153,7 +161,7 @@ extern struct commit_list *get_octopus_merge_bases(struct commit_list *in);
 
 extern int register_shallow(const unsigned char *sha1);
 extern int unregister_shallow(const unsigned char *sha1);
-extern int write_shallow_commits(struct strbuf *out, int use_pack_protocol);
+extern int for_each_commit_graft(each_commit_graft_fn, void *);
 extern int is_repository_shallow(void);
 extern struct commit_list *get_shallow_commits(struct object_array *heads,
                int depth, int shallow_flag, int not_shallow_flag);
index b4a51b958c5651d3b509013aaef0c4e30b68a816..dfe9b3084ffbfd6c41a648e32a1857e78b566215 100644 (file)
@@ -101,7 +101,7 @@ static int cygwin_stat(const char *path, struct stat *buf)
  * and calling git_default_config() from here would break such variables.
  */
 static int native_stat = 1;
-static int core_filemode;
+static int core_filemode = 1; /* matches trust_executable_bit default */
 
 static int git_cygwin_config(const char *var, const char *value, void *cb)
 {
@@ -114,8 +114,7 @@ static int git_cygwin_config(const char *var, const char *value, void *cb)
 
 static int init_stat(void)
 {
-       if (have_git_dir()) {
-               git_config(git_cygwin_config, NULL);
+       if (have_git_dir() && git_config(git_cygwin_config,NULL)) {
                if (!core_filemode && native_stat) {
                        cygwin_stat_fn = cygwin_stat;
                        cygwin_lstat_fn = cygwin_lstat;
index f6e9ff7762356e099d2fe20fd31359bc0a1f68a2..6ef0cc4f99becd772a6fed1cfe2484a67b3a9000 100644 (file)
@@ -178,7 +178,7 @@ static int ask_yes_no_if_possible(const char *format, ...)
        vsnprintf(question, sizeof(question), format, args);
        va_end(args);
 
-       if ((retry_hook[0] = getenv("GIT_ASK_YESNO"))) {
+       if ((retry_hook[0] = mingw_getenv("GIT_ASK_YESNO"))) {
                retry_hook[1] = question;
                return !run_command_v_opt(retry_hook, 0);
        }
@@ -599,19 +599,6 @@ char *mingw_getcwd(char *pointer, int len)
        return ret;
 }
 
-#undef getenv
-char *mingw_getenv(const char *name)
-{
-       char *result = getenv(name);
-       if (!result && !strcmp(name, "TMPDIR")) {
-               /* on Windows it is TMP and TEMP */
-               result = getenv("TMP");
-               if (!result)
-                       result = getenv("TEMP");
-       }
-       return result;
-}
-
 /*
  * See http://msdn2.microsoft.com/en-us/library/17w5ykft(vs.71).aspx
  * (Parsing C++ Command-Line Arguments)
@@ -711,7 +698,7 @@ static const char *parse_interpreter(const char *cmd)
  */
 static char **get_path_split(void)
 {
-       char *p, **path, *envpath = getenv("PATH");
+       char *p, **path, *envpath = mingw_getenv("PATH");
        int i, n = 0;
 
        if (!envpath || !*envpath)
@@ -1128,6 +1115,36 @@ char **make_augmented_environ(const char *const *vars)
        return env;
 }
 
+#undef getenv
+
+/*
+ * The system's getenv looks up the name in a case-insensitive manner.
+ * This version tries a case-sensitive lookup and falls back to
+ * case-insensitive if nothing was found.  This is necessary because,
+ * as a prominent example, CMD sets 'Path', but not 'PATH'.
+ * Warning: not thread-safe.
+ */
+static char *getenv_cs(const char *name)
+{
+       size_t len = strlen(name);
+       int i = lookup_env(environ, name, len);
+       if (i >= 0)
+               return environ[i] + len + 1;    /* skip past name and '=' */
+       return getenv(name);
+}
+
+char *mingw_getenv(const char *name)
+{
+       char *result = getenv_cs(name);
+       if (!result && !strcmp(name, "TMPDIR")) {
+               /* on Windows it is TMP and TEMP */
+               result = getenv_cs("TMP");
+               if (!result)
+                       result = getenv_cs("TEMP");
+       }
+       return result;
+}
+
 /*
  * Note, this isn't a complete replacement for getaddrinfo. It assumes
  * that service contains a numerical port, or that it is null. It
index 547568b9181d61d50f06cf5b4b0ab43af78e5aa2..ce9dd980eb211c0301c4008249c61fb0b09f1280 100644 (file)
@@ -300,6 +300,15 @@ int winansi_fprintf(FILE *stream, const char *format, ...) __attribute__((format
 
 #define has_dos_drive_prefix(path) (isalpha(*(path)) && (path)[1] == ':')
 #define is_dir_sep(c) ((c) == '/' || (c) == '\\')
+static inline char *mingw_find_last_dir_sep(const char *path)
+{
+       char *ret = NULL;
+       for (; *path; ++path)
+               if (is_dir_sep(*path))
+                       ret = (char *)path;
+       return ret;
+}
+#define find_last_dir_sep mingw_find_last_dir_sep
 #define PATH_SEP ';'
 #define PRIuMAX "I64u"
 
diff --git a/compat/obstack.c b/compat/obstack.c
new file mode 100644 (file)
index 0000000..a89ab5b
--- /dev/null
@@ -0,0 +1,414 @@
+/* obstack.c - subroutines used implicitly by object stack macros
+   Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1996, 1997, 1998,
+   1999, 2000, 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, write to the Free
+   Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301, USA.  */
+
+#include "git-compat-util.h"
+#include <gettext.h>
+#include "obstack.h"
+
+/* NOTE BEFORE MODIFYING THIS FILE: This version number must be
+   incremented whenever callers compiled using an old obstack.h can no
+   longer properly call the functions in this obstack.c.  */
+#define OBSTACK_INTERFACE_VERSION 1
+
+/* Comment out all this code if we are using the GNU C Library, and are not
+   actually compiling the library itself, and the installed library
+   supports the same library interface we do.  This code is part of the GNU
+   C Library, but also included in many other GNU distributions.  Compiling
+   and linking in this code is a waste when using the GNU C library
+   (especially if it is a shared library).  Rather than having every GNU
+   program understand `configure --with-gnu-libc' and omit the object
+   files, it is simpler to just do this in the source for each such file.  */
+
+#include <stdio.h>             /* Random thing to get __GNU_LIBRARY__.  */
+#if !defined _LIBC && defined __GNU_LIBRARY__ && __GNU_LIBRARY__ > 1
+# include <gnu-versions.h>
+# if _GNU_OBSTACK_INTERFACE_VERSION == OBSTACK_INTERFACE_VERSION
+#  define ELIDE_CODE
+# endif
+#endif
+
+#include <stddef.h>
+
+#ifndef ELIDE_CODE
+
+
+# if HAVE_INTTYPES_H
+#  include <inttypes.h>
+# endif
+# if HAVE_STDINT_H || defined _LIBC
+#  include <stdint.h>
+# endif
+
+/* Determine default alignment.  */
+union fooround
+{
+  uintmax_t i;
+  long double d;
+  void *p;
+};
+struct fooalign
+{
+  char c;
+  union fooround u;
+};
+/* If malloc were really smart, it would round addresses to DEFAULT_ALIGNMENT.
+   But in fact it might be less smart and round addresses to as much as
+   DEFAULT_ROUNDING.  So we prepare for it to do that.  */
+enum
+  {
+    DEFAULT_ALIGNMENT = offsetof (struct fooalign, u),
+    DEFAULT_ROUNDING = sizeof (union fooround)
+  };
+
+/* When we copy a long block of data, this is the unit to do it with.
+   On some machines, copying successive ints does not work;
+   in such a case, redefine COPYING_UNIT to `long' (if that works)
+   or `char' as a last resort.  */
+# ifndef COPYING_UNIT
+#  define COPYING_UNIT int
+# endif
+
+
+/* The functions allocating more room by calling `obstack_chunk_alloc'
+   jump to the handler pointed to by `obstack_alloc_failed_handler'.
+   This can be set to a user defined function which should either
+   abort gracefully or use longjump - but shouldn't return.  This
+   variable by default points to the internal function
+   `print_and_abort'.  */
+static void print_and_abort (void);
+void (*obstack_alloc_failed_handler) (void) = print_and_abort;
+
+# ifdef _LIBC
+#  if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_3_4)
+/* A looong time ago (before 1994, anyway; we're not sure) this global variable
+   was used by non-GNU-C macros to avoid multiple evaluation.  The GNU C
+   library still exports it because somebody might use it.  */
+struct obstack *_obstack_compat;
+compat_symbol (libc, _obstack_compat, _obstack, GLIBC_2_0);
+#  endif
+# endif
+
+/* Define a macro that either calls functions with the traditional malloc/free
+   calling interface, or calls functions with the mmalloc/mfree interface
+   (that adds an extra first argument), based on the state of use_extra_arg.
+   For free, do not use ?:, since some compilers, like the MIPS compilers,
+   do not allow (expr) ? void : void.  */
+
+# define CALL_CHUNKFUN(h, size) \
+  (((h) -> use_extra_arg) \
+   ? (*(h)->chunkfun) ((h)->extra_arg, (size)) \
+   : (*(struct _obstack_chunk *(*) (long)) (h)->chunkfun) ((size)))
+
+# define CALL_FREEFUN(h, old_chunk) \
+  do { \
+    if ((h) -> use_extra_arg) \
+      (*(h)->freefun) ((h)->extra_arg, (old_chunk)); \
+    else \
+      (*(void (*) (void *)) (h)->freefun) ((old_chunk)); \
+  } while (0)
+
+\f
+/* Initialize an obstack H for use.  Specify chunk size SIZE (0 means default).
+   Objects start on multiples of ALIGNMENT (0 means use default).
+   CHUNKFUN is the function to use to allocate chunks,
+   and FREEFUN the function to free them.
+
+   Return nonzero if successful, calls obstack_alloc_failed_handler if
+   allocation fails.  */
+
+int
+_obstack_begin (struct obstack *h,
+               int size, int alignment,
+               void *(*chunkfun) (long),
+               void (*freefun) (void *))
+{
+  register struct _obstack_chunk *chunk; /* points to new chunk */
+
+  if (alignment == 0)
+    alignment = DEFAULT_ALIGNMENT;
+  if (size == 0)
+    /* Default size is what GNU malloc can fit in a 4096-byte block.  */
+    {
+      /* 12 is sizeof (mhead) and 4 is EXTRA from GNU malloc.
+        Use the values for range checking, because if range checking is off,
+        the extra bytes won't be missed terribly, but if range checking is on
+        and we used a larger request, a whole extra 4096 bytes would be
+        allocated.
+
+        These number are irrelevant to the new GNU malloc.  I suspect it is
+        less sensitive to the size of the request.  */
+      int extra = ((((12 + DEFAULT_ROUNDING - 1) & ~(DEFAULT_ROUNDING - 1))
+                   + 4 + DEFAULT_ROUNDING - 1)
+                  & ~(DEFAULT_ROUNDING - 1));
+      size = 4096 - extra;
+    }
+
+  h->chunkfun = (struct _obstack_chunk * (*)(void *, long)) chunkfun;
+  h->freefun = (void (*) (void *, struct _obstack_chunk *)) freefun;
+  h->chunk_size = size;
+  h->alignment_mask = alignment - 1;
+  h->use_extra_arg = 0;
+
+  chunk = h->chunk = CALL_CHUNKFUN (h, h -> chunk_size);
+  if (!chunk)
+    (*obstack_alloc_failed_handler) ();
+  h->next_free = h->object_base = __PTR_ALIGN ((char *) chunk, chunk->contents,
+                                              alignment - 1);
+  h->chunk_limit = chunk->limit
+    = (char *) chunk + h->chunk_size;
+  chunk->prev = 0;
+  /* The initial chunk now contains no empty object.  */
+  h->maybe_empty_object = 0;
+  h->alloc_failed = 0;
+  return 1;
+}
+
+int
+_obstack_begin_1 (struct obstack *h, int size, int alignment,
+                 void *(*chunkfun) (void *, long),
+                 void (*freefun) (void *, void *),
+                 void *arg)
+{
+  register struct _obstack_chunk *chunk; /* points to new chunk */
+
+  if (alignment == 0)
+    alignment = DEFAULT_ALIGNMENT;
+  if (size == 0)
+    /* Default size is what GNU malloc can fit in a 4096-byte block.  */
+    {
+      /* 12 is sizeof (mhead) and 4 is EXTRA from GNU malloc.
+        Use the values for range checking, because if range checking is off,
+        the extra bytes won't be missed terribly, but if range checking is on
+        and we used a larger request, a whole extra 4096 bytes would be
+        allocated.
+
+        These number are irrelevant to the new GNU malloc.  I suspect it is
+        less sensitive to the size of the request.  */
+      int extra = ((((12 + DEFAULT_ROUNDING - 1) & ~(DEFAULT_ROUNDING - 1))
+                   + 4 + DEFAULT_ROUNDING - 1)
+                  & ~(DEFAULT_ROUNDING - 1));
+      size = 4096 - extra;
+    }
+
+  h->chunkfun = (struct _obstack_chunk * (*)(void *,long)) chunkfun;
+  h->freefun = (void (*) (void *, struct _obstack_chunk *)) freefun;
+  h->chunk_size = size;
+  h->alignment_mask = alignment - 1;
+  h->extra_arg = arg;
+  h->use_extra_arg = 1;
+
+  chunk = h->chunk = CALL_CHUNKFUN (h, h -> chunk_size);
+  if (!chunk)
+    (*obstack_alloc_failed_handler) ();
+  h->next_free = h->object_base = __PTR_ALIGN ((char *) chunk, chunk->contents,
+                                              alignment - 1);
+  h->chunk_limit = chunk->limit
+    = (char *) chunk + h->chunk_size;
+  chunk->prev = 0;
+  /* The initial chunk now contains no empty object.  */
+  h->maybe_empty_object = 0;
+  h->alloc_failed = 0;
+  return 1;
+}
+
+/* Allocate a new current chunk for the obstack *H
+   on the assumption that LENGTH bytes need to be added
+   to the current object, or a new object of length LENGTH allocated.
+   Copies any partial object from the end of the old chunk
+   to the beginning of the new one.  */
+
+void
+_obstack_newchunk (struct obstack *h, int length)
+{
+  register struct _obstack_chunk *old_chunk = h->chunk;
+  register struct _obstack_chunk *new_chunk;
+  register long        new_size;
+  register long obj_size = h->next_free - h->object_base;
+  register long i;
+  long already;
+  char *object_base;
+
+  /* Compute size for new chunk.  */
+  new_size = (obj_size + length) + (obj_size >> 3) + h->alignment_mask + 100;
+  if (new_size < h->chunk_size)
+    new_size = h->chunk_size;
+
+  /* Allocate and initialize the new chunk.  */
+  new_chunk = CALL_CHUNKFUN (h, new_size);
+  if (!new_chunk)
+    (*obstack_alloc_failed_handler) ();
+  h->chunk = new_chunk;
+  new_chunk->prev = old_chunk;
+  new_chunk->limit = h->chunk_limit = (char *) new_chunk + new_size;
+
+  /* Compute an aligned object_base in the new chunk */
+  object_base =
+    __PTR_ALIGN ((char *) new_chunk, new_chunk->contents, h->alignment_mask);
+
+  /* Move the existing object to the new chunk.
+     Word at a time is fast and is safe if the object
+     is sufficiently aligned.  */
+  if (h->alignment_mask + 1 >= DEFAULT_ALIGNMENT)
+    {
+      for (i = obj_size / sizeof (COPYING_UNIT) - 1;
+          i >= 0; i--)
+       ((COPYING_UNIT *)object_base)[i]
+         = ((COPYING_UNIT *)h->object_base)[i];
+      /* We used to copy the odd few remaining bytes as one extra COPYING_UNIT,
+        but that can cross a page boundary on a machine
+        which does not do strict alignment for COPYING_UNITS.  */
+      already = obj_size / sizeof (COPYING_UNIT) * sizeof (COPYING_UNIT);
+    }
+  else
+    already = 0;
+  /* Copy remaining bytes one by one.  */
+  for (i = already; i < obj_size; i++)
+    object_base[i] = h->object_base[i];
+
+  /* If the object just copied was the only data in OLD_CHUNK,
+     free that chunk and remove it from the chain.
+     But not if that chunk might contain an empty object.  */
+  if (! h->maybe_empty_object
+      && (h->object_base
+         == __PTR_ALIGN ((char *) old_chunk, old_chunk->contents,
+                         h->alignment_mask)))
+    {
+      new_chunk->prev = old_chunk->prev;
+      CALL_FREEFUN (h, old_chunk);
+    }
+
+  h->object_base = object_base;
+  h->next_free = h->object_base + obj_size;
+  /* The new chunk certainly contains no empty object yet.  */
+  h->maybe_empty_object = 0;
+}
+# ifdef _LIBC
+libc_hidden_def (_obstack_newchunk)
+# endif
+
+/* Return nonzero if object OBJ has been allocated from obstack H.
+   This is here for debugging.
+   If you use it in a program, you are probably losing.  */
+
+/* Suppress -Wmissing-prototypes warning.  We don't want to declare this in
+   obstack.h because it is just for debugging.  */
+int _obstack_allocated_p (struct obstack *h, void *obj);
+
+int
+_obstack_allocated_p (struct obstack *h, void *obj)
+{
+  register struct _obstack_chunk *lp;  /* below addr of any objects in this chunk */
+  register struct _obstack_chunk *plp; /* point to previous chunk if any */
+
+  lp = (h)->chunk;
+  /* We use >= rather than > since the object cannot be exactly at
+     the beginning of the chunk but might be an empty object exactly
+     at the end of an adjacent chunk.  */
+  while (lp != 0 && ((void *) lp >= obj || (void *) (lp)->limit < obj))
+    {
+      plp = lp->prev;
+      lp = plp;
+    }
+  return lp != 0;
+}
+\f
+/* Free objects in obstack H, including OBJ and everything allocate
+   more recently than OBJ.  If OBJ is zero, free everything in H.  */
+
+# undef obstack_free
+
+void
+obstack_free (struct obstack *h, void *obj)
+{
+  register struct _obstack_chunk *lp;  /* below addr of any objects in this chunk */
+  register struct _obstack_chunk *plp; /* point to previous chunk if any */
+
+  lp = h->chunk;
+  /* We use >= because there cannot be an object at the beginning of a chunk.
+     But there can be an empty object at that address
+     at the end of another chunk.  */
+  while (lp != 0 && ((void *) lp >= obj || (void *) (lp)->limit < obj))
+    {
+      plp = lp->prev;
+      CALL_FREEFUN (h, lp);
+      lp = plp;
+      /* If we switch chunks, we can't tell whether the new current
+        chunk contains an empty object, so assume that it may.  */
+      h->maybe_empty_object = 1;
+    }
+  if (lp)
+    {
+      h->object_base = h->next_free = (char *) (obj);
+      h->chunk_limit = lp->limit;
+      h->chunk = lp;
+    }
+  else if (obj != 0)
+    /* obj is not in any of the chunks! */
+    abort ();
+}
+
+# ifdef _LIBC
+/* Older versions of libc used a function _obstack_free intended to be
+   called by non-GCC compilers.  */
+strong_alias (obstack_free, _obstack_free)
+# endif
+\f
+int
+_obstack_memory_used (struct obstack *h)
+{
+  register struct _obstack_chunk* lp;
+  register int nbytes = 0;
+
+  for (lp = h->chunk; lp != 0; lp = lp->prev)
+    {
+      nbytes += lp->limit - (char *) lp;
+    }
+  return nbytes;
+}
+\f
+# ifdef _LIBC
+#  include <libio/iolibio.h>
+# endif
+
+# ifndef __attribute__
+/* This feature is available in gcc versions 2.5 and later.  */
+#  if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5)
+#   define __attribute__(Spec) /* empty */
+#  endif
+# endif
+
+static void
+__attribute__ ((noreturn))
+print_and_abort (void)
+{
+  /* Don't change any of these strings.  Yes, it would be possible to add
+     the newline to the string and use fputs or so.  But this must not
+     happen because the "memory exhausted" message appears in other places
+     like this and the translation should be reused instead of creating
+     a very similar string which requires a separate translation.  */
+# ifdef _LIBC
+  (void) __fxprintf (NULL, "%s\n", _("memory exhausted"));
+# else
+  fprintf (stderr, "%s\n", _("memory exhausted"));
+# endif
+  exit (1);
+}
+
+#endif /* !ELIDE_CODE */
diff --git a/compat/obstack.h b/compat/obstack.h
new file mode 100644 (file)
index 0000000..d178bd6
--- /dev/null
@@ -0,0 +1,506 @@
+/* obstack.h - object stack macros
+   Copyright (C) 1988-1994,1996-1999,2003,2004,2005,2009
+       Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, write to the Free
+   Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301, USA.  */
+
+/* Summary:
+
+All the apparent functions defined here are macros. The idea
+is that you would use these pre-tested macros to solve a
+very specific set of problems, and they would run fast.
+Caution: no side-effects in arguments please!! They may be
+evaluated MANY times!!
+
+These macros operate a stack of objects.  Each object starts life
+small, and may grow to maturity.  (Consider building a word syllable
+by syllable.)  An object can move while it is growing.  Once it has
+been "finished" it never changes address again.  So the "top of the
+stack" is typically an immature growing object, while the rest of the
+stack is of mature, fixed size and fixed address objects.
+
+These routines grab large chunks of memory, using a function you
+supply, called `obstack_chunk_alloc'.  On occasion, they free chunks,
+by calling `obstack_chunk_free'.  You must define them and declare
+them before using any obstack macros.
+
+Each independent stack is represented by a `struct obstack'.
+Each of the obstack macros expects a pointer to such a structure
+as the first argument.
+
+One motivation for this package is the problem of growing char strings
+in symbol tables.  Unless you are "fascist pig with a read-only mind"
+--Gosper's immortal quote from HAKMEM item 154, out of context--you
+would not like to put any arbitrary upper limit on the length of your
+symbols.
+
+In practice this often means you will build many short symbols and a
+few long symbols.  At the time you are reading a symbol you don't know
+how long it is.  One traditional method is to read a symbol into a
+buffer, realloc()ating the buffer every time you try to read a symbol
+that is longer than the buffer.  This is beaut, but you still will
+want to copy the symbol from the buffer to a more permanent
+symbol-table entry say about half the time.
+
+With obstacks, you can work differently.  Use one obstack for all symbol
+names.  As you read a symbol, grow the name in the obstack gradually.
+When the name is complete, finalize it.  Then, if the symbol exists already,
+free the newly read name.
+
+The way we do this is to take a large chunk, allocating memory from
+low addresses.  When you want to build a symbol in the chunk you just
+add chars above the current "high water mark" in the chunk.  When you
+have finished adding chars, because you got to the end of the symbol,
+you know how long the chars are, and you can create a new object.
+Mostly the chars will not burst over the highest address of the chunk,
+because you would typically expect a chunk to be (say) 100 times as
+long as an average object.
+
+In case that isn't clear, when we have enough chars to make up
+the object, THEY ARE ALREADY CONTIGUOUS IN THE CHUNK (guaranteed)
+so we just point to it where it lies.  No moving of chars is
+needed and this is the second win: potentially long strings need
+never be explicitly shuffled. Once an object is formed, it does not
+change its address during its lifetime.
+
+When the chars burst over a chunk boundary, we allocate a larger
+chunk, and then copy the partly formed object from the end of the old
+chunk to the beginning of the new larger chunk.  We then carry on
+accreting characters to the end of the object as we normally would.
+
+A special macro is provided to add a single char at a time to a
+growing object.  This allows the use of register variables, which
+break the ordinary 'growth' macro.
+
+Summary:
+       We allocate large chunks.
+       We carve out one object at a time from the current chunk.
+       Once carved, an object never moves.
+       We are free to append data of any size to the currently
+         growing object.
+       Exactly one object is growing in an obstack at any one time.
+       You can run one obstack per control block.
+       You may have as many control blocks as you dare.
+       Because of the way we do it, you can `unwind' an obstack
+         back to a previous state. (You may remove objects much
+         as you would with a stack.)
+*/
+
+
+/* Don't do the contents of this file more than once.  */
+
+#ifndef _OBSTACK_H
+#define _OBSTACK_H 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+\f
+/* We need the type of a pointer subtraction.  If __PTRDIFF_TYPE__ is
+   defined, as with GNU C, use that; that way we don't pollute the
+   namespace with <stddef.h>'s symbols.  Otherwise, include <stddef.h>
+   and use ptrdiff_t.  */
+
+#ifdef __PTRDIFF_TYPE__
+# define PTR_INT_TYPE __PTRDIFF_TYPE__
+#else
+# include <stddef.h>
+# define PTR_INT_TYPE ptrdiff_t
+#endif
+
+/* If B is the base of an object addressed by P, return the result of
+   aligning P to the next multiple of A + 1.  B and P must be of type
+   char *.  A + 1 must be a power of 2.  */
+
+#define __BPTR_ALIGN(B, P, A) ((B) + (((P) - (B) + (A)) & ~(A)))
+
+/* Similiar to _BPTR_ALIGN (B, P, A), except optimize the common case
+   where pointers can be converted to integers, aligned as integers,
+   and converted back again.  If PTR_INT_TYPE is narrower than a
+   pointer (e.g., the AS/400), play it safe and compute the alignment
+   relative to B.  Otherwise, use the faster strategy of computing the
+   alignment relative to 0.  */
+
+#define __PTR_ALIGN(B, P, A)                                               \
+  __BPTR_ALIGN (sizeof (PTR_INT_TYPE) < sizeof (void *) ? (B) : (char *) 0, \
+               P, A)
+
+#include <string.h>
+
+struct _obstack_chunk          /* Lives at front of each chunk. */
+{
+  char  *limit;                        /* 1 past end of this chunk */
+  struct _obstack_chunk *prev; /* address of prior chunk or NULL */
+  char contents[4];            /* objects begin here */
+};
+
+struct obstack         /* control current object in current chunk */
+{
+  long chunk_size;             /* preferred size to allocate chunks in */
+  struct _obstack_chunk *chunk;        /* address of current struct obstack_chunk */
+  char *object_base;           /* address of object we are building */
+  char *next_free;             /* where to add next char to current object */
+  char *chunk_limit;           /* address of char after current chunk */
+  union
+  {
+    PTR_INT_TYPE tempint;
+    void *tempptr;
+  } temp;                      /* Temporary for some macros.  */
+  int   alignment_mask;                /* Mask of alignment for each object. */
+  /* These prototypes vary based on `use_extra_arg', and we use
+     casts to the prototypeless function type in all assignments,
+     but having prototypes here quiets -Wstrict-prototypes.  */
+  struct _obstack_chunk *(*chunkfun) (void *, long);
+  void (*freefun) (void *, struct _obstack_chunk *);
+  void *extra_arg;             /* first arg for chunk alloc/dealloc funcs */
+  unsigned use_extra_arg:1;    /* chunk alloc/dealloc funcs take extra arg */
+  unsigned maybe_empty_object:1;/* There is a possibility that the current
+                                  chunk contains a zero-length object.  This
+                                  prevents freeing the chunk if we allocate
+                                  a bigger chunk to replace it. */
+  unsigned alloc_failed:1;     /* No longer used, as we now call the failed
+                                  handler on error, but retained for binary
+                                  compatibility.  */
+};
+
+/* Declare the external functions we use; they are in obstack.c.  */
+
+extern void _obstack_newchunk (struct obstack *, int);
+extern int _obstack_begin (struct obstack *, int, int,
+                           void *(*) (long), void (*) (void *));
+extern int _obstack_begin_1 (struct obstack *, int, int,
+                            void *(*) (void *, long),
+                            void (*) (void *, void *), void *);
+extern int _obstack_memory_used (struct obstack *);
+
+void obstack_free (struct obstack *, void *);
+
+\f
+/* Error handler called when `obstack_chunk_alloc' failed to allocate
+   more memory.  This can be set to a user defined function which
+   should either abort gracefully or use longjump - but shouldn't
+   return.  The default action is to print a message and abort.  */
+extern void (*obstack_alloc_failed_handler) (void);
+\f
+/* Pointer to beginning of object being allocated or to be allocated next.
+   Note that this might not be the final address of the object
+   because a new chunk might be needed to hold the final size.  */
+
+#define obstack_base(h) ((void *) (h)->object_base)
+
+/* Size for allocating ordinary chunks.  */
+
+#define obstack_chunk_size(h) ((h)->chunk_size)
+
+/* Pointer to next byte not yet allocated in current chunk.  */
+
+#define obstack_next_free(h)   ((h)->next_free)
+
+/* Mask specifying low bits that should be clear in address of an object.  */
+
+#define obstack_alignment_mask(h) ((h)->alignment_mask)
+
+/* To prevent prototype warnings provide complete argument list.  */
+#define obstack_init(h)                                                \
+  _obstack_begin ((h), 0, 0,                                   \
+                 (void *(*) (long)) obstack_chunk_alloc,       \
+                 (void (*) (void *)) obstack_chunk_free)
+
+#define obstack_begin(h, size)                                 \
+  _obstack_begin ((h), (size), 0,                              \
+                 (void *(*) (long)) obstack_chunk_alloc,       \
+                 (void (*) (void *)) obstack_chunk_free)
+
+#define obstack_specify_allocation(h, size, alignment, chunkfun, freefun)  \
+  _obstack_begin ((h), (size), (alignment),                               \
+                 (void *(*) (long)) (chunkfun),                           \
+                 (void (*) (void *)) (freefun))
+
+#define obstack_specify_allocation_with_arg(h, size, alignment, chunkfun, freefun, arg) \
+  _obstack_begin_1 ((h), (size), (alignment),                          \
+                   (void *(*) (void *, long)) (chunkfun),              \
+                   (void (*) (void *, void *)) (freefun), (arg))
+
+#define obstack_chunkfun(h, newchunkfun) \
+  ((h) -> chunkfun = (struct _obstack_chunk *(*)(void *, long)) (newchunkfun))
+
+#define obstack_freefun(h, newfreefun) \
+  ((h) -> freefun = (void (*)(void *, struct _obstack_chunk *)) (newfreefun))
+
+#define obstack_1grow_fast(h,achar) (*((h)->next_free)++ = (achar))
+
+#define obstack_blank_fast(h,n) ((h)->next_free += (n))
+
+#define obstack_memory_used(h) _obstack_memory_used (h)
+\f
+#if defined __GNUC__ && defined __STDC__ && __STDC__
+/* NextStep 2.0 cc is really gcc 1.93 but it defines __GNUC__ = 2 and
+   does not implement __extension__.  But that compiler doesn't define
+   __GNUC_MINOR__.  */
+# if __GNUC__ < 2 || (__NeXT__ && !__GNUC_MINOR__)
+#  define __extension__
+# endif
+
+/* For GNU C, if not -traditional,
+   we can define these macros to compute all args only once
+   without using a global variable.
+   Also, we can avoid using the `temp' slot, to make faster code.  */
+
+# define obstack_object_size(OBSTACK)                                  \
+  __extension__                                                                \
+  ({ struct obstack const *__o = (OBSTACK);                            \
+     (unsigned) (__o->next_free - __o->object_base); })
+
+# define obstack_room(OBSTACK)                                         \
+  __extension__                                                                \
+  ({ struct obstack const *__o = (OBSTACK);                            \
+     (unsigned) (__o->chunk_limit - __o->next_free); })
+
+# define obstack_make_room(OBSTACK,length)                             \
+__extension__                                                          \
+({ struct obstack *__o = (OBSTACK);                                    \
+   int __len = (length);                                               \
+   if (__o->chunk_limit - __o->next_free < __len)                      \
+     _obstack_newchunk (__o, __len);                                   \
+   (void) 0; })
+
+# define obstack_empty_p(OBSTACK)                                      \
+  __extension__                                                                \
+  ({ struct obstack const *__o = (OBSTACK);                            \
+     (__o->chunk->prev == 0                                            \
+      && __o->next_free == __PTR_ALIGN ((char *) __o->chunk,           \
+                                       __o->chunk->contents,           \
+                                       __o->alignment_mask)); })
+
+# define obstack_grow(OBSTACK,where,length)                            \
+__extension__                                                          \
+({ struct obstack *__o = (OBSTACK);                                    \
+   int __len = (length);                                               \
+   if (__o->next_free + __len > __o->chunk_limit)                      \
+     _obstack_newchunk (__o, __len);                                   \
+   memcpy (__o->next_free, where, __len);                              \
+   __o->next_free += __len;                                            \
+   (void) 0; })
+
+# define obstack_grow0(OBSTACK,where,length)                           \
+__extension__                                                          \
+({ struct obstack *__o = (OBSTACK);                                    \
+   int __len = (length);                                               \
+   if (__o->next_free + __len + 1 > __o->chunk_limit)                  \
+     _obstack_newchunk (__o, __len + 1);                               \
+   memcpy (__o->next_free, where, __len);                              \
+   __o->next_free += __len;                                            \
+   *(__o->next_free)++ = 0;                                            \
+   (void) 0; })
+
+# define obstack_1grow(OBSTACK,datum)                                  \
+__extension__                                                          \
+({ struct obstack *__o = (OBSTACK);                                    \
+   if (__o->next_free + 1 > __o->chunk_limit)                          \
+     _obstack_newchunk (__o, 1);                                       \
+   obstack_1grow_fast (__o, datum);                                    \
+   (void) 0; })
+
+/* These assume that the obstack alignment is good enough for pointers
+   or ints, and that the data added so far to the current object
+   shares that much alignment.  */
+
+# define obstack_ptr_grow(OBSTACK,datum)                               \
+__extension__                                                          \
+({ struct obstack *__o = (OBSTACK);                                    \
+   if (__o->next_free + sizeof (void *) > __o->chunk_limit)            \
+     _obstack_newchunk (__o, sizeof (void *));                         \
+   obstack_ptr_grow_fast (__o, datum); })                              \
+
+# define obstack_int_grow(OBSTACK,datum)                               \
+__extension__                                                          \
+({ struct obstack *__o = (OBSTACK);                                    \
+   if (__o->next_free + sizeof (int) > __o->chunk_limit)               \
+     _obstack_newchunk (__o, sizeof (int));                            \
+   obstack_int_grow_fast (__o, datum); })
+
+# define obstack_ptr_grow_fast(OBSTACK,aptr)                           \
+__extension__                                                          \
+({ struct obstack *__o1 = (OBSTACK);                                   \
+   *(const void **) __o1->next_free = (aptr);                          \
+   __o1->next_free += sizeof (const void *);                           \
+   (void) 0; })
+
+# define obstack_int_grow_fast(OBSTACK,aint)                           \
+__extension__                                                          \
+({ struct obstack *__o1 = (OBSTACK);                                   \
+   *(int *) __o1->next_free = (aint);                                  \
+   __o1->next_free += sizeof (int);                                    \
+   (void) 0; })
+
+# define obstack_blank(OBSTACK,length)                                 \
+__extension__                                                          \
+({ struct obstack *__o = (OBSTACK);                                    \
+   int __len = (length);                                               \
+   if (__o->chunk_limit - __o->next_free < __len)                      \
+     _obstack_newchunk (__o, __len);                                   \
+   obstack_blank_fast (__o, __len);                                    \
+   (void) 0; })
+
+# define obstack_alloc(OBSTACK,length)                                 \
+__extension__                                                          \
+({ struct obstack *__h = (OBSTACK);                                    \
+   obstack_blank (__h, (length));                                      \
+   obstack_finish (__h); })
+
+# define obstack_copy(OBSTACK,where,length)                            \
+__extension__                                                          \
+({ struct obstack *__h = (OBSTACK);                                    \
+   obstack_grow (__h, (where), (length));                              \
+   obstack_finish (__h); })
+
+# define obstack_copy0(OBSTACK,where,length)                           \
+__extension__                                                          \
+({ struct obstack *__h = (OBSTACK);                                    \
+   obstack_grow0 (__h, (where), (length));                             \
+   obstack_finish (__h); })
+
+/* The local variable is named __o1 to avoid a name conflict
+   when obstack_blank is called.  */
+# define obstack_finish(OBSTACK)                                       \
+__extension__                                                          \
+({ struct obstack *__o1 = (OBSTACK);                                   \
+   void *__value = (void *) __o1->object_base;                         \
+   if (__o1->next_free == __value)                                     \
+     __o1->maybe_empty_object = 1;                                     \
+   __o1->next_free                                                     \
+     = __PTR_ALIGN (__o1->object_base, __o1->next_free,                        \
+                   __o1->alignment_mask);                              \
+   if (__o1->next_free - (char *)__o1->chunk                           \
+       > __o1->chunk_limit - (char *)__o1->chunk)                      \
+     __o1->next_free = __o1->chunk_limit;                              \
+   __o1->object_base = __o1->next_free;                                        \
+   __value; })
+
+# define obstack_free(OBSTACK, OBJ)                                    \
+__extension__                                                          \
+({ struct obstack *__o = (OBSTACK);                                    \
+   void *__obj = (OBJ);                                                        \
+   if (__obj > (void *)__o->chunk && __obj < (void *)__o->chunk_limit)  \
+     __o->next_free = __o->object_base = (char *)__obj;                        \
+   else (obstack_free) (__o, __obj); })
+\f
+#else /* not __GNUC__ or not __STDC__ */
+
+# define obstack_object_size(h) \
+ (unsigned) ((h)->next_free - (h)->object_base)
+
+# define obstack_room(h)               \
+ (unsigned) ((h)->chunk_limit - (h)->next_free)
+
+# define obstack_empty_p(h) \
+ ((h)->chunk->prev == 0                                                        \
+  && (h)->next_free == __PTR_ALIGN ((char *) (h)->chunk,               \
+                                   (h)->chunk->contents,               \
+                                   (h)->alignment_mask))
+
+/* Note that the call to _obstack_newchunk is enclosed in (..., 0)
+   so that we can avoid having void expressions
+   in the arms of the conditional expression.
+   Casting the third operand to void was tried before,
+   but some compilers won't accept it.  */
+
+# define obstack_make_room(h,length)                                   \
+( (h)->temp.tempint = (length),                                                \
+  (((h)->next_free + (h)->temp.tempint > (h)->chunk_limit)             \
+   ? (_obstack_newchunk ((h), (h)->temp.tempint), 0) : 0))
+
+# define obstack_grow(h,where,length)                                  \
+( (h)->temp.tempint = (length),                                                \
+  (((h)->next_free + (h)->temp.tempint > (h)->chunk_limit)             \
+   ? (_obstack_newchunk ((h), (h)->temp.tempint), 0) : 0),             \
+  memcpy ((h)->next_free, where, (h)->temp.tempint),                   \
+  (h)->next_free += (h)->temp.tempint)
+
+# define obstack_grow0(h,where,length)                                 \
+( (h)->temp.tempint = (length),                                                \
+  (((h)->next_free + (h)->temp.tempint + 1 > (h)->chunk_limit)         \
+   ? (_obstack_newchunk ((h), (h)->temp.tempint + 1), 0) : 0),         \
+  memcpy ((h)->next_free, where, (h)->temp.tempint),                   \
+  (h)->next_free += (h)->temp.tempint,                                 \
+  *((h)->next_free)++ = 0)
+
+# define obstack_1grow(h,datum)                                                \
+( (((h)->next_free + 1 > (h)->chunk_limit)                             \
+   ? (_obstack_newchunk ((h), 1), 0) : 0),                             \
+  obstack_1grow_fast (h, datum))
+
+# define obstack_ptr_grow(h,datum)                                     \
+( (((h)->next_free + sizeof (char *) > (h)->chunk_limit)               \
+   ? (_obstack_newchunk ((h), sizeof (char *)), 0) : 0),               \
+  obstack_ptr_grow_fast (h, datum))
+
+# define obstack_int_grow(h,datum)                                     \
+( (((h)->next_free + sizeof (int) > (h)->chunk_limit)                  \
+   ? (_obstack_newchunk ((h), sizeof (int)), 0) : 0),                  \
+  obstack_int_grow_fast (h, datum))
+
+# define obstack_ptr_grow_fast(h,aptr)                                 \
+  (((const void **) ((h)->next_free += sizeof (void *)))[-1] = (aptr))
+
+# define obstack_int_grow_fast(h,aint)                                 \
+  (((int *) ((h)->next_free += sizeof (int)))[-1] = (aint))
+
+# define obstack_blank(h,length)                                       \
+( (h)->temp.tempint = (length),                                                \
+  (((h)->chunk_limit - (h)->next_free < (h)->temp.tempint)             \
+   ? (_obstack_newchunk ((h), (h)->temp.tempint), 0) : 0),             \
+  obstack_blank_fast (h, (h)->temp.tempint))
+
+# define obstack_alloc(h,length)                                       \
+ (obstack_blank ((h), (length)), obstack_finish ((h)))
+
+# define obstack_copy(h,where,length)                                  \
+ (obstack_grow ((h), (where), (length)), obstack_finish ((h)))
+
+# define obstack_copy0(h,where,length)                                 \
+ (obstack_grow0 ((h), (where), (length)), obstack_finish ((h)))
+
+# define obstack_finish(h)                                             \
+( ((h)->next_free == (h)->object_base                                  \
+   ? (((h)->maybe_empty_object = 1), 0)                                        \
+   : 0),                                                               \
+  (h)->temp.tempptr = (h)->object_base,                                        \
+  (h)->next_free                                                       \
+    = __PTR_ALIGN ((h)->object_base, (h)->next_free,                   \
+                  (h)->alignment_mask),                                \
+  (((h)->next_free - (char *) (h)->chunk                               \
+    > (h)->chunk_limit - (char *) (h)->chunk)                          \
+   ? ((h)->next_free = (h)->chunk_limit) : 0),                         \
+  (h)->object_base = (h)->next_free,                                   \
+  (h)->temp.tempptr)
+
+# define obstack_free(h,obj)                                           \
+( (h)->temp.tempint = (char *) (obj) - (char *) (h)->chunk,            \
+  ((((h)->temp.tempint > 0                                             \
+    && (h)->temp.tempint < (h)->chunk_limit - (char *) (h)->chunk))    \
+   ? (int) ((h)->next_free = (h)->object_base                          \
+           = (h)->temp.tempint + (char *) (h)->chunk)                  \
+   : (((obstack_free) ((h), (h)->temp.tempint + (char *) (h)->chunk), 0), 0)))
+
+#endif /* not __GNUC__ or not __STDC__ */
+
+#ifdef __cplusplus
+}      /* C++ */
+#endif
+
+#endif /* obstack.h */
index e0b3b80d92ece9f37e0e76292bd61ce97271fcce..4183f80262ea9f24e286295a2295f93459548b78 100644 (file)
--- a/config.c
+++ b/config.c
 
 #define MAXNAME (256)
 
-static FILE *config_file;
-static const char *config_file_name;
-static int config_linenr;
-static int config_file_eof;
+typedef struct config_file {
+       struct config_file *prev;
+       FILE *f;
+       const char *name;
+       int linenr;
+       int eof;
+       struct strbuf value;
+       char var[MAXNAME];
+} config_file;
+
+static config_file *cf;
+
 static int zlib_compression_seen;
 
 const char *config_exclusive_filename = NULL;
@@ -39,13 +47,13 @@ void git_config_push_parameter(const char *text)
        strbuf_release(&env);
 }
 
-static int git_config_parse_parameter(const char *text,
-                                     config_fn_t fn, void *data)
+int git_config_parse_parameter(const char *text,
+                              config_fn_t fn, void *data)
 {
-       struct strbuf tmp = STRBUF_INIT;
        struct strbuf **pair;
-       strbuf_addstr(&tmp, text);
-       pair = strbuf_split(&tmp, '=');
+       pair = strbuf_split_str(text, '=', 2);
+       if (!pair[0])
+               return error("bogus config parameter: %s", text);
        if (pair[0]->len && pair[0]->buf[pair[0]->len - 1] == '=')
                strbuf_setlen(pair[0], pair[0]->len - 1);
        strbuf_trim(pair[0]);
@@ -99,7 +107,7 @@ static int get_next_char(void)
        FILE *f;
 
        c = '\n';
-       if ((f = config_file) != NULL) {
+       if (cf && ((f = cf->f) != NULL)) {
                c = fgetc(f);
                if (c == '\r') {
                        /* DOS like systems */
@@ -110,9 +118,9 @@ static int get_next_char(void)
                        }
                }
                if (c == '\n')
-                       config_linenr++;
+                       cf->linenr++;
                if (c == EOF) {
-                       config_file_eof = 1;
+                       cf->eof = 1;
                        c = '\n';
                }
        }
@@ -121,21 +129,20 @@ static int get_next_char(void)
 
 static char *parse_value(void)
 {
-       static struct strbuf value = STRBUF_INIT;
        int quote = 0, comment = 0, space = 0;
 
-       strbuf_reset(&value);
+       strbuf_reset(&cf->value);
        for (;;) {
                int c = get_next_char();
                if (c == '\n') {
                        if (quote)
                                return NULL;
-                       return value.buf;
+                       return cf->value.buf;
                }
                if (comment)
                        continue;
                if (isspace(c) && !quote) {
-                       if (value.len)
+                       if (cf->value.len)
                                space++;
                        continue;
                }
@@ -146,7 +153,7 @@ static char *parse_value(void)
                        }
                }
                for (; space; space--)
-                       strbuf_addch(&value, ' ');
+                       strbuf_addch(&cf->value, ' ');
                if (c == '\\') {
                        c = get_next_char();
                        switch (c) {
@@ -168,14 +175,14 @@ static char *parse_value(void)
                        default:
                                return NULL;
                        }
-                       strbuf_addch(&value, c);
+                       strbuf_addch(&cf->value, c);
                        continue;
                }
                if (c == '"') {
                        quote = 1-quote;
                        continue;
                }
-               strbuf_addch(&value, c);
+               strbuf_addch(&cf->value, c);
        }
 }
 
@@ -192,7 +199,7 @@ static int get_value(config_fn_t fn, void *data, char *name, unsigned int len)
        /* Get the full name */
        for (;;) {
                c = get_next_char();
-               if (config_file_eof)
+               if (cf->eof)
                        break;
                if (!iskeychar(c))
                        break;
@@ -256,7 +263,7 @@ static int get_base_var(char *name)
 
        for (;;) {
                int c = get_next_char();
-               if (config_file_eof)
+               if (cf->eof)
                        return -1;
                if (c == ']')
                        return baselen;
@@ -274,7 +281,7 @@ static int git_parse_file(config_fn_t fn, void *data)
 {
        int comment = 0;
        int baselen = 0;
-       static char var[MAXNAME];
+       char *var = cf->var;
 
        /* U+FEFF Byte Order Mark in UTF8 */
        static const unsigned char *utf8_bom = (unsigned char *) "\xef\xbb\xbf";
@@ -298,7 +305,7 @@ static int git_parse_file(config_fn_t fn, void *data)
                        }
                }
                if (c == '\n') {
-                       if (config_file_eof)
+                       if (cf->eof)
                                return 0;
                        comment = 0;
                        continue;
@@ -323,7 +330,7 @@ static int git_parse_file(config_fn_t fn, void *data)
                if (get_value(fn, data, var, baselen+1) < 0)
                        break;
        }
-       die("bad config file line %d in %s", config_linenr, config_file_name);
+       die("bad config file line %d in %s", cf->linenr, cf->name);
 }
 
 static int parse_unit_factor(const char *end, unsigned long *val)
@@ -374,8 +381,8 @@ int git_parse_ulong(const char *value, unsigned long *ret)
 
 static void die_bad_config(const char *name)
 {
-       if (config_file_name)
-               die("bad config value for '%s' in %s", name, config_file_name);
+       if (cf && cf->name)
+               die("bad config value for '%s' in %s", name, cf->name);
        die("bad config value for '%s'", name);
 }
 
@@ -569,6 +576,9 @@ static int git_default_core_config(const char *var, const char *value)
                return 0;
        }
 
+       if (!strcmp(var, "core.logpackaccess"))
+               return git_config_string(&log_pack_access, var, value);
+
        if (!strcmp(var, "core.autocrlf")) {
                if (value && !strcasecmp(value, "input")) {
                        if (core_eol == EOL_CRLF)
@@ -795,13 +805,24 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data)
 
        ret = -1;
        if (f) {
-               config_file = f;
-               config_file_name = filename;
-               config_linenr = 1;
-               config_file_eof = 0;
+               config_file top;
+
+               /* push config-file parsing state stack */
+               top.prev = cf;
+               top.f = f;
+               top.name = filename;
+               top.linenr = 1;
+               top.eof = 0;
+               strbuf_init(&top.value, 1024);
+               cf = &top;
+
                ret = git_parse_file(fn, data);
+
+               /* pop config-file parsing state stack */
+               strbuf_release(&top.value);
+               cf = top.prev;
+
                fclose(f);
-               config_file_name = NULL;
        }
        return ret;
 }
@@ -856,7 +877,7 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
 
        switch (git_config_from_parameters(fn, data)) {
        case -1: /* error */
-               ret--;
+               die("unable to parse command-line config");
                break;
        case 0: /* found nothing */
                break;
@@ -909,6 +930,7 @@ static int store_aux(const char *key, const char *value, void *cb)
 {
        const char *ep;
        size_t section_len;
+       FILE *f = cf->f;
 
        switch (store.state) {
        case KEY_SEEN:
@@ -920,7 +942,7 @@ static int store_aux(const char *key, const char *value, void *cb)
                                return 1;
                        }
 
-                       store.offset[store.seen] = ftell(config_file);
+                       store.offset[store.seen] = ftell(f);
                        store.seen++;
                }
                break;
@@ -947,19 +969,19 @@ static int store_aux(const char *key, const char *value, void *cb)
                 * Do not increment matches: this is no match, but we
                 * just made sure we are in the desired section.
                 */
-               store.offset[store.seen] = ftell(config_file);
+               store.offset[store.seen] = ftell(f);
                /* fallthru */
        case SECTION_END_SEEN:
        case START:
                if (matches(key, value)) {
-                       store.offset[store.seen] = ftell(config_file);
+                       store.offset[store.seen] = ftell(f);
                        store.state = KEY_SEEN;
                        store.seen++;
                } else {
                        if (strrchr(key, '.') - key == store.baselen &&
                              !strncmp(key, store.key, store.baselen)) {
                                        store.state = SECTION_SEEN;
-                                       store.offset[store.seen] = ftell(config_file);
+                                       store.offset[store.seen] = ftell(f);
                        }
                }
        }
@@ -1415,6 +1437,7 @@ int git_config_rename_section(const char *old_name, const char *new_name)
        struct lock_file *lock = xcalloc(sizeof(struct lock_file), 1);
        int out_fd;
        char buf[1024];
+       FILE *config_file;
 
        if (config_exclusive_filename)
                config_filename = xstrdup(config_exclusive_filename);
@@ -1479,10 +1502,10 @@ int git_config_rename_section(const char *old_name, const char *new_name)
                }
        }
        fclose(config_file);
- unlock_and_out:
+unlock_and_out:
        if (commit_lock_file(lock) < 0)
                ret = error("could not commit config file %s", config_filename);
- out:
+out:
        free(config_filename);
        return ret;
 }
index 2119c3f74edcd2a976ff0e375c5d8d60d40da2ec..ee1d4b4b46f789168c155eb170b87dbed85fb590 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -192,7 +192,8 @@ static const char *ai_name(const struct addrinfo *ai)
  */
 static int git_tcp_connect_sock(char *host, int flags)
 {
-       int sockfd = -1, saved_errno = 0;
+       struct strbuf error_message = STRBUF_INIT;
+       int sockfd = -1;
        const char *port = STR(DEFAULT_GIT_PORT);
        struct addrinfo hints, *ai0, *ai;
        int gai;
@@ -216,21 +217,15 @@ static int git_tcp_connect_sock(char *host, int flags)
        if (flags & CONNECT_VERBOSE)
                fprintf(stderr, "done.\nConnecting to %s (port %s) ... ", host, port);
 
-       for (ai0 = ai; ai; ai = ai->ai_next) {
+       for (ai0 = ai; ai; ai = ai->ai_next, cnt++) {
                sockfd = socket(ai->ai_family,
                                ai->ai_socktype, ai->ai_protocol);
-               if (sockfd < 0) {
-                       saved_errno = errno;
-                       continue;
-               }
-               if (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
-                       saved_errno = errno;
-                       fprintf(stderr, "%s[%d: %s]: errno=%s\n",
-                               host,
-                               cnt,
-                               ai_name(ai),
-                               strerror(saved_errno));
-                       close(sockfd);
+               if ((sockfd < 0) ||
+                   (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0)) {
+                       strbuf_addf(&error_message, "%s[%d: %s]: errno=%s\n",
+                                   host, cnt, ai_name(ai), strerror(errno));
+                       if (0 <= sockfd)
+                               close(sockfd);
                        sockfd = -1;
                        continue;
                }
@@ -242,11 +237,13 @@ static int git_tcp_connect_sock(char *host, int flags)
        freeaddrinfo(ai0);
 
        if (sockfd < 0)
-               die("unable to connect a socket (%s)", strerror(saved_errno));
+               die("unable to connect to %s:\n%s", host, error_message.buf);
 
        if (flags & CONNECT_VERBOSE)
                fprintf(stderr, "done.\n");
 
+       strbuf_release(&error_message);
+
        return sockfd;
 }
 
@@ -257,7 +254,8 @@ static int git_tcp_connect_sock(char *host, int flags)
  */
 static int git_tcp_connect_sock(char *host, int flags)
 {
-       int sockfd = -1, saved_errno = 0;
+       struct strbuf error_message = STRBUF_INIT;
+       int sockfd = -1;
        const char *port = STR(DEFAULT_GIT_PORT);
        char *ep;
        struct hostent *he;
@@ -287,25 +285,21 @@ static int git_tcp_connect_sock(char *host, int flags)
                fprintf(stderr, "done.\nConnecting to %s (port %s) ... ", host, port);
 
        for (cnt = 0, ap = he->h_addr_list; *ap; ap++, cnt++) {
-               sockfd = socket(he->h_addrtype, SOCK_STREAM, 0);
-               if (sockfd < 0) {
-                       saved_errno = errno;
-                       continue;
-               }
-
                memset(&sa, 0, sizeof sa);
                sa.sin_family = he->h_addrtype;
                sa.sin_port = htons(nport);
                memcpy(&sa.sin_addr, *ap, he->h_length);
 
-               if (connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) {
-                       saved_errno = errno;
-                       fprintf(stderr, "%s[%d: %s]: errno=%s\n",
+               sockfd = socket(he->h_addrtype, SOCK_STREAM, 0);
+               if ((sockfd < 0) ||
+                   connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) {
+                       strbuf_addf(&error_message, "%s[%d: %s]: errno=%s\n",
                                host,
                                cnt,
                                inet_ntoa(*(struct in_addr *)&sa.sin_addr),
-                               strerror(saved_errno));
-                       close(sockfd);
+                               strerror(errno));
+                       if (0 <= sockfd)
+                               close(sockfd);
                        sockfd = -1;
                        continue;
                }
@@ -316,7 +310,7 @@ static int git_tcp_connect_sock(char *host, int flags)
        }
 
        if (sockfd < 0)
-               die("unable to connect a socket (%s)", strerror(saved_errno));
+               die("unable to connect to %s:\n%s", host, error_message.buf);
 
        if (flags & CONNECT_VERBOSE)
                fprintf(stderr, "done.\n");
index 5a8309076dc633f431797c4bd59876490b0d7408..8648a36e7b8239e969dc8971f451431c298e405e 100755 (executable)
@@ -1469,7 +1469,7 @@ _git_help ()
        __gitcomp "$__git_all_commands $(__git_aliases)
                attributes cli core-tutorial cvs-migration
                diffcore gitk glossary hooks ignore modules
-               repository-layout tutorial tutorial-2
+               namespaces repository-layout tutorial tutorial-2
                workflows
                "
 }
@@ -2640,6 +2640,7 @@ _git ()
                        --exec-path
                        --html-path
                        --work-tree=
+                       --namespace=
                        --help
                        "
                        ;;
index 9718abf86d8cd36ddae1eae8cf2337e35b927959..0565d83fc42c3d689b34299b38f0d2a7a0ae4212 100644 (file)
@@ -8,6 +8,7 @@ git-convert-objects - Converts old-style git repository
 
 SYNOPSIS
 --------
+[verse]
 'git-convert-objects'
 
 DESCRIPTION
index 98d2aee67fc1f6bfdfc960527b6f0bf6acf5122a..2f7b270566471ebe8088cd2f024c0ca5e49eee4c 100755 (executable)
@@ -342,6 +342,11 @@ def gitConfig(key, args = None): # set args to "--bool", for instance
         _gitConfig[key] = read_pipe(cmd, ignore_error=True).strip()
     return _gitConfig[key]
 
+def gitConfigList(key):
+    if not _gitConfig.has_key(key):
+        _gitConfig[key] = read_pipe("git config --get-all %s" % key, ignore_error=True).strip().split(os.linesep)
+    return _gitConfig[key]
+
 def p4BranchesInGit(branchesAreInRemotes = True):
     branches = {}
 
@@ -774,17 +779,22 @@ class P4Submit(Command, P4UserMap):
 
         if not self.detectRenames:
             # If not explicitly set check the config variable
-            self.detectRenames = gitConfig("git-p4.detectRenames").lower() == "true"
+            self.detectRenames = gitConfig("git-p4.detectRenames")
 
-        if self.detectRenames:
+        if self.detectRenames.lower() == "false" or self.detectRenames == "":
+            diffOpts = ""
+        elif self.detectRenames.lower() == "true":
             diffOpts = "-M"
         else:
-            diffOpts = ""
+            diffOpts = "-M%s" % self.detectRenames
 
-        if gitConfig("git-p4.detectCopies").lower() == "true":
+        detectCopies = gitConfig("git-p4.detectCopies")
+        if detectCopies.lower() == "true":
             diffOpts += " -C"
+        elif detectCopies != "" and detectCopies.lower() != "false":
+            diffOpts += " -C%s" % detectCopies
 
-        if gitConfig("git-p4.detectCopiesHarder").lower() == "true":
+        if gitConfig("git-p4.detectCopiesHarder", "--bool") == "true":
             diffOpts += " --find-copies-harder"
 
         diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
@@ -1450,7 +1460,13 @@ class P4Sync(Command, P4UserMap):
     def getBranchMapping(self):
         lostAndFoundBranches = set()
 
-        for info in p4CmdList("branches"):
+        user = gitConfig("git-p4.branchUser")
+        if len(user) > 0:
+            command = "branches -u %s" % user
+        else:
+            command = "branches"
+
+        for info in p4CmdList(command):
             details = p4Cmd("branch -o %s" % info["branch"])
             viewIdx = 0
             while details.has_key("View%s" % viewIdx):
@@ -1479,6 +1495,25 @@ class P4Sync(Command, P4UserMap):
                     if source not in self.knownBranches:
                         lostAndFoundBranches.add(source)
 
+        # Perforce does not strictly require branches to be defined, so we also
+        # check git config for a branch list.
+        #
+        # Example of branch definition in git config file:
+        # [git-p4]
+        #   branchList=main:branchA
+        #   branchList=main:branchB
+        #   branchList=branchA:branchC
+        configBranches = gitConfigList("git-p4.branchList")
+        for branch in configBranches:
+            if branch:
+                (source, destination) = branch.split(":")
+                self.knownBranches[destination] = source
+
+                lostAndFoundBranches.discard(destination)
+
+                if source not in self.knownBranches:
+                    lostAndFoundBranches.add(source)
+
 
         for branch in lostAndFoundBranches:
             self.knownBranches[branch] = branch
@@ -1649,7 +1684,8 @@ class P4Sync(Command, P4UserMap):
     def importHeadRevision(self, revision):
         print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
 
-        details = { "user" : "git perforce import user", "time" : int(time.time()) }
+        details = {}
+        details["user"] = "git perforce import user"
         details["desc"] = ("Initial import of %s from the state at revision %s\n"
                            % (' '.join(self.depotPaths), revision))
         details["change"] = revision
@@ -1689,6 +1725,18 @@ class P4Sync(Command, P4UserMap):
             fileCnt = fileCnt + 1
 
         details["change"] = newestRevision
+
+        # Use time from top-most change so that all git-p4 clones of
+        # the same p4 repo have the same commit SHA1s.
+        res = p4CmdList("describe -s %d" % newestRevision)
+        newestTime = None
+        for r in res:
+            if r.has_key('time'):
+                newestTime = int(r['time'])
+        if newestTime is None:
+            die("\"describe -s\" on newest change %d did not give a time")
+        details["time"] = newestTime
+
         self.updateOptionDict(details)
         try:
             self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths)
@@ -1811,12 +1859,14 @@ class P4Sync(Command, P4UserMap):
                     else:
                         paths = []
                         for (prev, cur) in zip(self.previousDepotPaths, depotPaths):
-                            for i in range(0, min(len(cur), len(prev))):
-                                if cur[i] <> prev[i]:
+                            prev_list = prev.split("/")
+                            cur_list = cur.split("/")
+                            for i in range(0, min(len(cur_list), len(prev_list))):
+                                if cur_list[i] <> prev_list[i]:
                                     i = i - 1
                                     break
 
-                            paths.append (cur[:i + 1])
+                            paths.append ("/".join(cur_list[:i + 1]))
 
                         self.previousDepotPaths = paths
 
index caa4bb3e30c081394b464de6e92ca3298a87fdd1..52003ae9045626077009811cd8e10c1135d69cd0 100644 (file)
@@ -232,6 +232,44 @@ git-p4.skipUserNameCheck
 When submitting, git-p4 checks that the git commits are authored by the current
 p4 user, and warns if they are not. This disables the check.
 
+git-p4.detectRenames
+
+Detect renames when submitting changes to Perforce server. Will enable -M git
+argument. Can be optionally set to a number representing the threshold
+percentage value of the rename detection.
+
+  git config [--global] git-p4.detectRenames true
+  git config [--global] git-p4.detectRenames 50
+
+git-p4.detectCopies
+
+Detect copies when submitting changes to Perforce server. Will enable -C git
+argument. Can be optionally set to a number representing the threshold
+percentage value of the copy detection.
+
+  git config [--global] git-p4.detectCopies true
+  git config [--global] git-p4.detectCopies 80
+
+git-p4.detectCopiesHarder
+
+Detect copies even between files that did not change when submitting changes to
+Perforce server. Will enable --find-copies-harder git argument.
+
+  git config [--global] git-p4.detectCopies true
+
+git-p4.branchUser
+
+Only use branch specifications defined by the selected username.
+
+  git config [--global] git-p4.branchUser username
+
+git-p4.branchList
+
+List of branches to be imported when branch detection is enabled.
+
+  git config [--global] git-p4.branchList main:branchA
+  git config [--global] --add git-p4.branchList main:branchB
+
 Implementation Details...
 =========================
 
index 77c29de305fabc518edf060b0e6634d9c5c9f71e..9e12f978425cbc45e5cd072b8bfb2fc57fae610b 100644 (file)
@@ -7,6 +7,7 @@ gitview - A GTK based repository browser for git
 
 SYNOPSIS
 --------
+[verse]
 'gitview' [options] [args]
 
 DESCRIPTION
index 21989fc6ab84c0503cdc9418673d9d0bf06f9d49..ba077c13f96779d5b29237e83974b0910b4bbdde 100755 (executable)
 # will have put this somewhere standard.  You should make this script
 # executable then link to it in the repository you would like to use it in.
 # For example, on debian the hook is stored in
-# /usr/share/doc/git-core/contrib/hooks/post-receive-email:
+# /usr/share/git-core/contrib/hooks/post-receive-email:
 #
 #  chmod a+x post-receive-email
 #  cd /path/to/your/repository.git
-#  ln -sf /usr/share/doc/git-core/contrib/hooks/post-receive-email hooks/post-receive
+#  ln -sf /usr/share/git-core/contrib/hooks/post-receive-email hooks/post-receive
 #
 # This hook script assumes it is enabled on the central repository of a
 # project, with all users pushing only to it and not between each other.  It
 #   email body. If not specified, there is no limit.
 #   Lines beyond the limit are suppressed and counted, and a final
 #   line is added indicating the number of suppressed lines.
+# hooks.diffopts
+#   Alternate options for the git diff-tree invocation that shows changes.
+#   Default is "--stat --summary --find-copies-harder". Add -p to those
+#   options to include a unified diff of changes in addition to the usual
+#   summary output.
 #
 # Notes
 # -----
@@ -446,7 +451,7 @@ generate_update_branch_email()
        # non-fast-forward updates.
        echo ""
        echo "Summary of changes:"
-       git diff-tree --stat --summary --find-copies-harder $oldrev..$newrev
+       git diff-tree $diffopts $oldrev..$newrev
 }
 
 #
@@ -723,6 +728,8 @@ envelopesender=$(git config hooks.envelopesender)
 emailprefix=$(git config hooks.emailprefix || echo '[SCM] ')
 custom_showrev=$(git config hooks.showrev)
 maxlines=$(git config hooks.emailmaxlines)
+diffopts=$(git config hooks.diffopts)
+: ${diffopts:="--stat --summary --find-copies-harder"}
 
 # --- Main loop
 # Allow dual mode: run from the command line just like the update hook, or
index cd075b96c54419fdaa25c56b24133dd080f440ca..72ffea0b3a6a29d69cb8c88989adc0692cd01f34 100644 (file)
@@ -7,6 +7,7 @@ svn-fe - convert an SVN "dumpfile" to a fast-import stream
 
 SYNOPSIS
 --------
+[verse]
 svnadmin dump --incremental REPO | svn-fe [url] | git fast-import
 
 DESCRIPTION
index efc7e07d475c66f7835dc6cbbd3bc358f01c41c3..3bb5a4dd57c669bc59be0e2317ef33b64b024992 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -533,7 +533,7 @@ static int ident_to_git(const char *path, const char *src, size_t len,
                dollar = memchr(src, '$', len);
                if (!dollar)
                        break;
-               memcpy(dst, src, dollar + 1 - src);
+               memmove(dst, src, dollar + 1 - src);
                dst += dollar + 1 - src;
                len -= dollar + 1 - src;
                src  = dollar + 1;
@@ -553,7 +553,7 @@ static int ident_to_git(const char *path, const char *src, size_t len,
                        src  = dollar + 1;
                }
        }
-       memcpy(dst, src, len);
+       memmove(dst, src, len);
        strbuf_setlen(buf, dst + len - buf->buf);
        return 1;
 }
@@ -727,7 +727,7 @@ static void convert_attrs(struct conv_attrs *ca, const char *path)
                git_config(read_convert_config, NULL);
        }
 
-       if (!git_checkattr(path, NUM_CONV_ATTRS, ccheck)) {
+       if (!git_check_attr(path, NUM_CONV_ATTRS, ccheck)) {
                ca->crlf_action = git_path_check_crlf(path, ccheck + 4);
                if (ca->crlf_action == CRLF_GUESS)
                        ca->crlf_action = git_path_check_crlf(path, ccheck + 0);
@@ -813,3 +813,400 @@ int renormalize_buffer(const char *path, const char *src, size_t len, struct str
        }
        return ret | convert_to_git(path, src, len, dst, 0);
 }
+
+/*****************************************************************
+ *
+ * Streaming converison support
+ *
+ *****************************************************************/
+
+typedef int (*filter_fn)(struct stream_filter *,
+                        const char *input, size_t *isize_p,
+                        char *output, size_t *osize_p);
+typedef void (*free_fn)(struct stream_filter *);
+
+struct stream_filter_vtbl {
+       filter_fn filter;
+       free_fn free;
+};
+
+struct stream_filter {
+       struct stream_filter_vtbl *vtbl;
+};
+
+static int null_filter_fn(struct stream_filter *filter,
+                         const char *input, size_t *isize_p,
+                         char *output, size_t *osize_p)
+{
+       size_t count;
+
+       if (!input)
+               return 0; /* we do not keep any states */
+       count = *isize_p;
+       if (*osize_p < count)
+               count = *osize_p;
+       if (count) {
+               memmove(output, input, count);
+               *isize_p -= count;
+               *osize_p -= count;
+       }
+       return 0;
+}
+
+static void null_free_fn(struct stream_filter *filter)
+{
+       ; /* nothing -- null instances are shared */
+}
+
+static struct stream_filter_vtbl null_vtbl = {
+       null_filter_fn,
+       null_free_fn,
+};
+
+static struct stream_filter null_filter_singleton = {
+       &null_vtbl,
+};
+
+int is_null_stream_filter(struct stream_filter *filter)
+{
+       return filter == &null_filter_singleton;
+}
+
+
+/*
+ * LF-to-CRLF filter
+ */
+static int lf_to_crlf_filter_fn(struct stream_filter *filter,
+                               const char *input, size_t *isize_p,
+                               char *output, size_t *osize_p)
+{
+       size_t count;
+
+       if (!input)
+               return 0; /* we do not keep any states */
+       count = *isize_p;
+       if (count) {
+               size_t i, o;
+               for (i = o = 0; o < *osize_p && i < count; i++) {
+                       char ch = input[i];
+                       if (ch == '\n') {
+                               if (o + 1 < *osize_p)
+                                       output[o++] = '\r';
+                               else
+                                       break;
+                       }
+                       output[o++] = ch;
+               }
+
+               *osize_p -= o;
+               *isize_p -= i;
+       }
+       return 0;
+}
+
+static struct stream_filter_vtbl lf_to_crlf_vtbl = {
+       lf_to_crlf_filter_fn,
+       null_free_fn,
+};
+
+static struct stream_filter lf_to_crlf_filter_singleton = {
+       &lf_to_crlf_vtbl,
+};
+
+
+/*
+ * Cascade filter
+ */
+#define FILTER_BUFFER 1024
+struct cascade_filter {
+       struct stream_filter filter;
+       struct stream_filter *one;
+       struct stream_filter *two;
+       char buf[FILTER_BUFFER];
+       int end, ptr;
+};
+
+static int cascade_filter_fn(struct stream_filter *filter,
+                            const char *input, size_t *isize_p,
+                            char *output, size_t *osize_p)
+{
+       struct cascade_filter *cas = (struct cascade_filter *) filter;
+       size_t filled = 0;
+       size_t sz = *osize_p;
+       size_t to_feed, remaining;
+
+       /*
+        * input -- (one) --> buf -- (two) --> output
+        */
+       while (filled < sz) {
+               remaining = sz - filled;
+
+               /* do we already have something to feed two with? */
+               if (cas->ptr < cas->end) {
+                       to_feed = cas->end - cas->ptr;
+                       if (stream_filter(cas->two,
+                                         cas->buf + cas->ptr, &to_feed,
+                                         output + filled, &remaining))
+                               return -1;
+                       cas->ptr += (cas->end - cas->ptr) - to_feed;
+                       filled = sz - remaining;
+                       continue;
+               }
+
+               /* feed one from upstream and have it emit into our buffer */
+               to_feed = input ? *isize_p : 0;
+               if (input && !to_feed)
+                       break;
+               remaining = sizeof(cas->buf);
+               if (stream_filter(cas->one,
+                                 input, &to_feed,
+                                 cas->buf, &remaining))
+                       return -1;
+               cas->end = sizeof(cas->buf) - remaining;
+               cas->ptr = 0;
+               if (input) {
+                       size_t fed = *isize_p - to_feed;
+                       *isize_p -= fed;
+                       input += fed;
+               }
+
+               /* do we know that we drained one completely? */
+               if (input || cas->end)
+                       continue;
+
+               /* tell two to drain; we have nothing more to give it */
+               to_feed = 0;
+               remaining = sz - filled;
+               if (stream_filter(cas->two,
+                                 NULL, &to_feed,
+                                 output + filled, &remaining))
+                       return -1;
+               if (remaining == (sz - filled))
+                       break; /* completely drained two */
+               filled = sz - remaining;
+       }
+       *osize_p -= filled;
+       return 0;
+}
+
+static void cascade_free_fn(struct stream_filter *filter)
+{
+       struct cascade_filter *cas = (struct cascade_filter *)filter;
+       free_stream_filter(cas->one);
+       free_stream_filter(cas->two);
+       free(filter);
+}
+
+static struct stream_filter_vtbl cascade_vtbl = {
+       cascade_filter_fn,
+       cascade_free_fn,
+};
+
+static struct stream_filter *cascade_filter(struct stream_filter *one,
+                                           struct stream_filter *two)
+{
+       struct cascade_filter *cascade;
+
+       if (!one || is_null_stream_filter(one))
+               return two;
+       if (!two || is_null_stream_filter(two))
+               return one;
+
+       cascade = xmalloc(sizeof(*cascade));
+       cascade->one = one;
+       cascade->two = two;
+       cascade->end = cascade->ptr = 0;
+       cascade->filter.vtbl = &cascade_vtbl;
+       return (struct stream_filter *)cascade;
+}
+
+/*
+ * ident filter
+ */
+#define IDENT_DRAINING (-1)
+#define IDENT_SKIPPING (-2)
+struct ident_filter {
+       struct stream_filter filter;
+       struct strbuf left;
+       int state;
+       char ident[45]; /* ": x40 $" */
+};
+
+static int is_foreign_ident(const char *str)
+{
+       int i;
+
+       if (prefixcmp(str, "$Id: "))
+               return 0;
+       for (i = 5; str[i]; i++) {
+               if (isspace(str[i]) && str[i+1] != '$')
+                       return 1;
+       }
+       return 0;
+}
+
+static void ident_drain(struct ident_filter *ident, char **output_p, size_t *osize_p)
+{
+       size_t to_drain = ident->left.len;
+
+       if (*osize_p < to_drain)
+               to_drain = *osize_p;
+       if (to_drain) {
+               memcpy(*output_p, ident->left.buf, to_drain);
+               strbuf_remove(&ident->left, 0, to_drain);
+               *output_p += to_drain;
+               *osize_p -= to_drain;
+       }
+       if (!ident->left.len)
+               ident->state = 0;
+}
+
+static int ident_filter_fn(struct stream_filter *filter,
+                          const char *input, size_t *isize_p,
+                          char *output, size_t *osize_p)
+{
+       struct ident_filter *ident = (struct ident_filter *)filter;
+       static const char head[] = "$Id";
+
+       if (!input) {
+               /* drain upon eof */
+               switch (ident->state) {
+               default:
+                       strbuf_add(&ident->left, head, ident->state);
+               case IDENT_SKIPPING:
+                       /* fallthru */
+               case IDENT_DRAINING:
+                       ident_drain(ident, &output, osize_p);
+               }
+               return 0;
+       }
+
+       while (*isize_p || (ident->state == IDENT_DRAINING)) {
+               int ch;
+
+               if (ident->state == IDENT_DRAINING) {
+                       ident_drain(ident, &output, osize_p);
+                       if (!*osize_p)
+                               break;
+                       continue;
+               }
+
+               ch = *(input++);
+               (*isize_p)--;
+
+               if (ident->state == IDENT_SKIPPING) {
+                       /*
+                        * Skipping until '$' or LF, but keeping them
+                        * in case it is a foreign ident.
+                        */
+                       strbuf_addch(&ident->left, ch);
+                       if (ch != '\n' && ch != '$')
+                               continue;
+                       if (ch == '$' && !is_foreign_ident(ident->left.buf)) {
+                               strbuf_setlen(&ident->left, sizeof(head) - 1);
+                               strbuf_addstr(&ident->left, ident->ident);
+                       }
+                       ident->state = IDENT_DRAINING;
+                       continue;
+               }
+
+               if (ident->state < sizeof(head) &&
+                   head[ident->state] == ch) {
+                       ident->state++;
+                       continue;
+               }
+
+               if (ident->state)
+                       strbuf_add(&ident->left, head, ident->state);
+               if (ident->state == sizeof(head) - 1) {
+                       if (ch != ':' && ch != '$') {
+                               strbuf_addch(&ident->left, ch);
+                               ident->state = 0;
+                               continue;
+                       }
+
+                       if (ch == ':') {
+                               strbuf_addch(&ident->left, ch);
+                               ident->state = IDENT_SKIPPING;
+                       } else {
+                               strbuf_addstr(&ident->left, ident->ident);
+                               ident->state = IDENT_DRAINING;
+                       }
+                       continue;
+               }
+
+               strbuf_addch(&ident->left, ch);
+               ident->state = IDENT_DRAINING;
+       }
+       return 0;
+}
+
+static void ident_free_fn(struct stream_filter *filter)
+{
+       struct ident_filter *ident = (struct ident_filter *)filter;
+       strbuf_release(&ident->left);
+       free(filter);
+}
+
+static struct stream_filter_vtbl ident_vtbl = {
+       ident_filter_fn,
+       ident_free_fn,
+};
+
+static struct stream_filter *ident_filter(const unsigned char *sha1)
+{
+       struct ident_filter *ident = xmalloc(sizeof(*ident));
+
+       sprintf(ident->ident, ": %s $", sha1_to_hex(sha1));
+       strbuf_init(&ident->left, 0);
+       ident->filter.vtbl = &ident_vtbl;
+       ident->state = 0;
+       return (struct stream_filter *)ident;
+}
+
+/*
+ * Return an appropriately constructed filter for the path, or NULL if
+ * the contents cannot be filtered without reading the whole thing
+ * in-core.
+ *
+ * Note that you would be crazy to set CRLF, smuge/clean or ident to a
+ * large binary blob you would want us not to slurp into the memory!
+ */
+struct stream_filter *get_stream_filter(const char *path, const unsigned char *sha1)
+{
+       struct conv_attrs ca;
+       enum crlf_action crlf_action;
+       struct stream_filter *filter = NULL;
+
+       convert_attrs(&ca, path);
+
+       if (ca.drv && (ca.drv->smudge || ca.drv->clean))
+               return filter;
+
+       if (ca.ident)
+               filter = ident_filter(sha1);
+
+       crlf_action = input_crlf_action(ca.crlf_action, ca.eol_attr);
+
+       if ((crlf_action == CRLF_BINARY) || (crlf_action == CRLF_INPUT) ||
+           (crlf_action == CRLF_GUESS && auto_crlf == AUTO_CRLF_FALSE))
+               filter = cascade_filter(filter, &null_filter_singleton);
+
+       else if (output_eol(crlf_action) == EOL_CRLF &&
+                !(crlf_action == CRLF_AUTO || crlf_action == CRLF_GUESS))
+               filter = cascade_filter(filter, &lf_to_crlf_filter_singleton);
+
+       return filter;
+}
+
+void free_stream_filter(struct stream_filter *filter)
+{
+       filter->vtbl->free(filter);
+}
+
+int stream_filter(struct stream_filter *filter,
+                 const char *input, size_t *isize_p,
+                 char *output, size_t *osize_p)
+{
+       return filter->vtbl->filter(filter, input, isize_p, output, osize_p);
+}
diff --git a/convert.h b/convert.h
new file mode 100644 (file)
index 0000000..d799a16
--- /dev/null
+++ b/convert.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2011, Google Inc.
+ */
+#ifndef CONVERT_H
+#define CONVERT_H
+
+enum safe_crlf {
+       SAFE_CRLF_FALSE = 0,
+       SAFE_CRLF_FAIL = 1,
+       SAFE_CRLF_WARN = 2
+};
+
+extern enum safe_crlf safe_crlf;
+
+enum auto_crlf {
+       AUTO_CRLF_FALSE = 0,
+       AUTO_CRLF_TRUE = 1,
+       AUTO_CRLF_INPUT = -1
+};
+
+extern enum auto_crlf auto_crlf;
+
+enum eol {
+       EOL_UNSET,
+       EOL_CRLF,
+       EOL_LF,
+#ifdef NATIVE_CRLF
+       EOL_NATIVE = EOL_CRLF
+#else
+       EOL_NATIVE = EOL_LF
+#endif
+};
+
+extern enum eol core_eol;
+
+/* returns 1 if *dst was used */
+extern int convert_to_git(const char *path, const char *src, size_t len,
+                         struct strbuf *dst, enum safe_crlf checksafe);
+extern int convert_to_working_tree(const char *path, const char *src,
+                                  size_t len, struct strbuf *dst);
+extern int renormalize_buffer(const char *path, const char *src, size_t len,
+                             struct strbuf *dst);
+
+/*****************************************************************
+ *
+ * Streaming converison support
+ *
+ *****************************************************************/
+
+struct stream_filter; /* opaque */
+
+extern struct stream_filter *get_stream_filter(const char *path, const unsigned char *);
+extern void free_stream_filter(struct stream_filter *);
+extern int is_null_stream_filter(struct stream_filter *);
+
+/*
+ * Use as much input up to *isize_p and fill output up to *osize_p;
+ * update isize_p and osize_p to indicate how much buffer space was
+ * consumed and filled. Return 0 on success, non-zero on error.
+ *
+ * Some filters may need to buffer the input and look-ahead inside it
+ * to decide what to output, and they may consume more than zero bytes
+ * of input and still not produce any output. After feeding all the
+ * input, pass NULL as input and keep calling this function, to let
+ * such filters know there is no more input coming and it is time for
+ * them to produce the remaining output based on the buffered input.
+ */
+extern int stream_filter(struct stream_filter *,
+                        const char *input, size_t *isize_p,
+                        char *output, size_t *osize_p);
+
+#endif /* CONVERT_H */
index be49d5fcf900cb47cb14d8c85a69112b26532b93..fc97d6e04528b5c5b55fc211a462f3cb828f3d49 100644 (file)
 #include "progress.h"
 #include "csum-file.h"
 
-static void flush(struct sha1file *f, void * buf, unsigned int count)
+static void flush(struct sha1file *f, void *buf, unsigned int count)
 {
+       if (0 <= f->check_fd && count)  {
+               unsigned char check_buffer[8192];
+               ssize_t ret = read_in_full(f->check_fd, check_buffer, count);
+
+               if (ret < 0)
+                       die_errno("%s: sha1 file read error", f->name);
+               if (ret < count)
+                       die("%s: sha1 file truncated", f->name);
+               if (memcmp(buf, check_buffer, count))
+                       die("sha1 file '%s' validation error", f->name);
+       }
+
        for (;;) {
                int ret = xwrite(f->fd, buf, count);
                if (ret > 0) {
@@ -59,6 +71,17 @@ int sha1close(struct sha1file *f, unsigned char *result, unsigned int flags)
                fd = 0;
        } else
                fd = f->fd;
+       if (0 <= f->check_fd) {
+               char discard;
+               int cnt = read_in_full(f->check_fd, &discard, 1);
+               if (cnt < 0)
+                       die_errno("%s: error when reading the tail of sha1 file",
+                                 f->name);
+               if (cnt)
+                       die("%s: sha1 file has trailing garbage", f->name);
+               if (close(f->check_fd))
+                       die_errno("%s: sha1 file error on close", f->name);
+       }
        free(f);
        return fd;
 }
@@ -101,10 +124,31 @@ struct sha1file *sha1fd(int fd, const char *name)
        return sha1fd_throughput(fd, name, NULL);
 }
 
+struct sha1file *sha1fd_check(const char *name)
+{
+       int sink, check;
+       struct sha1file *f;
+
+       sink = open("/dev/null", O_WRONLY);
+       if (sink < 0)
+               return NULL;
+       check = open(name, O_RDONLY);
+       if (check < 0) {
+               int saved_errno = errno;
+               close(sink);
+               errno = saved_errno;
+               return NULL;
+       }
+       f = sha1fd(sink, name);
+       f->check_fd = check;
+       return f;
+}
+
 struct sha1file *sha1fd_throughput(int fd, const char *name, struct progress *tp)
 {
        struct sha1file *f = xmalloc(sizeof(*f));
        f->fd = fd;
+       f->check_fd = -1;
        f->offset = 0;
        f->total = 0;
        f->tp = tp;
index 294add2a91496355b42ce02ecfe9c453d21b291a..6a7967c6bf604076c7d68ce139f65f34df3bc30e 100644 (file)
@@ -6,6 +6,7 @@ struct progress;
 /* A SHA1-protected file */
 struct sha1file {
        int fd;
+       int check_fd;
        unsigned int offset;
        git_SHA_CTX ctx;
        off_t total;
@@ -21,6 +22,7 @@ struct sha1file {
 #define CSUM_FSYNC     2
 
 extern struct sha1file *sha1fd(int fd, const char *name);
+extern struct sha1file *sha1fd_check(const char *name);
 extern struct sha1file *sha1fd_throughput(int fd, const char *name, struct progress *tp);
 extern int sha1close(struct sha1file *, unsigned char *, unsigned int);
 extern int sha1write(struct sha1file *, void *, unsigned int);
diff --git a/date.c b/date.c
index 896fbb48060c673b8dc2f2aa6ea3de3c1042fbd2..353e0a5e53f13c36549e65452931f254de3423c4 100644 (file)
--- a/date.c
+++ b/date.c
@@ -552,23 +552,35 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt
 static int match_tz(const char *date, int *offp)
 {
        char *end;
-       int offset = strtoul(date+1, &end, 10);
-       int min, hour;
-       int n = end - date - 1;
+       int hour = strtoul(date + 1, &end, 10);
+       int n = end - (date + 1);
+       int min = 0;
 
-       min = offset % 100;
-       hour = offset / 100;
+       if (n == 4) {
+               /* hhmm */
+               min = hour % 100;
+               hour = hour / 100;
+       } else if (n != 2) {
+               min = 99; /* random crap */
+       } else if (*end == ':') {
+               /* hh:mm? */
+               min = strtoul(end + 1, &end, 10);
+               if (end - (date + 1) != 5)
+                       min = 99; /* random crap */
+       } /* otherwise we parsed "hh" */
 
        /*
-        * Don't accept any random crap.. At least 3 digits, and
-        * a valid minute. We might want to check that the minutes
-        * are divisible by 30 or something too.
+        * Don't accept any random crap. Even though some places have
+        * offset larger than 12 hours (e.g. Pacific/Kiritimati is at
+        * UTC+14), there is something wrong if hour part is much
+        * larger than that. We might also want to check that the
+        * minutes are divisible by 15 or something too. (Offset of
+        * Kathmandu, Nepal is UTC+5:45)
         */
-       if (min < 60 && n > 2) {
-               offset = hour*60+min;
+       if (min < 60 && hour < 24) {
+               int offset = hour * 60 + min;
                if (*date == '-')
                        offset = -offset;
-
                *offp = offset;
        }
        return end - date;
index 9c29293bbc05d175ba13338813e8532c7ad677cf..62f4cd94cfbc4d3fe9e46c84f318af6624349a48 100644 (file)
@@ -289,7 +289,7 @@ static void show_new_file(struct rev_info *revs,
 
        /*
         * New file in the index: it might actually be different in
-        * the working copy.
+        * the working tree.
         */
        if (get_stat_data(new, &sha1, &mode, cached, match_missing,
            &dirty_submodule, &revs->diffopt) < 0)
@@ -379,7 +379,8 @@ static void do_oneway_diff(struct unpack_trees_options *o,
        if (cached && idx && ce_stage(idx)) {
                struct diff_filepair *pair;
                pair = diff_unmerge(&revs->diffopt, idx->name);
-               fill_filespec(pair->one, idx->sha1, idx->ce_mode);
+               if (tree)
+                       fill_filespec(pair->one, tree->sha1, tree->ce_mode);
                return;
        }
 
@@ -433,26 +434,30 @@ static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o)
        if (tree == o->df_conflict_entry)
                tree = NULL;
 
-       if (ce_path_match(idx ? idx : tree, &revs->prune_data))
+       if (ce_path_match(idx ? idx : tree, &revs->prune_data)) {
                do_oneway_diff(o, idx, tree);
+               if (diff_can_quit_early(&revs->diffopt)) {
+                       o->exiting_early = 1;
+                       return -1;
+               }
+       }
 
        return 0;
 }
 
-int run_diff_index(struct rev_info *revs, int cached)
+static int diff_cache(struct rev_info *revs,
+                     const unsigned char *tree_sha1,
+                     const char *tree_name,
+                     int cached)
 {
-       struct object *ent;
        struct tree *tree;
-       const char *tree_name;
-       struct unpack_trees_options opts;
        struct tree_desc t;
+       struct unpack_trees_options opts;
 
-       ent = revs->pending.objects[0].item;
-       tree_name = revs->pending.objects[0].name;
-       tree = parse_tree_indirect(ent->sha1);
+       tree = parse_tree_indirect(tree_sha1);
        if (!tree)
-               return error("bad tree object %s", tree_name);
-
+               return error("bad tree object %s",
+                            tree_name ? tree_name : sha1_to_hex(tree_sha1));
        memset(&opts, 0, sizeof(opts));
        opts.head_idx = 1;
        opts.index_only = cached;
@@ -463,9 +468,18 @@ int run_diff_index(struct rev_info *revs, int cached)
        opts.unpack_data = revs;
        opts.src_index = &the_index;
        opts.dst_index = NULL;
+       opts.pathspec = &revs->diffopt.pathspec;
 
        init_tree_desc(&t, tree->buffer, tree->size);
-       if (unpack_trees(1, &t, &opts))
+       return unpack_trees(1, &t, &opts);
+}
+
+int run_diff_index(struct rev_info *revs, int cached)
+{
+       struct object_array_entry *ent;
+
+       ent = revs->pending.objects;
+       if (diff_cache(revs, ent->item->sha1, ent->name, cached))
                exit(128);
 
        diff_set_mnemonic_prefix(&revs->diffopt, "c/", cached ? "i/" : "w/");
@@ -477,53 +491,13 @@ int run_diff_index(struct rev_info *revs, int cached)
 
 int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
 {
-       struct tree *tree;
        struct rev_info revs;
-       int i;
-       struct cache_entry **dst;
-       struct cache_entry *last = NULL;
-       struct unpack_trees_options opts;
-       struct tree_desc t;
-
-       /*
-        * This is used by git-blame to run diff-cache internally;
-        * it potentially needs to repeatedly run this, so we will
-        * start by removing the higher order entries the last round
-        * left behind.
-        */
-       dst = active_cache;
-       for (i = 0; i < active_nr; i++) {
-               struct cache_entry *ce = active_cache[i];
-               if (ce_stage(ce)) {
-                       if (last && !strcmp(ce->name, last->name))
-                               continue;
-                       cache_tree_invalidate_path(active_cache_tree,
-                                                  ce->name);
-                       last = ce;
-                       ce->ce_flags |= CE_REMOVE;
-               }
-               *dst++ = ce;
-       }
-       active_nr = dst - active_cache;
 
        init_revisions(&revs, NULL);
        init_pathspec(&revs.prune_data, opt->pathspec.raw);
-       tree = parse_tree_indirect(tree_sha1);
-       if (!tree)
-               die("bad tree object %s", sha1_to_hex(tree_sha1));
-
-       memset(&opts, 0, sizeof(opts));
-       opts.head_idx = 1;
-       opts.index_only = 1;
-       opts.diff_index_cached = !DIFF_OPT_TST(opt, FIND_COPIES_HARDER);
-       opts.merge = 1;
-       opts.fn = oneway_diff;
-       opts.unpack_data = &revs;
-       opts.src_index = &the_index;
-       opts.dst_index = &the_index;
+       revs.diffopt = *opt;
 
-       init_tree_desc(&t, tree->buffer, tree->size);
-       if (unpack_trees(1, &t, &opts))
+       if (diff_cache(&revs, tree_sha1, NULL, 1))
                exit(128);
        return 0;
 }
diff --git a/diff.c b/diff.c
index 61bedaed57216cadaf2716ef7aa0d63889571f8a..977ca7125b9acc603ce6c1d27c57a75c9d21fbdf 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -137,7 +137,7 @@ static int git_config_rename(const char *var, const char *value)
 int git_diff_ui_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
-               diff_use_color_default = git_config_colorbool(var, value, -1);
+               diff_use_color_default = git_config_colorbool(var, value);
                return 0;
        }
        if (!strcmp(var, "diff.renames")) {
@@ -164,6 +164,9 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
        if (!strcmp(var, "diff.ignoresubmodules"))
                handle_ignore_submodules_arg(&default_diff_options, value);
 
+       if (git_color_config(var, value, cb) < 0)
+               return -1;
+
        return git_diff_basic_config(var, value, cb);
 }
 
@@ -212,7 +215,7 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
        if (!prefixcmp(var, "submodule."))
                return parse_submodule_config_option(var, value);
 
-       return git_color_default_config(var, value, cb);
+       return git_default_config(var, value, cb);
 }
 
 static char *quote_two(const char *one, const char *two)
@@ -583,11 +586,10 @@ static void emit_rewrite_diff(const char *name_a,
                              struct diff_options *o)
 {
        int lc_a, lc_b;
-       int color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
        const char *name_a_tab, *name_b_tab;
-       const char *metainfo = diff_get_color(color_diff, DIFF_METAINFO);
-       const char *fraginfo = diff_get_color(color_diff, DIFF_FRAGINFO);
-       const char *reset = diff_get_color(color_diff, DIFF_RESET);
+       const char *metainfo = diff_get_color(o->use_color, DIFF_METAINFO);
+       const char *fraginfo = diff_get_color(o->use_color, DIFF_FRAGINFO);
+       const char *reset = diff_get_color(o->use_color, DIFF_RESET);
        static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT;
        const char *a_prefix, *b_prefix;
        char *data_one, *data_two;
@@ -623,7 +625,7 @@ static void emit_rewrite_diff(const char *name_a,
        size_two = fill_textconv(textconv_two, two, &data_two);
 
        memset(&ecbdata, 0, sizeof(ecbdata));
-       ecbdata.color_diff = color_diff;
+       ecbdata.color_diff = want_color(o->use_color);
        ecbdata.found_changesp = &o->found_changes;
        ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
        ecbdata.opt = o;
@@ -1004,7 +1006,7 @@ static void free_diff_words_data(struct emit_callback *ecbdata)
 
 const char *diff_get_color(int diff_use_color, enum color_diff ix)
 {
-       if (diff_use_color)
+       if (want_color(diff_use_color))
                return diff_colors[ix];
        return "";
 }
@@ -1316,9 +1318,10 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
        int i, len, add, del, adds = 0, dels = 0;
        uintmax_t max_change = 0, max_len = 0;
        int total_files = data->nr;
-       int width, name_width;
+       int width, name_width, count;
        const char *reset, *add_c, *del_c;
        const char *line_prefix = "";
+       int extra_shown = 0;
        struct strbuf *msg = NULL;
 
        if (data->nr == 0)
@@ -1331,6 +1334,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
 
        width = options->stat_width ? options->stat_width : 80;
        name_width = options->stat_name_width ? options->stat_name_width : 50;
+       count = options->stat_count ? options->stat_count : data->nr;
 
        /* Sanity: give at least 5 columns to the graph,
         * but leave at least 10 columns for the name.
@@ -1347,9 +1351,14 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
        add_c = diff_get_color_opt(options, DIFF_FILE_NEW);
        del_c = diff_get_color_opt(options, DIFF_FILE_OLD);
 
-       for (i = 0; i < data->nr; i++) {
+       for (i = 0; (i < count) && (i < data->nr); i++) {
                struct diffstat_file *file = data->files[i];
                uintmax_t change = file->added + file->deleted;
+               if (!data->files[i]->is_renamed &&
+                        (change == 0)) {
+                       count++; /* not shown == room for one more */
+                       continue;
+               }
                fill_print_name(file);
                len = strlen(file->print_name);
                if (max_len < len)
@@ -1360,6 +1369,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                if (max_change < change)
                        max_change = change;
        }
+       count = i; /* min(count, data->nr) */
 
        /* Compute the width of the graph part;
         * 10 is for one blank at the beginning of the line plus
@@ -1374,13 +1384,18 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
        else
                width = max_change;
 
-       for (i = 0; i < data->nr; i++) {
+       for (i = 0; i < count; i++) {
                const char *prefix = "";
                char *name = data->files[i]->print_name;
                uintmax_t added = data->files[i]->added;
                uintmax_t deleted = data->files[i]->deleted;
                int name_len;
 
+               if (!data->files[i]->is_renamed &&
+                        (added + deleted == 0)) {
+                       total_files--;
+                       continue;
+               }
                /*
                 * "scale" the filename
                 */
@@ -1415,11 +1430,6 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                        fprintf(options->file, "  Unmerged\n");
                        continue;
                }
-               else if (!data->files[i]->is_renamed &&
-                        (added + deleted == 0)) {
-                       total_files--;
-                       continue;
-               }
 
                /*
                 * scale the add/delete
@@ -1441,6 +1451,20 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                show_graph(options->file, '-', del, del_c, reset);
                fprintf(options->file, "\n");
        }
+       for (i = count; i < data->nr; i++) {
+               uintmax_t added = data->files[i]->added;
+               uintmax_t deleted = data->files[i]->deleted;
+               if (!data->files[i]->is_renamed &&
+                        (added + deleted == 0)) {
+                       total_files--;
+                       continue;
+               }
+               adds += added;
+               dels += deleted;
+               if (!extra_shown)
+                       fprintf(options->file, "%s ...\n", line_prefix);
+               extra_shown = 1;
+       }
        fprintf(options->file, "%s", line_prefix);
        fprintf(options->file,
               " %d files changed, %d insertions(+), %d deletions(-)\n",
@@ -1786,11 +1810,10 @@ static int is_conflict_marker(const char *line, int marker_size, unsigned long l
 static void checkdiff_consume(void *priv, char *line, unsigned long len)
 {
        struct checkdiff_t *data = priv;
-       int color_diff = DIFF_OPT_TST(data->o, COLOR_DIFF);
        int marker_size = data->conflict_marker_size;
-       const char *ws = diff_get_color(color_diff, DIFF_WHITESPACE);
-       const char *reset = diff_get_color(color_diff, DIFF_RESET);
-       const char *set = diff_get_color(color_diff, DIFF_FILE_NEW);
+       const char *ws = diff_get_color(data->o->use_color, DIFF_WHITESPACE);
+       const char *reset = diff_get_color(data->o->use_color, DIFF_RESET);
+       const char *set = diff_get_color(data->o->use_color, DIFF_FILE_NEW);
        char *err;
        char *line_prefix = "";
        struct strbuf *msgbuf;
@@ -1839,20 +1862,20 @@ static unsigned char *deflate_it(char *data,
 {
        int bound;
        unsigned char *deflated;
-       z_stream stream;
+       git_zstream stream;
 
        memset(&stream, 0, sizeof(stream));
-       deflateInit(&stream, zlib_compression_level);
-       bound = deflateBound(&stream, size);
+       git_deflate_init(&stream, zlib_compression_level);
+       bound = git_deflate_bound(&stream, size);
        deflated = xmalloc(bound);
        stream.next_out = deflated;
        stream.avail_out = bound;
 
        stream.next_in = (unsigned char *)data;
        stream.avail_in = size;
-       while (deflate(&stream, Z_FINISH) == Z_OK)
+       while (git_deflate(&stream, Z_FINISH) == Z_OK)
                ; /* nothing */
-       deflateEnd(&stream);
+       git_deflate_end(&stream);
        *result_size = stream.total_out;
        return deflated;
 }
@@ -1984,19 +2007,7 @@ struct userdiff_driver *get_textconv(struct diff_filespec *one)
                return NULL;
 
        diff_filespec_load_driver(one);
-       if (!one->driver->textconv)
-               return NULL;
-
-       if (one->driver->textconv_want_cache && !one->driver->textconv_cache) {
-               struct notes_cache *c = xmalloc(sizeof(*c));
-               struct strbuf name = STRBUF_INIT;
-
-               strbuf_addf(&name, "textconv/%s", one->driver->name);
-               notes_cache_init(c, name.buf, one->driver->textconv);
-               one->driver->textconv_cache = c;
-       }
-
-       return one->driver;
+       return userdiff_get_textconv(one->driver);
 }
 
 static void builtin_diff(const char *name_a,
@@ -2147,7 +2158,7 @@ static void builtin_diff(const char *name_a,
                memset(&xecfg, 0, sizeof(xecfg));
                memset(&ecbdata, 0, sizeof(ecbdata));
                ecbdata.label_path = lbl;
-               ecbdata.color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
+               ecbdata.color_diff = want_color(o->use_color);
                ecbdata.found_changesp = &o->found_changes;
                ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
                if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
@@ -2195,7 +2206,7 @@ static void builtin_diff(const char *name_a,
                                        break;
                                }
                        }
-                       if (DIFF_OPT_TST(o, COLOR_DIFF)) {
+                       if (want_color(o->use_color)) {
                                struct diff_words_style *st = ecbdata.diff_words->style;
                                st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD);
                                st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW);
@@ -2845,7 +2856,7 @@ static void run_diff_cmd(const char *pgm,
                 */
                fill_metainfo(msg, name, other, one, two, o, p,
                              &must_show_header,
-                             DIFF_OPT_TST(o, COLOR_DIFF) && !pgm);
+                             want_color(o->use_color) && !pgm);
                xfrm_msg = msg->len ? msg->buf : NULL;
        }
 
@@ -3011,8 +3022,7 @@ void diff_setup(struct diff_options *options)
 
        options->change = diff_change;
        options->add_remove = diff_addremove;
-       if (diff_use_color_default > 0)
-               DIFF_OPT_SET(options, COLOR_DIFF);
+       options->use_color = diff_use_color_default;
        options->detect_rename = diff_detect_rename_default;
 
        if (diff_no_prefix) {
@@ -3220,6 +3230,7 @@ static int stat_opt(struct diff_options *options, const char **av)
        char *end;
        int width = options->stat_width;
        int name_width = options->stat_name_width;
+       int count = options->stat_count;
        int argcount = 1;
 
        arg += strlen("--stat");
@@ -3247,12 +3258,24 @@ static int stat_opt(struct diff_options *options, const char **av)
                                name_width = strtoul(av[1], &end, 10);
                                argcount = 2;
                        }
+               } else if (!prefixcmp(arg, "-count")) {
+                       arg += strlen("-count");
+                       if (*arg == '=')
+                               count = strtoul(arg + 1, &end, 10);
+                       else if (!*arg && !av[1])
+                               die("Option '--stat-count' requires a value");
+                       else if (!*arg) {
+                               count = strtoul(av[1], &end, 10);
+                               argcount = 2;
+                       }
                }
                break;
        case '=':
                width = strtoul(arg+1, &end, 10);
                if (*end == ',')
                        name_width = strtoul(end+1, &end, 10);
+               if (*end == ',')
+                       count = strtoul(end+1, &end, 10);
        }
 
        /* Important! This checks all the error cases! */
@@ -3261,6 +3284,7 @@ static int stat_opt(struct diff_options *options, const char **av)
        options->output_format |= DIFF_FORMAT_DIFFSTAT;
        options->stat_name_width = name_width;
        options->stat_width = width;
+       options->stat_count = count;
        return argcount;
 }
 
@@ -3325,7 +3349,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (!strcmp(arg, "-s"))
                options->output_format |= DIFF_FORMAT_NO_OUTPUT;
        else if (!prefixcmp(arg, "--stat"))
-               /* --stat, --stat-width, or --stat-name-width */
+               /* --stat, --stat-width, --stat-name-width, or --stat-count */
                return stat_opt(options, av);
 
        /* renames options */
@@ -3361,6 +3385,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        }
 
        /* xdiff options */
+       else if (!strcmp(arg, "--minimal"))
+               DIFF_XDL_SET(options, NEED_MINIMAL);
+       else if (!strcmp(arg, "--no-minimal"))
+               DIFF_XDL_CLR(options, NEED_MINIMAL);
        else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space"))
                DIFF_XDL_SET(options, IGNORE_WHITESPACE);
        else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change"))
@@ -3369,6 +3397,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
        else if (!strcmp(arg, "--patience"))
                DIFF_XDL_SET(options, PATIENCE_DIFF);
+       else if (!strcmp(arg, "--histogram"))
+               DIFF_XDL_SET(options, HISTOGRAM_DIFF);
 
        /* flags options */
        else if (!strcmp(arg, "--binary")) {
@@ -3386,24 +3416,21 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (!strcmp(arg, "--follow"))
                DIFF_OPT_SET(options, FOLLOW_RENAMES);
        else if (!strcmp(arg, "--color"))
-               DIFF_OPT_SET(options, COLOR_DIFF);
+               options->use_color = 1;
        else if (!prefixcmp(arg, "--color=")) {
-               int value = git_config_colorbool(NULL, arg+8, -1);
-               if (value == 0)
-                       DIFF_OPT_CLR(options, COLOR_DIFF);
-               else if (value > 0)
-                       DIFF_OPT_SET(options, COLOR_DIFF);
-               else
+               int value = git_config_colorbool(NULL, arg+8);
+               if (value < 0)
                        return error("option `color' expects \"always\", \"auto\", or \"never\"");
+               options->use_color = value;
        }
        else if (!strcmp(arg, "--no-color"))
-               DIFF_OPT_CLR(options, COLOR_DIFF);
+               options->use_color = 0;
        else if (!strcmp(arg, "--color-words")) {
-               DIFF_OPT_SET(options, COLOR_DIFF);
+               options->use_color = 1;
                options->word_diff = DIFF_WORDS_COLOR;
        }
        else if (!prefixcmp(arg, "--color-words=")) {
-               DIFF_OPT_SET(options, COLOR_DIFF);
+               options->use_color = 1;
                options->word_diff = DIFF_WORDS_COLOR;
                options->word_regex = arg + 14;
        }
@@ -3416,7 +3443,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                if (!strcmp(type, "plain"))
                        options->word_diff = DIFF_WORDS_PLAIN;
                else if (!strcmp(type, "color")) {
-                       DIFF_OPT_SET(options, COLOR_DIFF);
+                       options->use_color = 1;
                        options->word_diff = DIFF_WORDS_COLOR;
                }
                else if (!strcmp(type, "porcelain"))
diff --git a/diff.h b/diff.h
index 6d303c1d50aa799ec90aeb64b2a1b1f55811dd46..8c66b59517305546d3e2f66fca652284468e1da1 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -58,7 +58,7 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data)
 #define DIFF_OPT_SILENT_ON_REMOVE    (1 <<  5)
 #define DIFF_OPT_FIND_COPIES_HARDER  (1 <<  6)
 #define DIFF_OPT_FOLLOW_RENAMES      (1 <<  7)
-#define DIFF_OPT_COLOR_DIFF          (1 <<  8)
+/* (1 <<  8) unused */
 /* (1 <<  9) unused */
 #define DIFF_OPT_HAS_CHANGES         (1 << 10)
 #define DIFF_OPT_QUICK               (1 << 11)
@@ -101,6 +101,7 @@ struct diff_options {
        const char *single_follow;
        const char *a_prefix, *b_prefix;
        unsigned flags;
+       int use_color;
        int context;
        int interhunkcontext;
        int break_opt;
@@ -125,6 +126,7 @@ struct diff_options {
 
        int stat_width;
        int stat_name_width;
+       int stat_count;
        const char *word_regex;
        enum diff_words_type word_diff;
 
@@ -159,7 +161,7 @@ enum color_diff {
 };
 const char *diff_get_color(int diff_use_color, enum color_diff ix);
 #define diff_get_color_opt(o, ix) \
-       diff_get_color(DIFF_OPT_TST((o), COLOR_DIFF), ix)
+       diff_get_color((o)->use_color, ix)
 
 
 extern const char mime_boundary_leader[];
index ea03b9107eb2e85dd4a4c5ad93ac08dfe7e1c9f0..c3760cfefd5dd123481b869eac3081b6e7d93201 100644 (file)
@@ -6,6 +6,7 @@
 #include "diff.h"
 #include "diffcore.h"
 #include "xdiff-interface.h"
+#include "kwset.h"
 
 struct diffgrep_cb {
        regex_t *regexp;
@@ -146,7 +147,7 @@ static void diffcore_pickaxe_grep(struct diff_options *o)
 
 static unsigned int contains(struct diff_filespec *one,
                             const char *needle, unsigned long len,
-                            regex_t *regexp)
+                            regex_t *regexp, kwset_t kws)
 {
        unsigned int cnt;
        unsigned long sz;
@@ -175,9 +176,12 @@ static unsigned int contains(struct diff_filespec *one,
 
        } else { /* Classic exact string match */
                while (sz) {
-                       const char *found = memmem(data, sz, needle, len);
-                       if (!found)
+                       size_t offset = kwsexec(kws, data, sz, NULL);
+                       const char *found;
+                       if (offset == -1)
                                break;
+                       else
+                               found = data + offset;
                        sz -= found - data + len;
                        data = found + len;
                        cnt++;
@@ -195,6 +199,7 @@ static void diffcore_pickaxe_count(struct diff_options *o)
        unsigned long len = strlen(needle);
        int i, has_changes;
        regex_t regex, *regexp = NULL;
+       kwset_t kws = NULL;
        struct diff_queue_struct outq;
        DIFF_QUEUE_CLEAR(&outq);
 
@@ -209,6 +214,10 @@ static void diffcore_pickaxe_count(struct diff_options *o)
                        die("invalid pickaxe regex: %s", errbuf);
                }
                regexp = &regex;
+       } else {
+               kws = kwsalloc(NULL);
+               kwsincr(kws, needle, len);
+               kwsprep(kws);
        }
 
        if (opts & DIFF_PICKAXE_ALL) {
@@ -219,16 +228,16 @@ static void diffcore_pickaxe_count(struct diff_options *o)
                                if (!DIFF_FILE_VALID(p->two))
                                        continue; /* ignore unmerged */
                                /* created */
-                               if (contains(p->two, needle, len, regexp))
+                               if (contains(p->two, needle, len, regexp, kws))
                                        has_changes++;
                        }
                        else if (!DIFF_FILE_VALID(p->two)) {
-                               if (contains(p->one, needle, len, regexp))
+                               if (contains(p->one, needle, len, regexp, kws))
                                        has_changes++;
                        }
                        else if (!diff_unmodified_pair(p) &&
-                                contains(p->one, needle, len, regexp) !=
-                                contains(p->two, needle, len, regexp))
+                                contains(p->one, needle, len, regexp, kws) !=
+                                contains(p->two, needle, len, regexp, kws))
                                has_changes++;
                }
                if (has_changes)
@@ -251,16 +260,17 @@ static void diffcore_pickaxe_count(struct diff_options *o)
                                if (!DIFF_FILE_VALID(p->two))
                                        ; /* ignore unmerged */
                                /* created */
-                               else if (contains(p->two, needle, len, regexp))
+                               else if (contains(p->two, needle, len, regexp,
+                                                 kws))
                                        has_changes = 1;
                        }
                        else if (!DIFF_FILE_VALID(p->two)) {
-                               if (contains(p->one, needle, len, regexp))
+                               if (contains(p->one, needle, len, regexp, kws))
                                        has_changes = 1;
                        }
                        else if (!diff_unmodified_pair(p) &&
-                                contains(p->one, needle, len, regexp) !=
-                                contains(p->two, needle, len, regexp))
+                                contains(p->one, needle, len, regexp, kws) !=
+                                contains(p->two, needle, len, regexp, kws))
                                has_changes = 1;
 
                        if (has_changes)
@@ -271,6 +281,8 @@ static void diffcore_pickaxe_count(struct diff_options *o)
 
        if (opts & DIFF_PICKAXE_REGEX)
                regfree(&regex);
+       else
+               kwsfree(kws);
 
        free(q->queue);
        *q = outq;
index b8f1fdecf4d9e5e8f8e834eb55a374f1af8352fd..8f32b824cdc1bac1f094d743f3bee9f32890edca 100644 (file)
@@ -45,7 +45,7 @@ struct diff_filespec {
        unsigned dirty_submodule : 2;  /* For submodules: its work tree is dirty */
 #define DIRTY_SUBMODULE_UNTRACKED 1
 #define DIRTY_SUBMODULE_MODIFIED  2
-
+       unsigned has_more_entries : 1; /* only appear in combined diff */
        struct userdiff_driver *driver;
        /* data should be considered "binary"; -1 means "don't know yet" */
        int is_binary;
diff --git a/entry.c b/entry.c
index b017167f2015623fb9c721e91d0a940abd1d5196..852fea13955475c1e2fda9cfc25a63a54a1f61c7 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "blob.h"
 #include "dir.h"
+#include "streaming.h"
 
 static void create_directories(const char *path, int path_len,
                               const struct checkout *state)
@@ -91,6 +92,91 @@ static void *read_blob_entry(struct cache_entry *ce, unsigned long *size)
        return NULL;
 }
 
+static int open_output_fd(char *path, struct cache_entry *ce, int to_tempfile)
+{
+       int symlink = (ce->ce_mode & S_IFMT) != S_IFREG;
+       if (to_tempfile) {
+               strcpy(path, symlink
+                      ? ".merge_link_XXXXXX" : ".merge_file_XXXXXX");
+               return mkstemp(path);
+       } else {
+               return create_file(path, !symlink ? ce->ce_mode : 0666);
+       }
+}
+
+static int fstat_output(int fd, const struct checkout *state, struct stat *st)
+{
+       /* use fstat() only when path == ce->name */
+       if (fstat_is_reliable() &&
+           state->refresh_cache && !state->base_dir_len) {
+               fstat(fd, st);
+               return 1;
+       }
+       return 0;
+}
+
+static int streaming_write_entry(struct cache_entry *ce, char *path,
+                                struct stream_filter *filter,
+                                const struct checkout *state, int to_tempfile,
+                                int *fstat_done, struct stat *statbuf)
+{
+       struct git_istream *st;
+       enum object_type type;
+       unsigned long sz;
+       int result = -1;
+       ssize_t kept = 0;
+       int fd = -1;
+
+       st = open_istream(ce->sha1, &type, &sz, filter);
+       if (!st)
+               return -1;
+       if (type != OBJ_BLOB)
+               goto close_and_exit;
+
+       fd = open_output_fd(path, ce, to_tempfile);
+       if (fd < 0)
+               goto close_and_exit;
+
+       for (;;) {
+               char buf[1024 * 16];
+               ssize_t wrote, holeto;
+               ssize_t readlen = read_istream(st, buf, sizeof(buf));
+
+               if (!readlen)
+                       break;
+               if (sizeof(buf) == readlen) {
+                       for (holeto = 0; holeto < readlen; holeto++)
+                               if (buf[holeto])
+                                       break;
+                       if (readlen == holeto) {
+                               kept += holeto;
+                               continue;
+                       }
+               }
+
+               if (kept && lseek(fd, kept, SEEK_CUR) == (off_t) -1)
+                       goto close_and_exit;
+               else
+                       kept = 0;
+               wrote = write_in_full(fd, buf, readlen);
+
+               if (wrote != readlen)
+                       goto close_and_exit;
+       }
+       if (kept && (lseek(fd, kept - 1, SEEK_CUR) == (off_t) -1 ||
+                    write(fd, "", 1) != 1))
+               goto close_and_exit;
+       *fstat_done = fstat_output(fd, state, statbuf);
+
+close_and_exit:
+       close_istream(st);
+       if (0 <= fd)
+               result = close(fd);
+       if (result && 0 <= fd)
+               unlink(path);
+       return result;
+}
+
 static int write_entry(struct cache_entry *ce, char *path, const struct checkout *state, int to_tempfile)
 {
        unsigned int ce_mode_s_ifmt = ce->ce_mode & S_IFMT;
@@ -101,6 +187,15 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
        size_t wrote, newsize = 0;
        struct stat st;
 
+       if (ce_mode_s_ifmt == S_IFREG) {
+               struct stream_filter *filter = get_stream_filter(path, ce->sha1);
+               if (filter &&
+                   !streaming_write_entry(ce, path, filter,
+                                          state, to_tempfile,
+                                          &fstat_done, &st))
+                       goto finish;
+       }
+
        switch (ce_mode_s_ifmt) {
        case S_IFREG:
        case S_IFLNK:
@@ -128,17 +223,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
                        size = newsize;
                }
 
-               if (to_tempfile) {
-                       if (ce_mode_s_ifmt == S_IFREG)
-                               strcpy(path, ".merge_file_XXXXXX");
-                       else
-                               strcpy(path, ".merge_link_XXXXXX");
-                       fd = mkstemp(path);
-               } else if (ce_mode_s_ifmt == S_IFREG) {
-                       fd = create_file(path, ce->ce_mode);
-               } else {
-                       fd = create_file(path, 0666);
-               }
+               fd = open_output_fd(path, ce, to_tempfile);
                if (fd < 0) {
                        free(new);
                        return error("unable to create file %s (%s)",
@@ -146,12 +231,8 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
                }
 
                wrote = write_in_full(fd, new, size);
-               /* use fstat() only when path == ce->name */
-               if (fstat_is_reliable() &&
-                   state->refresh_cache && !to_tempfile && !state->base_dir_len) {
-                       fstat(fd, &st);
-                       fstat_done = 1;
-               }
+               if (!to_tempfile)
+                       fstat_done = fstat_output(fd, state, &st);
                close(fd);
                free(new);
                if (wrote != size)
@@ -167,6 +248,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
                return error("unknown file mode for %s in index", path);
        }
 
+finish:
        if (state->refresh_cache) {
                if (!fstat_done)
                        lstat(ce->name, &st);
index 94d58fd24413ec1d1a5769029bd5149b609f0d4e..e96edcfebc4174a5166c11e7a511ea792e7a2639 100644 (file)
@@ -8,6 +8,7 @@
  * are.
  */
 #include "cache.h"
+#include "refs.h"
 
 char git_default_email[MAX_GITNAME];
 char git_default_name[MAX_GITNAME];
@@ -36,6 +37,7 @@ size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE;
 size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT;
 size_t delta_base_cache_limit = 16 * 1024 * 1024;
 unsigned long big_file_threshold = 512 * 1024 * 1024;
+const char *log_pack_access;
 const char *pager_program;
 int pager_use_color = 1;
 const char *editor_program;
@@ -65,6 +67,9 @@ int core_preload_index = 0;
 char *git_work_tree_cfg;
 static char *work_tree;
 
+static const char *namespace;
+static size_t namespace_len;
+
 static const char *git_dir;
 static char *git_object_dir, *git_index_file, *git_graft_file;
 
@@ -86,12 +91,33 @@ const char * const local_repo_env[LOCAL_REPO_ENV_SIZE + 1] = {
        NULL
 };
 
+static char *expand_namespace(const char *raw_namespace)
+{
+       struct strbuf buf = STRBUF_INIT;
+       struct strbuf **components, **c;
+
+       if (!raw_namespace || !*raw_namespace)
+               return xstrdup("");
+
+       strbuf_addstr(&buf, raw_namespace);
+       components = strbuf_split(&buf, '/');
+       strbuf_reset(&buf);
+       for (c = components; *c; c++)
+               if (strcmp((*c)->buf, "/") != 0)
+                       strbuf_addf(&buf, "refs/namespaces/%s", (*c)->buf);
+       strbuf_list_free(components);
+       if (check_ref_format(buf.buf) != CHECK_REF_FORMAT_OK)
+               die("bad git namespace path \"%s\"", raw_namespace);
+       strbuf_addch(&buf, '/');
+       return strbuf_detach(&buf, NULL);
+}
+
 static void setup_git_env(void)
 {
        git_dir = getenv(GIT_DIR_ENVIRONMENT);
        git_dir = git_dir ? xstrdup(git_dir) : NULL;
        if (!git_dir) {
-               git_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT);
+               git_dir = read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT);
                git_dir = git_dir ? xstrdup(git_dir) : NULL;
        }
        if (!git_dir)
@@ -111,6 +137,8 @@ static void setup_git_env(void)
                git_graft_file = git_pathdup("info/grafts");
        if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
                read_replace_refs = 0;
+       namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));
+       namespace_len = strlen(namespace);
 }
 
 int is_bare_repository(void)
@@ -131,6 +159,20 @@ const char *get_git_dir(void)
        return git_dir;
 }
 
+const char *get_git_namespace(void)
+{
+       if (!namespace)
+               setup_git_env();
+       return namespace;
+}
+
+const char *strip_namespace(const char *namespaced_ref)
+{
+       if (prefixcmp(namespaced_ref, get_git_namespace()) != 0)
+               return NULL;
+       return namespaced_ref + namespace_len;
+}
+
 static int git_work_tree_initialized;
 
 /*
index 78d978684da2e2b79340c138e1b6300100df1221..742e7da6b8b58dd0803d89c6ad6c59589274a31b 100644 (file)
@@ -170,6 +170,11 @@ Format of STDIN stream:
 #define DEPTH_BITS 13
 #define MAX_DEPTH ((1<<DEPTH_BITS)-1)
 
+/*
+ * We abuse the setuid bit on directories to mean "do not delta".
+ */
+#define NO_DELTA S_ISUID
+
 struct object_entry {
        struct pack_idx_entry idx;
        struct object_entry *next;
@@ -284,6 +289,7 @@ static uintmax_t marks_set_count;
 static uintmax_t object_count_by_type[1 << TYPE_BITS];
 static uintmax_t duplicate_count_by_type[1 << TYPE_BITS];
 static uintmax_t delta_count_by_type[1 << TYPE_BITS];
+static uintmax_t delta_count_attempts_by_type[1 << TYPE_BITS];
 static unsigned long object_count;
 static unsigned long branch_count;
 static unsigned long branch_load_count;
@@ -304,6 +310,7 @@ static unsigned int atom_cnt;
 static struct atom_str **atom_table;
 
 /* The .pack file being generated */
+static struct pack_idx_option pack_idx_opts;
 static unsigned int pack_id;
 static struct sha1file *pack_file;
 static struct packed_git *pack_data;
@@ -354,6 +361,7 @@ static unsigned int cmd_save = 100;
 static uintmax_t next_mark;
 static struct strbuf new_data = STRBUF_INIT;
 static int seen_data_command;
+static int require_explicit_termination;
 
 /* Signal handling */
 static volatile sig_atomic_t checkpoint_requested;
@@ -896,7 +904,7 @@ static const char *create_index(void)
        if (c != last)
                die("internal consistency error creating the index");
 
-       tmpfile = write_idx_file(NULL, idx, object_count, pack_data->sha1);
+       tmpfile = write_idx_file(NULL, idx, object_count, &pack_idx_opts, pack_data->sha1);
        free(idx);
        return tmpfile;
 }
@@ -1017,7 +1025,7 @@ static int store_object(
        unsigned char sha1[20];
        unsigned long hdrlen, deltalen;
        git_SHA_CTX c;
-       z_stream s;
+       git_zstream s;
 
        hdrlen = sprintf((char *)hdr,"%s %lu", typename(type),
                (unsigned long)dat->len) + 1;
@@ -1043,6 +1051,7 @@ static int store_object(
        }
 
        if (last && last->data.buf && last->depth < max_depth && dat->len > 20) {
+               delta_count_attempts_by_type[type]++;
                delta = diff_delta(last->data.buf, last->data.len,
                        dat->buf, dat->len,
                        &deltalen, dat->len - 20);
@@ -1050,7 +1059,7 @@ static int store_object(
                delta = NULL;
 
        memset(&s, 0, sizeof(s));
-       deflateInit(&s, pack_compression_level);
+       git_deflate_init(&s, pack_compression_level);
        if (delta) {
                s.next_in = delta;
                s.avail_in = deltalen;
@@ -1058,11 +1067,11 @@ static int store_object(
                s.next_in = (void *)dat->buf;
                s.avail_in = dat->len;
        }
-       s.avail_out = deflateBound(&s, s.avail_in);
+       s.avail_out = git_deflate_bound(&s, s.avail_in);
        s.next_out = out = xmalloc(s.avail_out);
-       while (deflate(&s, Z_FINISH) == Z_OK)
-               /* nothing */;
-       deflateEnd(&s);
+       while (git_deflate(&s, Z_FINISH) == Z_OK)
+               ; /* nothing */
+       git_deflate_end(&s);
 
        /* Determine if we should auto-checkpoint. */
        if ((max_packsize && (pack_size + 60 + s.total_out) > max_packsize)
@@ -1078,14 +1087,14 @@ static int store_object(
                        delta = NULL;
 
                        memset(&s, 0, sizeof(s));
-                       deflateInit(&s, pack_compression_level);
+                       git_deflate_init(&s, pack_compression_level);
                        s.next_in = (void *)dat->buf;
                        s.avail_in = dat->len;
-                       s.avail_out = deflateBound(&s, s.avail_in);
+                       s.avail_out = git_deflate_bound(&s, s.avail_in);
                        s.next_out = out = xrealloc(out, s.avail_out);
-                       while (deflate(&s, Z_FINISH) == Z_OK)
-                               /* nothing */;
-                       deflateEnd(&s);
+                       while (git_deflate(&s, Z_FINISH) == Z_OK)
+                               ; /* nothing */
+                       git_deflate_end(&s);
                }
        }
 
@@ -1163,7 +1172,7 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
        off_t offset;
        git_SHA_CTX c;
        git_SHA_CTX pack_file_ctx;
-       z_stream s;
+       git_zstream s;
        int status = Z_OK;
 
        /* Determine if we should auto-checkpoint. */
@@ -1187,7 +1196,7 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
        crc32_begin(pack_file);
 
        memset(&s, 0, sizeof(s));
-       deflateInit(&s, pack_compression_level);
+       git_deflate_init(&s, pack_compression_level);
 
        hdrlen = encode_in_pack_object_header(OBJ_BLOB, len, out_buf);
        if (out_sz <= hdrlen)
@@ -1209,7 +1218,7 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
                        len -= n;
                }
 
-               status = deflate(&s, len ? 0 : Z_FINISH);
+               status = git_deflate(&s, len ? 0 : Z_FINISH);
 
                if (!s.avail_out || status == Z_STREAM_END) {
                        size_t n = s.next_out - out_buf;
@@ -1228,7 +1237,7 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
                        die("unexpected deflate failure: %d", status);
                }
        }
-       deflateEnd(&s);
+       git_deflate_end(&s);
        git_SHA1_Final(sha1, &c);
 
        if (sha1out)
@@ -1414,8 +1423,9 @@ static void mktree(struct tree_content *t, int v, struct strbuf *b)
                struct tree_entry *e = t->entries[i];
                if (!e->versions[v].mode)
                        continue;
-               strbuf_addf(b, "%o %s%c", (unsigned int)e->versions[v].mode,
-                                       e->name->str_dat, '\0');
+               strbuf_addf(b, "%o %s%c",
+                       (unsigned int)(e->versions[v].mode & ~NO_DELTA),
+                       e->name->str_dat, '\0');
                strbuf_add(b, e->versions[v].sha1, 20);
        }
 }
@@ -1425,7 +1435,7 @@ static void store_tree(struct tree_entry *root)
        struct tree_content *t = root->tree;
        unsigned int i, j, del;
        struct last_object lo = { STRBUF_INIT, 0, 0, /* no_swap */ 1 };
-       struct object_entry *le;
+       struct object_entry *le = NULL;
 
        if (!is_null_sha1(root->versions[1].sha1))
                return;
@@ -1435,7 +1445,8 @@ static void store_tree(struct tree_entry *root)
                        store_tree(t->entries[i]);
        }
 
-       le = find_object(root->versions[0].sha1);
+       if (!(root->versions[0].mode & NO_DELTA))
+               le = find_object(root->versions[0].sha1);
        if (S_ISDIR(root->versions[0].mode) && le && le->pack_id == pack_id) {
                mktree(t, 0, &old_tree);
                lo.data = old_tree;
@@ -1469,6 +1480,7 @@ static void tree_content_replace(
 {
        if (!S_ISDIR(mode))
                die("Root cannot be a non-directory");
+       hashclr(root->versions[0].sha1);
        hashcpy(root->versions[1].sha1, sha1);
        if (root->tree)
                release_tree_content_recursive(root->tree);
@@ -1513,6 +1525,23 @@ static int tree_content_set(
                                if (e->tree)
                                        release_tree_content_recursive(e->tree);
                                e->tree = subtree;
+
+                               /*
+                                * We need to leave e->versions[0].sha1 alone
+                                * to avoid modifying the preimage tree used
+                                * when writing out the parent directory.
+                                * But after replacing the subdir with a
+                                * completely different one, it's not a good
+                                * delta base any more, and besides, we've
+                                * thrown away the tree entries needed to
+                                * make a delta against it.
+                                *
+                                * So let's just explicitly disable deltas
+                                * for the subtree.
+                                */
+                               if (S_ISDIR(e->versions[0].mode))
+                                       e->versions[0].mode |= NO_DELTA;
+
                                hashclr(root->versions[1].sha1);
                                return 1;
                        }
@@ -1967,32 +1996,41 @@ static int validate_raw_date(const char *src, char *result, int maxlen)
 
 static char *parse_ident(const char *buf)
 {
-       const char *gt;
+       const char *ltgt;
        size_t name_len;
        char *ident;
 
-       gt = strrchr(buf, '>');
-       if (!gt)
+       /* ensure there is a space delimiter even if there is no name */
+       if (*buf == '<')
+               --buf;
+
+       ltgt = buf + strcspn(buf, "<>");
+       if (*ltgt != '<')
+               die("Missing < in ident string: %s", buf);
+       if (ltgt != buf && ltgt[-1] != ' ')
+               die("Missing space before < in ident string: %s", buf);
+       ltgt = ltgt + 1 + strcspn(ltgt + 1, "<>");
+       if (*ltgt != '>')
                die("Missing > in ident string: %s", buf);
-       gt++;
-       if (*gt != ' ')
+       ltgt++;
+       if (*ltgt != ' ')
                die("Missing space after > in ident string: %s", buf);
-       gt++;
-       name_len = gt - buf;
+       ltgt++;
+       name_len = ltgt - buf;
        ident = xmalloc(name_len + 24);
        strncpy(ident, buf, name_len);
 
        switch (whenspec) {
        case WHENSPEC_RAW:
-               if (validate_raw_date(gt, ident + name_len, 24) < 0)
-                       die("Invalid raw date \"%s\" in ident: %s", gt, buf);
+               if (validate_raw_date(ltgt, ident + name_len, 24) < 0)
+                       die("Invalid raw date \"%s\" in ident: %s", ltgt, buf);
                break;
        case WHENSPEC_RFC2822:
-               if (parse_date(gt, ident + name_len, 24) < 0)
-                       die("Invalid rfc2822 date \"%s\" in ident: %s", gt, buf);
+               if (parse_date(ltgt, ident + name_len, 24) < 0)
+                       die("Invalid rfc2822 date \"%s\" in ident: %s", ltgt, buf);
                break;
        case WHENSPEC_NOW:
-               if (strcmp("now", gt))
+               if (strcmp("now", ltgt))
                        die("Date in ident must be 'now': %s", buf);
                datestamp(ident + name_len, 24);
                break;
@@ -2688,13 +2726,13 @@ static void parse_new_tag(void)
                type = oe->type;
                hashcpy(sha1, oe->idx.sha1);
        } else if (!get_sha1(from, sha1)) {
-               unsigned long size;
-               char *buf;
-
-               buf = read_sha1_file(sha1, &type, &size);
-               if (!buf || size < 46)
-                       die("Not a valid commit: %s", from);
-               free(buf);
+               struct object_entry *oe = find_object(sha1);
+               if (!oe) {
+                       type = sha1_object_info(sha1, NULL);
+                       if (type < 0)
+                               die("Not a valid object: %s", from);
+               } else
+                       type = oe->type;
        } else
                die("Invalid ref name or SHA1 expression: %s", from);
        read_next_command();
@@ -2798,7 +2836,12 @@ static void cat_blob(struct object_entry *oe, unsigned char sha1[20])
        strbuf_release(&line);
        cat_blob_write(buf, size);
        cat_blob_write("\n", 1);
-       free(buf);
+       if (oe && oe->pack_id == pack_id) {
+               last_blob.offset = oe->idx.offset;
+               strbuf_attach(&last_blob.data, buf, size, size);
+               last_blob.depth = oe->depth;
+       } else
+               free(buf);
 }
 
 static void parse_cat_blob(void)
@@ -2927,7 +2970,7 @@ static void print_ls(int mode, const unsigned char *sha1, const char *path)
                /* mode SP type SP object_name TAB path LF */
                strbuf_reset(&line);
                strbuf_addf(&line, "%06o %s %s\t",
-                               mode, type, sha1_to_hex(sha1));
+                               mode & ~NO_DELTA, type, sha1_to_hex(sha1));
                quote_c_style(path, &line, NULL, 0);
                strbuf_addch(&line, '\n');
        }
@@ -3139,6 +3182,8 @@ static int parse_one_feature(const char *feature, int from_stream)
                relative_marks_paths = 1;
        } else if (!strcmp(feature, "no-relative-marks")) {
                relative_marks_paths = 0;
+       } else if (!strcmp(feature, "done")) {
+               require_explicit_termination = 1;
        } else if (!strcmp(feature, "force")) {
                force_update = 1;
        } else if (!strcmp(feature, "notes") || !strcmp(feature, "ls")) {
@@ -3195,10 +3240,10 @@ static int git_pack_config(const char *k, const char *v, void *cb)
                return 0;
        }
        if (!strcmp(k, "pack.indexversion")) {
-               pack_idx_default_version = git_config_int(k, v);
-               if (pack_idx_default_version > 2)
+               pack_idx_opts.version = git_config_int(k, v);
+               if (pack_idx_opts.version > 2)
                        die("bad pack.indexversion=%"PRIu32,
-                           pack_idx_default_version);
+                           pack_idx_opts.version);
                return 0;
        }
        if (!strcmp(k, "pack.packsizelimit")) {
@@ -3252,6 +3297,7 @@ int main(int argc, const char **argv)
                usage(fast_import_usage);
 
        setup_git_directory();
+       reset_pack_idx_option(&pack_idx_opts);
        git_config(git_pack_config, NULL);
        if (!pack_compression_seen && core_compression_seen)
                pack_compression_level = core_compression_level;
@@ -3288,6 +3334,8 @@ int main(int argc, const char **argv)
                        parse_reset_branch();
                else if (!strcmp("checkpoint", command_buf.buf))
                        parse_checkpoint();
+               else if (!strcmp("done", command_buf.buf))
+                       break;
                else if (!prefixcmp(command_buf.buf, "progress "))
                        parse_progress();
                else if (!prefixcmp(command_buf.buf, "feature "))
@@ -3307,6 +3355,9 @@ int main(int argc, const char **argv)
        if (!seen_data_command)
                parse_argv();
 
+       if (require_explicit_termination && feof(stdin))
+               die("stream ends early");
+
        end_packfile();
 
        dump_branches();
@@ -3328,10 +3379,10 @@ int main(int argc, const char **argv)
                fprintf(stderr, "---------------------------------------------------------------------\n");
                fprintf(stderr, "Alloc'd objects: %10" PRIuMAX "\n", alloc_count);
                fprintf(stderr, "Total objects:   %10" PRIuMAX " (%10" PRIuMAX " duplicates                  )\n", total_count, duplicate_count);
-               fprintf(stderr, "      blobs  :   %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB], delta_count_by_type[OBJ_BLOB]);
-               fprintf(stderr, "      trees  :   %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE], delta_count_by_type[OBJ_TREE]);
-               fprintf(stderr, "      commits:   %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT], delta_count_by_type[OBJ_COMMIT]);
-               fprintf(stderr, "      tags   :   %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG], delta_count_by_type[OBJ_TAG]);
+               fprintf(stderr, "      blobs  :   %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB], delta_count_by_type[OBJ_BLOB], delta_count_attempts_by_type[OBJ_BLOB]);
+               fprintf(stderr, "      trees  :   %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE], delta_count_by_type[OBJ_TREE], delta_count_attempts_by_type[OBJ_TREE]);
+               fprintf(stderr, "      commits:   %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT], delta_count_by_type[OBJ_COMMIT], delta_count_attempts_by_type[OBJ_COMMIT]);
+               fprintf(stderr, "      tags   :   %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG], delta_count_by_type[OBJ_TAG], delta_count_attempts_by_type[OBJ_TAG]);
                fprintf(stderr, "Total branches:  %10lu (%10lu loads     )\n", branch_count, branch_load_count);
                fprintf(stderr, "      marks:     %10" PRIuMAX " (%10" PRIuMAX " unique    )\n", (((uintmax_t)1) << marks->shift) * 1024, marks_set_count);
                fprintf(stderr, "      atoms:     %10u\n", atom_cnt);
diff --git a/fsck.c b/fsck.c
index 60bd4bbf6a2206e2b1991093ea5d84be7f338f73..6c855f84f01c19678399d85181da1094bd61b371 100644 (file)
--- a/fsck.c
+++ b/fsck.c
@@ -224,13 +224,15 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func)
 
 static int fsck_ident(char **ident, struct object *obj, fsck_error error_func)
 {
-       if (**ident == '<' || **ident == '\n')
-               return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before email");
-       *ident += strcspn(*ident, "<\n");
-       if ((*ident)[-1] != ' ')
+       if (**ident == '<')
                return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before email");
+       *ident += strcspn(*ident, "<>\n");
+       if (**ident == '>')
+               return error_func(obj, FSCK_ERROR, "invalid author/committer line - bad name");
        if (**ident != '<')
                return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing email");
+       if ((*ident)[-1] != ' ')
+               return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before email");
        (*ident)++;
        *ident += strcspn(*ident, "<>\n");
        if (**ident != '>')
index 3ef4861d04ed5fed14d31b92e047a03cf5362970..1093ef4ad6b9793fb829403c41391a09a07e57a2 100755 (executable)
@@ -15,8 +15,8 @@ do
      sed -n '
      /^NAME/,/git-'"$cmd"'/H
      ${
-            x
-            s/.*git-'"$cmd"' - \(.*\)/  {"'"$cmd"'", "\1"},/
+           x
+           s/.*git-'"$cmd"' - \(.*\)/  {"'"$cmd"'", "\1"},/
            p
      }' "Documentation/git-$cmd.txt"
 done
index 6cdd5910db50c96df3d149fba172750cb10c09cb..016b5056eb29fb24d06667c340ad372ed096d4dc 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -22,6 +22,7 @@ whitespace=     pass it through git-apply
 ignore-space-change pass it through git-apply
 ignore-whitespace pass it through git-apply
 directory=      pass it through git-apply
+exclude=        pass it through git-apply
 C=              pass it through git-apply
 p=              pass it through git-apply
 patch-format=   format the patch(es) are in
@@ -37,13 +38,14 @@ rerere-autoupdate update the index with reused conflict resolution if possible
 rebasing*       (internal use for git-rebase)"
 
 . git-sh-setup
+. git-sh-i18n
 prefix=$(git rev-parse --show-prefix)
 set_reflog_action am
 require_work_tree
 cd_to_toplevel
 
 git var GIT_COMMITTER_IDENT >/dev/null ||
-       die "You need to set your committer info first"
+       die "$(gettext "You need to set your committer info first")"
 
 if git rev-parse --verify -q HEAD >/dev/null
 then
@@ -88,8 +90,8 @@ safe_to_abort () {
        then
                return 0
        fi
-       echo >&2 "You seem to have moved HEAD since the last 'am' failure."
-       echo >&2 "Not rewinding to ORIG_HEAD"
+               gettextln "You seem to have moved HEAD since the last 'am' failure.
+Not rewinding to ORIG_HEAD" >&2
        return 1
 }
 
@@ -98,9 +100,9 @@ stop_here_user_resolve () {
            printf '%s\n' "$resolvemsg"
            stop_here $1
     fi
-    echo "When you have resolved this problem run \"$cmdline --resolved\"."
-    echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"."
-    echo "To restore the original branch and stop patching run \"$cmdline --abort\"."
+    eval_gettextln "When you have resolved this problem run \"\$cmdline --resolved\".
+If you would prefer to skip this patch, instead run \"\$cmdline --skip\".
+To restore the original branch and stop patching run \"\$cmdline --abort\"."
 
     stop_here $1
 }
@@ -114,7 +116,7 @@ go_next () {
 
 cannot_fallback () {
        echo "$1"
-       echo "Cannot fall back to three-way merge."
+       gettextln "Cannot fall back to three-way merge."
        exit 1
 }
 
@@ -129,7 +131,7 @@ fall_back_3way () {
        "$dotest/patch" &&
     GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
     git write-tree >"$dotest/patch-merge-base+" ||
-    cannot_fallback "Repository lacks necessary blobs to fall back on 3-way merge."
+    cannot_fallback "$(gettext "Repository lacks necessary blobs to fall back on 3-way merge.")"
 
     say Using index info to reconstruct a base tree...
     if GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
@@ -138,8 +140,8 @@ fall_back_3way () {
        mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base"
        mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index"
     else
-        cannot_fallback "Did you hand edit your patch?
-It does not apply to blobs recorded in its index."
+       cannot_fallback "$(gettext "Did you hand edit your patch?
+It does not apply to blobs recorded in its index.")"
     fi
 
     test -f "$dotest/patch-merge-index" &&
@@ -147,7 +149,7 @@ It does not apply to blobs recorded in its index."
     orig_tree=$(cat "$dotest/patch-merge-base") &&
     rm -fr "$dotest"/patch-merge-* || exit 1
 
-    say Falling back to patching base and 3-way merge...
+    say "$(gettext "Falling back to patching base and 3-way merge...")"
 
     # This is not so wrong.  Depending on which base we picked,
     # orig_tree may be wildly different from ours, but his_tree
@@ -192,10 +194,15 @@ check_patch_format () {
                return 0
        fi
 
-       # otherwise, check the first few lines of the first patch to try
-       # to detect its format
+       # otherwise, check the first few non-blank lines of the first
+       # patch to try to detect its format
        {
-               read l1
+               # Start from first line containing non-whitespace
+               l1=
+               while test -z "$l1"
+               do
+                       read l1
+               done
                read l2
                read l3
                case "$l1" in
@@ -254,7 +261,7 @@ split_patches () {
        stgit-series)
                if test $# -ne 1
                then
-                       clean_abort "Only one StGIT patch series can be applied at once"
+                       clean_abort "$(gettext "Only one StGIT patch series can be applied at once")"
                fi
                series_dir=`dirname "$1"`
                series_file="$1"
@@ -288,7 +295,7 @@ split_patches () {
                        perl -ne 'BEGIN { $subject = 0 }
                                if ($subject > 1) { print ; }
                                elsif (/^\s+$/) { next ; }
-                               elsif (/^Author:/) { print s/Author/From/ ; }
+                               elsif (/^Author:/) { s/Author/From/ ; print ;}
                                elsif (/^(From|Date)/) { print ; }
                                elsif ($subject) {
                                        $subject = 2 ;
@@ -305,10 +312,11 @@ split_patches () {
                msgnum=
                ;;
        *)
-               if test -n "$parse_patch" ; then
-                       clean_abort "Patch format $patch_format is not supported."
+               if test -n "$patch_format"
+               then
+                       clean_abort "$(eval_gettext "Patch format \$patch_format is not supported.")"
                else
-                       clean_abort "Patch format detection failed."
+                       clean_abort "$(gettext "Patch format detection failed.")"
                fi
                ;;
        esac
@@ -358,11 +366,11 @@ do
        --rebasing)
                rebasing=t threeway=t keep=t scissors=f no_inbody_headers=t ;;
        -d|--dotest)
-               die "-d option is no longer supported.  Do not use."
+               die "$(gettext "-d option is no longer supported.  Do not use.")"
                ;;
        --resolvemsg)
                shift; resolvemsg=$1 ;;
-       --whitespace|--directory)
+       --whitespace|--directory|--exclude)
                git_apply_opt="$git_apply_opt $(sq "$1=$2")"; shift ;;
        -C|-p)
                git_apply_opt="$git_apply_opt $(sq "$1$2")"; shift ;;
@@ -421,12 +429,12 @@ then
                false
                ;;
        esac ||
-       die "previous rebase directory $dotest still exists but mbox given."
+       die "$(eval_gettext "previous rebase directory \$dotest still exists but mbox given.")"
        resume=yes
 
        case "$skip,$abort" in
        t,t)
-               die "Please make up your mind. --skip or --abort?"
+               die "$(gettext "Please make up your mind. --skip or --abort?")"
                ;;
        t,)
                git rerere clear
@@ -453,7 +461,7 @@ then
 else
        # Make sure we are not given --skip, --resolved, nor --abort
        test "$skip$resolved$abort" = "" ||
-               die "Resolve operation not in progress, we are not resuming."
+               die "$(gettext "Resolve operation not in progress, we are not resuming.")"
 
        # Start afresh.
        mkdir -p "$dotest" || exit
@@ -507,6 +515,8 @@ else
        fi
 fi
 
+git update-index -q --refresh
+
 case "$resolved" in
 '')
        case "$HAS_HEAD" in
@@ -518,7 +528,7 @@ case "$resolved" in
        if test "$files"
        then
                test -n "$HAS_HEAD" && : >"$dotest/dirtyindex"
-               die "Dirty index: cannot apply patches (dirty: $files)"
+               die "$(eval_gettext "Dirty index: cannot apply patches (dirty: \$files)")"
        fi
 esac
 
@@ -607,9 +617,9 @@ do
                        go_next && continue
 
                test -s "$dotest/patch" || {
-                       echo "Patch is empty.  Was it split wrong?"
-                       echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"."
-                       echo "To restore the original branch and stop patching run \"$cmdline --abort\"."
+                       eval_gettextln "Patch is empty.  Was it split wrong?
+If you would prefer to skip this patch, instead run \"\$cmdline --skip\".
+To restore the original branch and stop patching run \"\$cmdline --abort\"."
                        stop_here $this
                }
                rm -f "$dotest/original-commit" "$dotest/author-script"
@@ -644,7 +654,7 @@ do
 
        if test -z "$GIT_AUTHOR_EMAIL"
        then
-               echo "Patch does not have a valid e-mail address."
+               gettextln "Patch does not have a valid e-mail address."
                stop_here $this
        fi
 
@@ -691,15 +701,18 @@ do
        if test "$interactive" = t
        then
            test -t 0 ||
-           die "cannot be interactive without stdin connected to a terminal."
+           die "$(gettext "cannot be interactive without stdin connected to a terminal.")"
            action=again
            while test "$action" = again
            do
-               echo "Commit Body is:"
+               gettextln "Commit Body is:"
                echo "--------------------------"
                cat "$dotest/final-commit"
                echo "--------------------------"
-               printf "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+               # TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
+               # in your translation. The program will only accept English
+               # input at this point.
+               gettext "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
                read reply
                case "$reply" in
                [yY]*) action=yes ;;
@@ -735,7 +748,7 @@ do
                stop_here $this
        fi
 
-       say "Applying: $FIRSTLINE"
+       say "$(eval_gettext "Applying: \$FIRSTLINE")"
 
        case "$resolved" in
        '')
@@ -756,16 +769,16 @@ do
                # working tree.
                resolved=
                git diff-index --quiet --cached HEAD -- && {
-                       echo "No changes - did you forget to use 'git add'?"
-                       echo "If there is nothing left to stage, chances are that something else"
-                       echo "already introduced the same changes; you might want to skip this patch."
+                       gettextln "No changes - did you forget to use 'git add'?
+If there is nothing left to stage, chances are that something else
+already introduced the same changes; you might want to skip this patch."
                        stop_here_user_resolve $this
                }
                unmerged=$(git ls-files -u)
                if test -n "$unmerged"
                then
-                       echo "You still have unmerged paths in your index"
-                       echo "did you forget to use 'git add'?"
+                       gettextln "You still have unmerged paths in your index
+did you forget to use 'git add'?"
                        stop_here_user_resolve $this
                fi
                apply_status=0
@@ -780,7 +793,7 @@ do
                    # Applying the patch to an earlier tree and merging the
                    # result may have produced the same tree as ours.
                    git diff-index --quiet --cached HEAD -- && {
-                       say No changes -- Patch already applied.
+                       say "$(gettext "No changes -- Patch already applied.")"
                        go_next
                        continue
                    }
@@ -790,7 +803,7 @@ do
        fi
        if test $apply_status != 0
        then
-               printf 'Patch failed at %s %s\n' "$msgnum" "$FIRSTLINE"
+               eval_gettextln 'Patch failed at $msgnum $FIRSTLINE'
                stop_here_user_resolve $this
        fi
 
@@ -806,7 +819,7 @@ do
                        GIT_AUTHOR_DATE=
                fi
                parent=$(git rev-parse --verify -q HEAD) ||
-               say >&2 "applying to an empty history"
+               say >&2 "$(gettext "applying to an empty history")"
 
                if test -n "$committer_date_is_author_date"
                then
index 415a8d04ccc4f313eb111a3bbfbf2af2382beebd..2524060475ef369c0b9a4641aa88dc2ad6083da4 100755 (executable)
@@ -2,43 +2,56 @@
 
 USAGE='[help|start|bad|good|skip|next|reset|visualize|replay|log|run]'
 LONG_USAGE='git bisect help
-        print this long help message.
-git bisect start [<bad> [<good>...]] [--] [<pathspec>...]
-        reset bisect state and start bisection.
+       print this long help message.
+git bisect start [--no-checkout] [<bad> [<good>...]] [--] [<pathspec>...]
+       reset bisect state and start bisection.
 git bisect bad [<rev>]
-        mark <rev> a known-bad revision.
+       mark <rev> a known-bad revision.
 git bisect good [<rev>...]
-        mark <rev>... known-good revisions.
+       mark <rev>... known-good revisions.
 git bisect skip [(<rev>|<range>)...]
-        mark <rev>... untestable revisions.
+       mark <rev>... untestable revisions.
 git bisect next
-        find next bisection to test and check it out.
+       find next bisection to test and check it out.
 git bisect reset [<commit>]
-        finish bisection search and go back to commit.
+       finish bisection search and go back to commit.
 git bisect visualize
-        show bisect status in gitk.
+       show bisect status in gitk.
 git bisect replay <logfile>
-        replay bisection log.
+       replay bisection log.
 git bisect log
-        show bisect log.
+       show bisect log.
 git bisect run <cmd>...
-        use <cmd>... to automatically bisect.
+       use <cmd>... to automatically bisect.
 
 Please use "git help bisect" to get the full man page.'
 
 OPTIONS_SPEC=
 . git-sh-setup
-require_work_tree
+. git-sh-i18n
 
 _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
 _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
 
+bisect_head()
+{
+       if test -f "$GIT_DIR/BISECT_HEAD"
+       then
+               echo BISECT_HEAD
+       else
+               echo HEAD
+       fi
+}
+
 bisect_autostart() {
        test -s "$GIT_DIR/BISECT_START" || {
-               echo >&2 'You need to start by "git bisect start"'
+               gettextln "You need to start by \"git bisect start\"" >&2
                if test -t 0
                then
-                       echo >&2 -n 'Do you want me to do it for you [Y/n]? '
+                       # TRANSLATORS: Make sure to include [Y] and [n] in your
+                       # translation. The program will only accept English input
+                       # at this point.
+                       gettext "Do you want me to do it for you [Y/n]? " >&2
                        read yesno
                        case "$yesno" in
                        [Nn]*)
@@ -52,12 +65,56 @@ bisect_autostart() {
 }
 
 bisect_start() {
+       #
+       # Check for one bad and then some good revisions.
+       #
+       has_double_dash=0
+       for arg; do
+               case "$arg" in --) has_double_dash=1; break ;; esac
+       done
+       orig_args=$(git rev-parse --sq-quote "$@")
+       bad_seen=0
+       eval=''
+       if test "z$(git rev-parse --is-bare-repository)" != zfalse
+       then
+               mode=--no-checkout
+       else
+               mode=''
+       fi
+       while [ $# -gt 0 ]; do
+               arg="$1"
+               case "$arg" in
+               --)
+                       shift
+                       break
+               ;;
+               --no-checkout)
+                       mode=--no-checkout
+                       shift ;;
+               --*)
+                       die "$(eval_gettext "unrecognised option: '\$arg'")" ;;
+               *)
+                       rev=$(git rev-parse -q --verify "$arg^{commit}") || {
+                               test $has_double_dash -eq 1 &&
+                               die "$(eval_gettext "'\$arg' does not appear to be a valid revision")"
+                               break
+                       }
+                       case $bad_seen in
+                       0) state='bad' ; bad_seen=1 ;;
+                       *) state='good' ;;
+                       esac
+                       eval="$eval bisect_write '$state' '$rev' 'nolog' &&"
+                       shift
+                       ;;
+               esac
+       done
+
        #
        # Verify HEAD.
        #
        head=$(GIT_DIR="$GIT_DIR" git symbolic-ref -q HEAD) ||
        head=$(GIT_DIR="$GIT_DIR" git rev-parse --verify HEAD) ||
-       die "Bad HEAD - I need a HEAD"
+       die "$(gettext "Bad HEAD - I need a HEAD")"
 
        #
        # Check if we are bisecting.
@@ -67,7 +124,10 @@ bisect_start() {
        then
                # Reset to the rev from where we started.
                start_head=$(cat "$GIT_DIR/BISECT_START")
-               git checkout "$start_head" -- || exit
+               if test "z$mode" != "z--no-checkout"
+               then
+                       git checkout "$start_head" --
+               fi
        else
                # Get rev from where we start.
                case "$head" in
@@ -76,11 +136,11 @@ bisect_start() {
                        # cogito usage, and cogito users should understand
                        # it relates to cg-seek.
                        [ -s "$GIT_DIR/head-name" ] &&
-                               die "won't bisect on seeked tree"
+                               die "$(gettext "won't bisect on seeked tree")"
                        start_head="${head#refs/heads/}"
                        ;;
                *)
-                       die "Bad HEAD - strange symbolic ref"
+                       die "$(gettext "Bad HEAD - strange symbolic ref")"
                        ;;
                esac
        fi
@@ -90,39 +150,6 @@ bisect_start() {
        #
        bisect_clean_state || exit
 
-       #
-       # Check for one bad and then some good revisions.
-       #
-       has_double_dash=0
-       for arg; do
-           case "$arg" in --) has_double_dash=1; break ;; esac
-       done
-       orig_args=$(git rev-parse --sq-quote "$@")
-       bad_seen=0
-       eval=''
-       while [ $# -gt 0 ]; do
-           arg="$1"
-           case "$arg" in
-           --)
-               shift
-               break
-               ;;
-           *)
-               rev=$(git rev-parse -q --verify "$arg^{commit}") || {
-                   test $has_double_dash -eq 1 &&
-                       die "'$arg' does not appear to be a valid revision"
-                   break
-               }
-               case $bad_seen in
-               0) state='bad' ; bad_seen=1 ;;
-               *) state='good' ;;
-               esac
-               eval="$eval bisect_write '$state' '$rev' 'nolog'; "
-               shift
-               ;;
-           esac
-       done
-
        #
        # Change state.
        # In case of mistaken revs or checkout error, or signals received,
@@ -136,9 +163,12 @@ bisect_start() {
        #
        # Write new start state.
        #
-       echo "$start_head" >"$GIT_DIR/BISECT_START" &&
+       echo "$start_head" >"$GIT_DIR/BISECT_START" && {
+               test "z$mode" != "z--no-checkout" ||
+               git update-ref --no-deref BISECT_HEAD "$start_head"
+       } &&
        git rev-parse --sq-quote "$@" >"$GIT_DIR/BISECT_NAMES" &&
-       eval "$eval" &&
+       eval "$eval true" &&
        echo "git bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" || exit
        #
        # Check if we can proceed to the next bisect state.
@@ -155,7 +185,7 @@ bisect_write() {
        case "$state" in
                bad)            tag="$state" ;;
                good|skip)      tag="$state"-"$rev" ;;
-               *)              die "Bad bisect_write argument: $state" ;;
+               *)              die "$(eval_gettext "Bad bisect_write argument: \$state")" ;;
        esac
        git update-ref "refs/bisect/$tag" "$rev" || exit
        echo "# $state: $(git show-branch $rev)" >>"$GIT_DIR/BISECT_LOG"
@@ -169,7 +199,8 @@ is_expected_rev() {
 
 check_expected_revs() {
        for _rev in "$@"; do
-               if ! is_expected_rev "$_rev"; then
+               if ! is_expected_rev "$_rev"
+               then
                        rm -f "$GIT_DIR/BISECT_ANCESTORS_OK"
                        rm -f "$GIT_DIR/BISECT_EXPECTED_REV"
                        return
@@ -178,18 +209,18 @@ check_expected_revs() {
 }
 
 bisect_skip() {
-        all=''
+       all=''
        for arg in "$@"
        do
-           case "$arg" in
-            *..*)
-                revs=$(git rev-list "$arg") || die "Bad rev input: $arg" ;;
-            *)
-                revs=$(git rev-parse --sq-quote "$arg") ;;
-           esac
-            all="$all $revs"
-        done
-        eval bisect_state 'skip' $all
+               case "$arg" in
+               *..*)
+                       revs=$(git rev-list "$arg") || die "$(eval_gettext "Bad rev input: \$arg")" ;;
+               *)
+                       revs=$(git rev-parse --sq-quote "$arg") ;;
+               esac
+               all="$all $revs"
+       done
+       eval bisect_state 'skip' $all
 }
 
 bisect_state() {
@@ -197,10 +228,10 @@ bisect_state() {
        state=$1
        case "$#,$state" in
        0,*)
-               die "Please call 'bisect_state' with at least one argument." ;;
+               die "$(gettext "Please call 'bisect_state' with at least one argument.")" ;;
        1,bad|1,good|1,skip)
-               rev=$(git rev-parse --verify HEAD) ||
-                       die "Bad rev input: HEAD"
+               rev=$(git rev-parse --verify $(bisect_head)) ||
+                       die "$(gettext "Bad rev input: $(bisect_head)")"
                bisect_write "$state" "$rev"
                check_expected_revs "$rev" ;;
        2,bad|*,good|*,skip)
@@ -209,13 +240,13 @@ bisect_state() {
                for rev in "$@"
                do
                        sha=$(git rev-parse --verify "$rev^{commit}") ||
-                               die "Bad rev input: $rev"
+                               die "$(eval_gettext "Bad rev input: \$rev")"
                        eval="$eval bisect_write '$state' '$sha'; "
                done
                eval "$eval"
                check_expected_revs "$@" ;;
        *,bad)
-               die "'git bisect bad' can take only one argument." ;;
+               die "$(gettext "'git bisect bad' can take only one argument.")" ;;
        *)
                usage ;;
        esac
@@ -238,25 +269,29 @@ bisect_next_check() {
        t,,good)
                # have bad but not good.  we could bisect although
                # this is less optimum.
-               echo >&2 'Warning: bisecting only with a bad commit.'
+               gettextln "Warning: bisecting only with a bad commit." >&2
                if test -t 0
                then
-                       printf >&2 'Are you sure [Y/n]? '
+                       # TRANSLATORS: Make sure to include [Y] and [n] in your
+                       # translation. The program will only accept English input
+                       # at this point.
+                       gettext "Are you sure [Y/n]? " >&2
                        read yesno
                        case "$yesno" in [Nn]*) exit 1 ;; esac
                fi
                : bisect without good...
                ;;
        *)
-               THEN=''
-               test -s "$GIT_DIR/BISECT_START" || {
-                       echo >&2 'You need to start by "git bisect start".'
-                       THEN='then '
-               }
-               echo >&2 'You '$THEN'need to give me at least one good' \
-                       'and one bad revisions.'
-               echo >&2 '(You can use "git bisect bad" and' \
-                       '"git bisect good" for that.)'
+
+               if test -s "$GIT_DIR/BISECT_START"
+               then
+                       gettextln "You need to give me at least one good and one bad revisions.
+(You can use \"git bisect bad\" and \"git bisect good\" for that.)" >&2
+               else
+                       gettextln "You need to start by \"git bisect start\".
+You then need to give me at least one good and one bad revisions.
+(You can use \"git bisect bad\" and \"git bisect good\" for that.)" >&2
+               fi
                exit 1 ;;
        esac
 }
@@ -271,10 +306,10 @@ bisect_next() {
        bisect_next_check good
 
        # Perform all bisection computation, display and checkout
-       git bisect--helper --next-all
+       git bisect--helper --next-all $(test -f "$GIT_DIR/BISECT_HEAD" && echo --no-checkout)
        res=$?
 
-        # Check if we should exit because bisection is finished
+       # Check if we should exit because bisection is finished
        test $res -eq 10 && exit 0
 
        # Check for an error in the bisection process
@@ -289,7 +324,8 @@ bisect_visualize() {
        if test $# = 0
        then
                if test -n "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" &&
-                  type gitk >/dev/null 2>&1; then
+                       type gitk >/dev/null 2>&1
+               then
                        set gitk
                else
                        set git log
@@ -307,23 +343,26 @@ bisect_visualize() {
 
 bisect_reset() {
        test -s "$GIT_DIR/BISECT_START" || {
-               echo "We are not bisecting."
+               gettextln "We are not bisecting."
                return
        }
        case "$#" in
        0) branch=$(cat "$GIT_DIR/BISECT_START") ;;
-       1) git rev-parse --quiet --verify "$1^{commit}" > /dev/null ||
-              die "'$1' is not a valid commit"
-          branch="$1" ;;
+       1) git rev-parse --quiet --verify "$1^{commit}" > /dev/null || {
+                       invalid="$1"
+                       die "$(eval_gettext "'\$invalid' is not a valid commit")"
+               }
+               branch="$1" ;;
        *)
-           usage ;;
+               usage ;;
        esac
-       if git checkout "$branch" -- ; then
-               bisect_clean_state
-       else
-               die "Could not check out original HEAD '$branch'." \
-                               "Try 'git bisect reset <commit>'."
+
+       if ! test -f "$GIT_DIR/BISECT_HEAD" && ! git checkout "$branch" --
+       then
+               die "$(eval_gettext "Could not check out original HEAD '\$branch'.
+Try 'git bisect reset <commit>'.")"
        fi
+       bisect_clean_state
 }
 
 bisect_clean_state() {
@@ -340,18 +379,21 @@ bisect_clean_state() {
        rm -f "$GIT_DIR/BISECT_RUN" &&
        # Cleanup head-name if it got left by an old version of git-bisect
        rm -f "$GIT_DIR/head-name" &&
-
+       git update-ref -d --no-deref BISECT_HEAD &&
+       # clean up BISECT_START last
        rm -f "$GIT_DIR/BISECT_START"
 }
 
 bisect_replay () {
-       test "$#" -eq 1 || die "No logfile given"
-       test -r "$1" || die "cannot read $1 for replaying"
+       file="$1"
+       test "$#" -eq 1 || die "$(gettext "No logfile given")"
+       test -r "$file" || die "$(eval_gettext "cannot read \$file for replaying")"
        bisect_reset
        while read git bisect command rev
        do
                test "$git $bisect" = "git bisect" -o "$git" = "git-bisect" || continue
-               if test "$git" = "git-bisect"; then
+               if test "$git" = "git-bisect"
+               then
                        rev="$command"
                        command="$bisect"
                fi
@@ -362,98 +404,105 @@ bisect_replay () {
                good|bad|skip)
                        bisect_write "$command" "$rev" ;;
                *)
-                       die "?? what are you talking about?" ;;
+                       die "$(gettext "?? what are you talking about?")" ;;
                esac
-       done <"$1"
+       done <"$file"
        bisect_auto_next
 }
 
 bisect_run () {
-    bisect_next_check fail
-
-    while true
-    do
-      echo "running $@"
-      "$@"
-      res=$?
-
-      # Check for really bad run error.
-      if [ $res -lt 0 -o $res -ge 128 ]; then
-         echo >&2 "bisect run failed:"
-         echo >&2 "exit code $res from '$@' is < 0 or >= 128"
-         exit $res
-      fi
-
-      # Find current state depending on run success or failure.
-      # A special exit code of 125 means cannot test.
-      if [ $res -eq 125 ]; then
-         state='skip'
-      elif [ $res -gt 0 ]; then
-         state='bad'
-      else
-         state='good'
-      fi
-
-      # We have to use a subshell because "bisect_state" can exit.
-      ( bisect_state $state > "$GIT_DIR/BISECT_RUN" )
-      res=$?
-
-      cat "$GIT_DIR/BISECT_RUN"
-
-      if sane_grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
-               > /dev/null; then
-         echo >&2 "bisect run cannot continue any more"
-         exit $res
-      fi
-
-      if [ $res -ne 0 ]; then
-         echo >&2 "bisect run failed:"
-         echo >&2 "'bisect_state $state' exited with error code $res"
-         exit $res
-      fi
-
-      if sane_grep "is the first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then
-         echo "bisect run success"
-         exit 0;
-      fi
-
-    done
+       bisect_next_check fail
+
+       while true
+       do
+               command="$@"
+               eval_gettextln "running \$command"
+               "$@"
+               res=$?
+
+               # Check for really bad run error.
+               if [ $res -lt 0 -o $res -ge 128 ]
+               then
+                       eval_gettextln "bisect run failed:
+exit code \$res from '\$command' is < 0 or >= 128" >&2
+                       exit $res
+               fi
+
+               # Find current state depending on run success or failure.
+               # A special exit code of 125 means cannot test.
+               if [ $res -eq 125 ]
+               then
+                       state='skip'
+               elif [ $res -gt 0 ]
+               then
+                       state='bad'
+               else
+                       state='good'
+               fi
+
+               # We have to use a subshell because "bisect_state" can exit.
+               ( bisect_state $state > "$GIT_DIR/BISECT_RUN" )
+               res=$?
+
+               cat "$GIT_DIR/BISECT_RUN"
+
+               if sane_grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
+                       > /dev/null
+               then
+                       gettextln "bisect run cannot continue any more" >&2
+                       exit $res
+               fi
+
+               if [ $res -ne 0 ]
+               then
+                       eval_gettextln "bisect run failed:
+'bisect_state \$state' exited with error code \$res" >&2
+                       exit $res
+               fi
+
+               if sane_grep "is the first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null
+               then
+                       gettextln "bisect run success"
+                       exit 0;
+               fi
+
+       done
 }
 
 bisect_log () {
-       test -s "$GIT_DIR/BISECT_LOG" || die "We are not bisecting."
+       test -s "$GIT_DIR/BISECT_LOG" || die "$(gettext "We are not bisecting.")"
        cat "$GIT_DIR/BISECT_LOG"
 }
 
 case "$#" in
 0)
-    usage ;;
+       usage ;;
 *)
-    cmd="$1"
-    shift
-    case "$cmd" in
-    help)
-        git bisect -h ;;
-    start)
-        bisect_start "$@" ;;
-    bad|good)
-        bisect_state "$cmd" "$@" ;;
-    skip)
-        bisect_skip "$@" ;;
-    next)
-        # Not sure we want "next" at the UI level anymore.
-        bisect_next "$@" ;;
-    visualize|view)
-       bisect_visualize "$@" ;;
-    reset)
-        bisect_reset "$@" ;;
-    replay)
-       bisect_replay "$@" ;;
-    log)
-       bisect_log ;;
-    run)
-        bisect_run "$@" ;;
-    *)
-        usage ;;
-    esac
+       cmd="$1"
+       shift
+       case "$cmd" in
+       help)
+               git bisect -h ;;
+       start)
+               bisect_start "$@" ;;
+       bad|good)
+               bisect_state "$cmd" "$@" ;;
+       skip)
+               bisect_skip "$@" ;;
+       next)
+               # Not sure we want "next" at the UI level anymore.
+               bisect_next "$@" ;;
+       visualize|view)
+               bisect_visualize "$@" ;;
+       reset)
+               bisect_reset "$@" ;;
+       replay)
+               bisect_replay "$@" ;;
+       log)
+               bisect_log ;;
+       run)
+               bisect_run "$@" ;;
+       *)
+               usage ;;
+       esac
 esac
index e0bb81ed8d0bd89f18b31b1c03d3e23744aea5a1..5ef8ff76f6fd262c3109d25d706ebd3db35c73fa 100644 (file)
@@ -215,10 +215,14 @@ extern char *gitbasename(char *);
 #define is_dir_sep(c) ((c) == '/')
 #endif
 
+#ifndef find_last_dir_sep
+#define find_last_dir_sep(path) strrchr(path, '/')
+#endif
+
 #if __HP_cc >= 61000
 #define NORETURN __attribute__((noreturn))
 #define NORETURN_PTR
-#elif defined(__GNUC__)
+#elif defined(__GNUC__) && !defined(NO_NORETURN)
 #define NORETURN __attribute__((__noreturn__))
 #define NORETURN_PTR __attribute__((__noreturn__))
 #elif defined(_MSC_VER)
@@ -236,6 +240,7 @@ extern char *gitbasename(char *);
 
 /* General helper functions */
 extern void vreportf(const char *prefix, const char *err, va_list params);
+extern void vwritef(int fd, const char *prefix, const char *err, va_list params);
 extern NORETURN void usage(const char *err);
 extern NORETURN void usagef(const char *err, ...) __attribute__((format (printf, 1, 2)));
 extern NORETURN void die(const char *err, ...) __attribute__((format (printf, 1, 2)));
@@ -244,6 +249,7 @@ extern int error(const char *err, ...) __attribute__((format (printf, 1, 2)));
 extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2)));
 
 extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params));
+extern void set_error_routine(void (*routine)(const char *err, va_list params));
 
 extern int prefixcmp(const char *str, const char *prefix);
 extern int suffixcmp(const char *str, const char *suffix);
index 0594bf7ca54c6b91d5f96dd86d8962e93459d004..8452890be974d30942f0acaa3f19282c1b8b25b2 100755 (executable)
@@ -13,7 +13,8 @@ TOOL_MODE=diff
 should_prompt () {
        prompt_merge=$(git config --bool mergetool.prompt || echo true)
        prompt=$(git config --bool difftool.prompt || echo $prompt_merge)
-       if test "$prompt" = true; then
+       if test "$prompt" = true
+       then
                test -z "$GIT_DIFFTOOL_NO_PROMPT"
        else
                test -n "$GIT_DIFFTOOL_PROMPT"
@@ -37,9 +38,11 @@ launch_merge_tool () {
 
        # $LOCAL and $REMOTE are temporary files so prompt
        # the user with the real $MERGED name before launching $merge_tool.
-       if should_prompt; then
+       if should_prompt
+       then
                printf "\nViewing: '$MERGED'\n"
-               if use_ext_cmd; then
+               if use_ext_cmd
+               then
                        printf "Hit return to launch '%s': " \
                                "$GIT_DIFFTOOL_EXTCMD"
                else
@@ -48,7 +51,8 @@ launch_merge_tool () {
                read ans
        fi
 
-       if use_ext_cmd; then
+       if use_ext_cmd
+       then
                export BASE
                eval $GIT_DIFFTOOL_EXTCMD '"$LOCAL"' '"$REMOTE"'
        else
@@ -56,8 +60,10 @@ launch_merge_tool () {
        fi
 }
 
-if ! use_ext_cmd; then
-       if test -n "$GIT_DIFF_TOOL"; then
+if ! use_ext_cmd
+then
+       if test -n "$GIT_DIFF_TOOL"
+       then
                merge_tool="$GIT_DIFF_TOOL"
        else
                merge_tool="$(get_merge_tool)" || exit
index 962a93b586571eb6fc60aae53c77f6e6b9fb281f..add2c0247fa91e0f629428c295fc581f19cf85e1 100755 (executable)
@@ -12,7 +12,7 @@
 
 functions=$(cat << \EOF
 warn () {
-        echo "$*" >&2
+       echo "$*" >&2
 }
 
 map()
@@ -98,19 +98,17 @@ set_ident () {
 }
 
 USAGE="[--env-filter <command>] [--tree-filter <command>]
-            [--index-filter <command>] [--parent-filter <command>]
-            [--msg-filter <command>] [--commit-filter <command>]
-            [--tag-name-filter <command>] [--subdirectory-filter <directory>]
-            [--original <namespace>] [-d <directory>] [-f | --force]
-            [<rev-list options>...]"
+       [--index-filter <command>] [--parent-filter <command>]
+       [--msg-filter <command>] [--commit-filter <command>]
+       [--tag-name-filter <command>] [--subdirectory-filter <directory>]
+       [--original <namespace>] [-d <directory>] [-f | --force]
+       [<rev-list options>...]"
 
 OPTIONS_SPEC=
 . git-sh-setup
 
 if [ "$(is_bare_repository)" = false ]; then
-       git diff-files --ignore-submodules --quiet &&
-       git diff-index --cached --quiet HEAD -- ||
-       die "Cannot rewrite branch(es) with a dirty working directory."
+       require_clean_work_tree 'rewrite branches'
 fi
 
 tempdir=.git-rewrite
@@ -363,7 +361,7 @@ while read commit parents; do
        sed -e '1,/^$/d' <../commit | \
                eval "$filter_msg" > ../message ||
                        die "msg filter failed: $filter_msg"
-       @SHELL_PATH@ -c "$filter_commit" "git commit-tree" \
+       workdir=$workdir @SHELL_PATH@ -c "$filter_commit" "git commit-tree" \
                $(git write-tree) $parentstr < ../message > ../map/$commit ||
                        die "could not write rewritten commit"
 done <../revs
index 8bfa8a055ccd0c344d595f112b571bb3d6e21c28..01a1b05e6bdcd12f82f70282975780d3a19d910d 100755 (executable)
@@ -27,6 +27,7 @@ httpd="$(git config --get instaweb.httpd)"
 root="$(git config --get instaweb.gitwebdir)"
 port=$(git config --get instaweb.port)
 module_path="$(git config --get instaweb.modulepath)"
+action="browse"
 
 conf="$GIT_DIR/gitweb/httpd.conf"
 
@@ -98,12 +99,18 @@ start_httpd () {
 
        # here $httpd should have a meaningful value
        resolve_full_httpd
+       mkdir -p "$fqgitdir/gitweb/$httpd_only"
+       conf="$fqgitdir/gitweb/$httpd_only.conf"
+
+       # generate correct config file if it doesn't exist
+       test -f "$conf" || configure_httpd
+       test -f "$fqgitdir/gitweb/gitweb_config.perl" || gitweb_conf
 
        # don't quote $full_httpd, there can be arguments to it (-f)
        case "$httpd" in
        *mongoose*|*plackup*)
                #These servers don't have a daemon mode so we'll have to fork it
-               $full_httpd "$fqgitdir/gitweb/httpd.conf" &
+               $full_httpd "$conf" &
                #Save the pid before doing anything else (we'll print it later)
                pid=$!
 
@@ -117,7 +124,7 @@ $pid
 EOF
                ;;
        *)
-               $full_httpd "$fqgitdir/gitweb/httpd.conf"
+               $full_httpd "$conf"
                if test $? != 0; then
                        echo "Could not execute http daemon $httpd."
                        exit 1
@@ -148,17 +155,13 @@ while test $# != 0
 do
        case "$1" in
        --stop|stop)
-               stop_httpd
-               exit 0
+               action="stop"
                ;;
        --start|start)
-               start_httpd
-               exit 0
+               action="start"
                ;;
        --restart|restart)
-               stop_httpd
-               start_httpd
-               exit 0
+               action="restart"
                ;;
        -l|--local)
                local=true
@@ -587,33 +590,54 @@ our \$projects_list = \$projectroot;
 EOF
 }
 
-gitweb_conf
-
-resolve_full_httpd
-mkdir -p "$fqgitdir/gitweb/$httpd_only"
+configure_httpd() {
+       case "$httpd" in
+       *lighttpd*)
+               lighttpd_conf
+               ;;
+       *apache2*|*httpd*)
+               apache2_conf
+               ;;
+       webrick)
+               webrick_conf
+               ;;
+       *mongoose*)
+               mongoose_conf
+               ;;
+       *plackup*)
+               plackup_conf
+               ;;
+       *)
+               echo "Unknown httpd specified: $httpd"
+               exit 1
+               ;;
+       esac
+}
 
-case "$httpd" in
-*lighttpd*)
-       lighttpd_conf
-       ;;
-*apache2*|*httpd*)
-       apache2_conf
-       ;;
-webrick)
-       webrick_conf
+case "$action" in
+stop)
+       stop_httpd
+       exit 0
        ;;
-*mongoose*)
-       mongoose_conf
+start)
+       start_httpd
+       exit 0
        ;;
-*plackup*)
-       plackup_conf
-       ;;
-*)
-       echo "Unknown httpd specified: $httpd"
-       exit 1
+restart)
+       stop_httpd
+       start_httpd
+       exit 0
        ;;
 esac
 
+gitweb_conf
+
+resolve_full_httpd
+mkdir -p "$fqgitdir/gitweb/$httpd_only"
+conf="$fqgitdir/gitweb/$httpd_only.conf"
+
+configure_httpd
+
 start_httpd
 url=http://127.0.0.1:$port
 
index 7aeb96952f668baef2fd18383806e13a20415f19..f612cb847aca981e16cb6006fa08d870497c6263 100755 (executable)
@@ -117,7 +117,7 @@ case "${1:-.}${2:-.}${3:-.}" in
 
                # If we do not have enough common material, it is not
                # worth trying two-file merge using common subsections.
-               expr "$sz0" \< "$sz1" \* 2 >/dev/null || : >$orig
+               expr $sz0 \< $sz1 \* 2 >/dev/null || : >$orig
                ;;
        *)
                echo "Auto-merging $4"
index 4db9212331259664732f031438b7b87b7a10244f..ed630b208a80a36d729b5074a39bc53df0cabd59 100644 (file)
@@ -9,36 +9,19 @@ merge_mode() {
 }
 
 translate_merge_tool_path () {
-       case "$1" in
-       araxis)
-               echo compare
-               ;;
-       bc3)
-               echo bcompare
-               ;;
-       emerge)
-               echo emacs
-               ;;
-       gvimdiff|gvimdiff2)
-               echo gvim
-               ;;
-       vimdiff|vimdiff2)
-               echo vim
-               ;;
-       *)
-               echo "$1"
-               ;;
-       esac
+       echo "$1"
 }
 
 check_unchanged () {
-       if test "$MERGED" -nt "$BACKUP"; then
+       if test "$MERGED" -nt "$BACKUP"
+       then
                status=0
        else
-               while true; do
+               while true
+               do
                        echo "$MERGED seems unchanged."
                        printf "Was the merge successful? [y/n] "
-                       read answer
+                       read answer || return 1
                        case "$answer" in
                        y*|Y*) status=0; break ;;
                        n*|N*) status=1; break ;;
@@ -47,312 +30,98 @@ check_unchanged () {
        fi
 }
 
+valid_tool_config () {
+       if test -n "$(get_merge_tool_cmd "$1")"
+       then
+               return 0
+       else
+               return 1
+       fi
+}
+
 valid_tool () {
+       setup_tool "$1" || valid_tool_config "$1"
+}
+
+setup_tool () {
        case "$1" in
-       araxis | bc3 | diffuse | ecmerge | emerge | gvimdiff | gvimdiff2 | \
-       kdiff3 | meld | opendiff | p4merge | tkdiff | vimdiff | vimdiff2 | xxdiff)
-               ;; # happy
-       kompare)
-               if ! diff_mode; then
-                       return 1
-               fi
-               ;;
-       tortoisemerge)
-               if ! merge_mode; then
-                       return 1
-               fi
+       vim*|gvim*)
+               tool=vim
                ;;
        *)
-               if test -z "$(get_merge_tool_cmd "$1")"; then
-                       return 1
-               fi
+               tool="$1"
                ;;
        esac
+       mergetools="$(git --exec-path)/mergetools"
+
+       # Load the default definitions
+       . "$mergetools/defaults"
+       if ! test -f "$mergetools/$tool"
+       then
+               return 1
+       fi
+
+       # Load the redefined functions
+       . "$mergetools/$tool"
+
+       if merge_mode && ! can_merge
+       then
+               echo "error: '$tool' can not be used to resolve merges" >&2
+               exit 1
+       elif diff_mode && ! can_diff
+       then
+               echo "error: '$tool' can only be used to resolve merges" >&2
+               exit 1
+       fi
+       return 0
 }
 
 get_merge_tool_cmd () {
        # Prints the custom command for a merge tool
-       if test -n "$1"; then
-               merge_tool="$1"
-       else
-               merge_tool="$(get_merge_tool)"
-       fi
-       if diff_mode; then
+       merge_tool="$1"
+       if diff_mode
+       then
                echo "$(git config difftool.$merge_tool.cmd ||
-                       git config mergetool.$merge_tool.cmd)"
+                       git config mergetool.$merge_tool.cmd)"
        else
                echo "$(git config mergetool.$merge_tool.cmd)"
        fi
 }
 
+# Entry point for running tools
 run_merge_tool () {
+       # If GIT_PREFIX is empty then we cannot use it in tools
+       # that expect to be able to chdir() to its value.
+       GIT_PREFIX=${GIT_PREFIX:-.}
+       export GIT_PREFIX
+
        merge_tool_path="$(get_merge_tool_path "$1")" || exit
        base_present="$2"
        status=0
 
-       case "$1" in
-       araxis)
-               if merge_mode; then
-                       touch "$BACKUP"
-                       if $base_present; then
-                               "$merge_tool_path" -wait -merge -3 -a1 \
-                                       "$BASE" "$LOCAL" "$REMOTE" "$MERGED" \
-                                       >/dev/null 2>&1
-                       else
-                               "$merge_tool_path" -wait -2 \
-                                       "$LOCAL" "$REMOTE" "$MERGED" \
-                                       >/dev/null 2>&1
-                       fi
-                       check_unchanged
-               else
-                       "$merge_tool_path" -wait -2 "$LOCAL" "$REMOTE" \
-                               >/dev/null 2>&1
-               fi
-               ;;
-       bc3)
-               if merge_mode; then
-                       touch "$BACKUP"
-                       if $base_present; then
-                               "$merge_tool_path" "$LOCAL" "$REMOTE" "$BASE" \
-                                       -mergeoutput="$MERGED"
-                       else
-                               "$merge_tool_path" "$LOCAL" "$REMOTE" \
-                                       -mergeoutput="$MERGED"
-                       fi
-                       check_unchanged
-               else
-                       "$merge_tool_path" "$LOCAL" "$REMOTE"
-               fi
-               ;;
-       diffuse)
-               if merge_mode; then
-                       touch "$BACKUP"
-                       if $base_present; then
-                               "$merge_tool_path" \
-                                       "$LOCAL" "$MERGED" "$REMOTE" \
-                                       "$BASE" | cat
-                       else
-                               "$merge_tool_path" \
-                                       "$LOCAL" "$MERGED" "$REMOTE" | cat
-                       fi
-                       check_unchanged
-               else
-                       "$merge_tool_path" "$LOCAL" "$REMOTE" | cat
-               fi
-               ;;
-       ecmerge)
-               if merge_mode; then
-                       touch "$BACKUP"
-                       if $base_present; then
-                               "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" \
-                                       --default --mode=merge3 --to="$MERGED"
-                       else
-                               "$merge_tool_path" "$LOCAL" "$REMOTE" \
-                                       --default --mode=merge2 --to="$MERGED"
-                       fi
-                       check_unchanged
-               else
-                       "$merge_tool_path" --default --mode=diff2 \
-                               "$LOCAL" "$REMOTE"
-               fi
-               ;;
-       emerge)
-               if merge_mode; then
-                       if $base_present; then
-                               "$merge_tool_path" \
-                                       -f emerge-files-with-ancestor-command \
-                                       "$LOCAL" "$REMOTE" "$BASE" \
-                                       "$(basename "$MERGED")"
-                       else
-                               "$merge_tool_path" \
-                                       -f emerge-files-command \
-                                       "$LOCAL" "$REMOTE" \
-                                       "$(basename "$MERGED")"
-                       fi
-                       status=$?
-               else
-                       "$merge_tool_path" -f emerge-files-command \
-                               "$LOCAL" "$REMOTE"
-               fi
-               ;;
-       gvimdiff|vimdiff)
-               if merge_mode; then
-                       touch "$BACKUP"
-                       if $base_present; then
-                               "$merge_tool_path" -f -d -c "wincmd J" \
-                                       "$MERGED" "$LOCAL" "$BASE" "$REMOTE"
-                       else
-                               "$merge_tool_path" -f -d -c "wincmd l" \
-                                       "$LOCAL" "$MERGED" "$REMOTE"
-                       fi
-                       check_unchanged
-               else
-                       "$merge_tool_path" -R -f -d -c "wincmd l" \
-                               "$LOCAL" "$REMOTE"
-               fi
-               ;;
-       gvimdiff2|vimdiff2)
-               if merge_mode; then
-                       touch "$BACKUP"
-                       "$merge_tool_path" -f -d -c "wincmd l" \
-                               "$LOCAL" "$MERGED" "$REMOTE"
-                       check_unchanged
-               else
-                       "$merge_tool_path" -R -f -d -c "wincmd l" \
-                               "$LOCAL" "$REMOTE"
-               fi
-               ;;
-       kdiff3)
-               if merge_mode; then
-                       if $base_present; then
-                               ("$merge_tool_path" --auto \
-                                       --L1 "$MERGED (Base)" \
-                                       --L2 "$MERGED (Local)" \
-                                       --L3 "$MERGED (Remote)" \
-                                       -o "$MERGED" \
-                                       "$BASE" "$LOCAL" "$REMOTE" \
-                               > /dev/null 2>&1)
-                       else
-                               ("$merge_tool_path" --auto \
-                                       --L1 "$MERGED (Local)" \
-                                       --L2 "$MERGED (Remote)" \
-                                       -o "$MERGED" \
-                                       "$LOCAL" "$REMOTE" \
-                               > /dev/null 2>&1)
-                       fi
-                       status=$?
-               else
-                       ("$merge_tool_path" --auto \
-                               --L1 "$MERGED (A)" \
-                               --L2 "$MERGED (B)" "$LOCAL" "$REMOTE" \
-                       > /dev/null 2>&1)
-               fi
-               ;;
-       kompare)
-               "$merge_tool_path" "$LOCAL" "$REMOTE"
-               ;;
-       meld)
-               if merge_mode; then
-                       touch "$BACKUP"
-                       "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"
-                       check_unchanged
-               else
-                       "$merge_tool_path" "$LOCAL" "$REMOTE"
-               fi
-               ;;
-       opendiff)
-               if merge_mode; then
-                       touch "$BACKUP"
-                       if $base_present; then
-                               "$merge_tool_path" "$LOCAL" "$REMOTE" \
-                                       -ancestor "$BASE" \
-                                       -merge "$MERGED" | cat
-                       else
-                               "$merge_tool_path" "$LOCAL" "$REMOTE" \
-                                       -merge "$MERGED" | cat
-                       fi
-                       check_unchanged
-               else
-                       "$merge_tool_path" "$LOCAL" "$REMOTE" | cat
-               fi
-               ;;
-       p4merge)
-               if merge_mode; then
-                       touch "$BACKUP"
-                       $base_present || >"$BASE"
-                       "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED"
-                       check_unchanged
-               else
-                       "$merge_tool_path" "$LOCAL" "$REMOTE"
-               fi
-               ;;
-       tkdiff)
-               if merge_mode; then
-                       if $base_present; then
-                               "$merge_tool_path" -a "$BASE" \
-                                       -o "$MERGED" "$LOCAL" "$REMOTE"
-                       else
-                               "$merge_tool_path" \
-                                       -o "$MERGED" "$LOCAL" "$REMOTE"
-                       fi
-                       status=$?
-               else
-                       "$merge_tool_path" "$LOCAL" "$REMOTE"
-               fi
-               ;;
-       tortoisemerge)
-               if $base_present; then
-                       touch "$BACKUP"
-                       "$merge_tool_path" \
-                               -base:"$BASE" -mine:"$LOCAL" \
-                               -theirs:"$REMOTE" -merged:"$MERGED"
-                       check_unchanged
-               else
-                       echo "TortoiseMerge cannot be used without a base" 1>&2
-                       status=1
-               fi
-               ;;
-       xxdiff)
-               if merge_mode; then
-                       touch "$BACKUP"
-                       if $base_present; then
-                               "$merge_tool_path" -X --show-merged-pane \
-                                       -R 'Accel.SaveAsMerged: "Ctrl-S"' \
-                                       -R 'Accel.Search: "Ctrl+F"' \
-                                       -R 'Accel.SearchForward: "Ctrl-G"' \
-                                       --merged-file "$MERGED" \
-                                       "$LOCAL" "$BASE" "$REMOTE"
-                       else
-                               "$merge_tool_path" -X $extra \
-                                       -R 'Accel.SaveAsMerged: "Ctrl-S"' \
-                                       -R 'Accel.Search: "Ctrl+F"' \
-                                       -R 'Accel.SearchForward: "Ctrl-G"' \
-                                       --merged-file "$MERGED" \
-                                       "$LOCAL" "$REMOTE"
-                       fi
-                       check_unchanged
-               else
-                       "$merge_tool_path" \
-                               -R 'Accel.Search: "Ctrl+F"' \
-                               -R 'Accel.SearchForward: "Ctrl-G"' \
-                               "$LOCAL" "$REMOTE"
-               fi
-               ;;
-       *)
-               merge_tool_cmd="$(get_merge_tool_cmd "$1")"
-               if test -z "$merge_tool_cmd"; then
-                       if merge_mode; then
-                               status=1
-                       fi
-                       break
-               fi
-               if merge_mode; then
-                       trust_exit_code="$(git config --bool \
-                               mergetool."$1".trustExitCode || echo false)"
-                       if test "$trust_exit_code" = "false"; then
-                               touch "$BACKUP"
-                               ( eval $merge_tool_cmd )
-                               check_unchanged
-                       else
-                               ( eval $merge_tool_cmd )
-                               status=$?
-                       fi
-               else
-                       ( eval $merge_tool_cmd )
-               fi
-               ;;
-       esac
+       # Bring tool-specific functions into scope
+       setup_tool "$1"
+
+       if merge_mode
+       then
+               merge_cmd "$1"
+       else
+               diff_cmd "$1"
+       fi
        return $status
 }
 
 guess_merge_tool () {
-       if merge_mode; then
+       if merge_mode
+       then
                tools="tortoisemerge"
        else
                tools="kompare"
        fi
-       if test -n "$DISPLAY"; then
-               if test -n "$GNOME_DESKTOP_SESSION_ID" ; then
+       if test -n "$DISPLAY"
+       then
+               if test -n "$GNOME_DESKTOP_SESSION_ID"
+               then
                        tools="meld opendiff kdiff3 tkdiff xxdiff $tools"
                else
                        tools="opendiff kdiff3 tkdiff xxdiff meld $tools"
@@ -373,7 +142,8 @@ guess_merge_tool () {
        for i in $tools
        do
                merge_tool_path="$(translate_merge_tool_path "$i")"
-               if type "$merge_tool_path" > /dev/null 2>&1; then
+               if type "$merge_tool_path" >/dev/null 2>&1
+               then
                        echo "$i"
                        return 0
                fi
@@ -386,12 +156,14 @@ guess_merge_tool () {
 get_configured_merge_tool () {
        # Diff mode first tries diff.tool and falls back to merge.tool.
        # Merge mode only checks merge.tool
-       if diff_mode; then
+       if diff_mode
+       then
                merge_tool=$(git config diff.tool || git config merge.tool)
        else
                merge_tool=$(git config merge.tool)
        fi
-       if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then
+       if test -n "$merge_tool" && ! valid_tool "$merge_tool"
+       then
                echo >&2 "git config option $TOOL_MODE.tool set to unknown tool: $merge_tool"
                echo >&2 "Resetting to default..."
                return 1
@@ -401,28 +173,28 @@ get_configured_merge_tool () {
 
 get_merge_tool_path () {
        # A merge tool has been set, so verify that it's valid.
-       if test -n "$1"; then
-               merge_tool="$1"
-       else
-               merge_tool="$(get_merge_tool)"
-       fi
-       if ! valid_tool "$merge_tool"; then
+       merge_tool="$1"
+       if ! valid_tool "$merge_tool"
+       then
                echo >&2 "Unknown merge tool $merge_tool"
                exit 1
        fi
-       if diff_mode; then
+       if diff_mode
+       then
                merge_tool_path=$(git config difftool."$merge_tool".path ||
-                                 git config mergetool."$merge_tool".path)
+                                 git config mergetool."$merge_tool".path)
        else
                merge_tool_path=$(git config mergetool."$merge_tool".path)
        fi
-       if test -z "$merge_tool_path"; then
+       if test -z "$merge_tool_path"
+       then
                merge_tool_path="$(translate_merge_tool_path "$merge_tool")"
        fi
        if test -z "$(get_merge_tool_cmd "$merge_tool")" &&
-       ! type "$merge_tool_path" > /dev/null 2>&1; then
+               ! type "$merge_tool_path" >/dev/null 2>&1
+       then
                echo >&2 "The $TOOL_MODE tool $merge_tool is not available as"\
-                        "'$merge_tool_path'"
+                        "'$merge_tool_path'"
                exit 1
        fi
        echo "$merge_tool_path"
@@ -430,9 +202,10 @@ get_merge_tool_path () {
 
 get_merge_tool () {
        # Check if a merge tool has been configured
-       merge_tool=$(get_configured_merge_tool)
+       merge_tool="$(get_configured_merge_tool)"
        # Try to guess an appropriate merge tool if no tool has been set.
-       if test -z "$merge_tool"; then
+       if test -z "$merge_tool"
+       then
                merge_tool="$(guess_merge_tool)" || exit
        fi
        echo "$merge_tool"
index 3aab5aae844b963fc4178675a56da20e02488dfd..085e213a126e5864befc8e3436832ae3c587a624 100755 (executable)
@@ -72,7 +72,7 @@ describe_file () {
 resolve_symlink_merge () {
     while true; do
        printf "Use (l)ocal or (r)emote, or (a)bort? "
-       read ans
+       read ans || return 1
        case "$ans" in
            [lL]*)
                git checkout-index -f --stage=2 -- "$MERGED"
@@ -100,7 +100,7 @@ resolve_deleted_merge () {
        else
            printf "Use (c)reated or (d)eleted file, or (a)bort? "
        fi
-       read ans
+       read ans || return 1
        case "$ans" in
            [mMcC]*)
                git add -- "$MERGED"
@@ -122,7 +122,7 @@ resolve_deleted_merge () {
 resolve_submodule_merge () {
     while true; do
        printf "Use (l)ocal or (r)emote, or (a)bort? "
-       read ans
+       read ans || return 1
        case "$ans" in
            [lL]*)
                if ! local_present; then
@@ -249,7 +249,7 @@ merge_file () {
     describe_file "$remote_mode" "remote" "$REMOTE"
     if "$prompt" = true; then
        printf "Hit return to start merge resolution tool (%s): " "$merge_tool"
-       read ans
+       read ans || return 1
     fi
 
     if base_present; then
@@ -320,7 +320,7 @@ done
 prompt_after_failed_merge() {
     while true; do
        printf "Continue merging other unresolved paths (y/n) ? "
-       read ans
+       read ans || return 1
        case "$ans" in
 
            [yY]*)
@@ -342,64 +342,42 @@ merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo fa
 
 last_status=0
 rollup_status=0
-rerere=false
-
-files_to_merge() {
-    if test "$rerere" = true
-    then
-       git rerere remaining
-    else
-       git ls-files -u | sed -e 's/^[^ ]*      //' | sort -u
-    fi
-}
-
+files=
 
 if test $# -eq 0 ; then
     cd_to_toplevel
 
     if test -e "$GIT_DIR/MERGE_RR"
     then
-       rerere=true
-    fi
-
-    files=$(files_to_merge)
-    if test -z "$files" ; then
-       echo "No files need merging"
-       exit 0
+       files=$(git rerere remaining)
+    else
+       files=$(git ls-files -u | sed -e 's/^[^ ]*      //' | sort -u)
     fi
+else
+    files=$(git ls-files -u -- "$@" | sed -e 's/^[^    ]*      //' | sort -u)
+fi
 
-    # Save original stdin
-    exec 3<&0
+if test -z "$files" ; then
+    echo "No files need merging"
+    exit 0
+fi
 
-    printf "Merging:\n"
-    printf "$files\n"
+printf "Merging:\n"
+printf "$files\n"
 
-    files_to_merge |
-    while IFS= read i
-    do
-       if test $last_status -ne 0; then
-           prompt_after_failed_merge <&3 || exit 1
-       fi
-       printf "\n"
-       merge_file "$i" <&3
-       last_status=$?
-       if test $last_status -ne 0; then
-           rollup_status=1
-       fi
-    done
-else
-    while test $# -gt 0; do
-       if test $last_status -ne 0; then
-           prompt_after_failed_merge || exit 1
-       fi
-       printf "\n"
-       merge_file "$1"
-       last_status=$?
-       if test $last_status -ne 0; then
-           rollup_status=1
-       fi
-       shift
-    done
-fi
+IFS='
+'
+for i in $files
+do
+    if test $last_status -ne 0; then
+       prompt_after_failed_merge || exit 1
+    fi
+    printf "\n"
+    merge_file "$i"
+    last_status=$?
+    if test $last_status -ne 0; then
+       rollup_status=1
+    fi
+done
 
 exit $rollup_status
index fb9e2df9312e96ecf8ad5ab2bde4b74b979fe02e..63da37bcc2730358140ae4b862040ed8fb0ed77e 100755 (executable)
@@ -9,7 +9,8 @@ LONG_USAGE='Fetch one or more remote refs and merge it/them into the current HEA
 SUBDIRECTORY_OK=Yes
 OPTIONS_SPEC=
 . git-sh-setup
-set_reflog_action "pull $*"
+. git-sh-i18n
+set_reflog_action "pull${1+ $*}"
 require_work_tree
 cd_to_toplevel
 
@@ -17,20 +18,20 @@ cd_to_toplevel
 die_conflict () {
     git diff-index --cached --name-status -r --ignore-submodules HEAD --
     if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then
-       die "Pull is not possible because you have unmerged files.
+       die "$(gettext "Pull is not possible because you have unmerged files.
 Please, fix them up in the work tree, and then use 'git add/rm <file>'
-as appropriate to mark resolution, or use 'git commit -a'."
+as appropriate to mark resolution, or use 'git commit -a'.")"
     else
-       die "Pull is not possible because you have unmerged files."
+       die "$(gettext "Pull is not possible because you have unmerged files.")"
     fi
 }
 
 die_merge () {
     if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then
-       die "You have not concluded your merge (MERGE_HEAD exists).
-Please, commit your changes before you can merge."
+       die "$(gettext "You have not concluded your merge (MERGE_HEAD exists).
+Please, commit your changes before you can merge.")"
     else
-       die "You have not concluded your merge (MERGE_HEAD exists)."
+       die "$(gettext "You have not concluded your merge (MERGE_HEAD exists).")"
     fi
 }
 
@@ -185,7 +186,7 @@ test true = "$rebase" && {
                # On an unborn branch
                if test -f "$GIT_DIR/index"
                then
-                       die "updating an unborn branch with changes added to the index"
+                       die "$(gettext "updating an unborn branch with changes added to the index")"
                fi
        else
                require_clean_work_tree "pull with rebase" "Please commit or stash them."
@@ -216,17 +217,17 @@ then
        # $orig_head commit, but we are merging into $curr_head.
        # First update the working tree to match $curr_head.
 
-       echo >&2 "Warning: fetch updated the current branch head."
-       echo >&2 "Warning: fast-forwarding your working tree from"
-       echo >&2 "Warning: commit $orig_head."
+       eval_gettextln "Warning: fetch updated the current branch head.
+Warning: fast-forwarding your working tree from
+Warning: commit \$orig_head." >&2
        git update-index -q --refresh
        git read-tree -u -m "$orig_head" "$curr_head" ||
-               die 'Cannot fast-forward your working tree.
+               die "$(eval_gettext "Cannot fast-forward your working tree.
 After making sure that you saved anything precious from
-$ git diff '$orig_head'
+$ git diff \$orig_head
 output, run
 $ git reset --hard
-to recover.'
+to recover.")"
 
 fi
 
@@ -241,11 +242,11 @@ case "$merge_head" in
 ?*' '?*)
        if test -z "$orig_head"
        then
-               die "Cannot merge multiple branches into empty head"
+               die "$(gettext "Cannot merge multiple branches into empty head")"
        fi
        if test true = "$rebase"
        then
-               die "Cannot rebase onto multiple branches"
+               die "$(gettext "Cannot rebase onto multiple branches")"
        fi
        ;;
 esac
index 65690af89307abd72749123caddce9e6c4692383..94f36c254c53366ba53d256c0bd50a1de07cdb85 100644 (file)
@@ -472,18 +472,24 @@ do_next () {
                git rev-parse --verify HEAD > "$state_dir"/stopped-sha
                ${SHELL:-@SHELL_PATH@} -c "$rest" # Actual execution
                status=$?
+               # Run in subshell because require_clean_work_tree can die.
+               dirty=f
+               (require_clean_work_tree "rebase" 2>/dev/null) || dirty=t
                if test "$status" -ne 0
                then
                        warn "Execution failed: $rest"
+                       test "$dirty" = f ||
+                       warn "and made changes to the index and/or the working tree"
+
                        warn "You can fix the problem, and then run"
                        warn
                        warn "  git rebase --continue"
                        warn
                        exit "$status"
-               fi
-               # Run in subshell because require_clean_work_tree can die.
-               if ! (require_clean_work_tree "rebase")
+               elif test "$dirty" = t
                then
+                       warn "Execution succeeded: $rest"
+                       warn "but left changes to the index and/or the working tree"
                        warn "Commit or stash your changes, and then run"
                        warn
                        warn "  git rebase --continue"
@@ -647,8 +653,24 @@ continue)
        then
                : Nothing to commit -- skip this
        else
+               if ! test -f "$author_script"
+               then
+                       die "You have staged changes in your working tree. If these changes are meant to be
+squashed into the previous commit, run:
+
+  git commit --amend
+
+If they are meant to go into a new commit, run:
+
+  git commit
+
+In both case, once you're done, continue with:
+
+  git rebase --continue
+"
+               fi
                . "$author_script" ||
-                       die "Cannot find the author identity"
+                       die "Error trying to find the author identity to amend commit"
                current_head=
                if test -f "$amend"
                then
@@ -713,7 +735,6 @@ then
        # parents to rewrite and skipping dropped commits would
        # prematurely end our probe
        merges_option=
-       first_after_upstream="$(git rev-list --reverse --first-parent $upstream..$orig_head | head -n 1)"
 else
        merges_option="--no-merges --cherry-pick"
 fi
@@ -746,7 +767,7 @@ do
                        preserve=t
                        for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)
                        do
-                               if test -f "$rewritten"/$p -a \( $p != $onto -o $sha1 = $first_after_upstream \)
+                               if test -f "$rewritten"/$p
                                then
                                        preserve=f
                                fi
index d7855ea1c6dae8d8cbe35ae39f9c65e7bc9d7b11..6759702c573f51ac00bf1b5c8abccf252cb4ff1d 100755 (executable)
@@ -13,7 +13,7 @@ It then attempts to create a new commit for each commit from the original
 It is possible that a merge failure will prevent this process from being
 completely automatic.  You will have to resolve any such merge failure
 and run git rebase --continue.  Another option is to bypass the commit
-that caused the merge failure with git rebase --skip.  To restore the
+that caused the merge failure with git rebase --skip.  To check out the
 original <branch> and remove the .git/rebase-apply working files, use the
 command git rebase --abort instead.
 
@@ -22,7 +22,7 @@ currently checked out branch is used.
 
 Example:       git-rebase master~1 topic
 
-        A---B---C topic                   A'\''--B'\''--C'\'' topic
+       A---B---C topic                   A'\''--B'\''--C'\'' topic
        /                   -->           /
   D---E---F---G master          D---E---F---G master
 '
@@ -57,9 +57,9 @@ whitespace=!       passed to 'git apply'
 ignore-whitespace! passed to 'git apply'
 C=!                passed to 'git apply'
  Actions:
-continue!          continue rebasing process
-abort!             abort rebasing process and restore original branch
-skip!              skip current patch and continue rebasing process
+continue!          continue
+abort!             abort and check out the original branch
+skip!              skip current patch and continue
 "
 . git-sh-setup
 set_reflog_action rebase
@@ -72,7 +72,7 @@ ok_to_skip_pre_rebase=
 resolvemsg="
 When you have resolved this problem run \"git rebase --continue\".
 If you would prefer to skip this patch, instead run \"git rebase --skip\".
-To restore the original branch and stop rebasing run \"git rebase --abort\".
+To check out the original branch and stop rebasing run \"git rebase --abort\".
 "
 unset onto
 strategy=
@@ -441,8 +441,7 @@ case "$#" in
        then
                head_name="detached HEAD"
        else
-               echo >&2 "fatal: no such branch: $1"
-               usage
+               die "fatal: no such branch: $1"
        fi
        ;;
 *)
index df9d512f1a966635828cb7a8dadde3b0c2b7b9d8..3dc4851cfc70a2804b23a8eca4dac7702ae93c87 100644 (file)
@@ -1,5 +1,18 @@
 #!/usr/bin/env python
 
+# This command is a simple remote-helper, that is used both as a
+# testcase for the remote-helper functionality, and as an example to
+# show remote-helper authors one possible implementation.
+#
+# This is a Git <-> Git importer/exporter, that simply uses git
+# fast-import and git fast-export to consume and produce fast-import
+# streams.
+#
+# To understand better the way things work, one can activate debug
+# traces by setting (to any value) the environment variables
+# GIT_TRANSPORT_HELPER_DEBUG and GIT_DEBUG_TESTGIT, to see messages
+# from the transport-helper side, or from this example remote-helper.
+
 # hashlib is only available in python >= 2.5
 try:
     import hashlib
@@ -35,7 +48,7 @@ def get_repo(alias, url):
     prefix = 'refs/testgit/%s/' % alias
     debug("prefix: '%s'", prefix)
 
-    repo.gitdir = ""
+    repo.gitdir = os.environ["GIT_DIR"]
     repo.alias = alias
     repo.prefix = prefix
 
@@ -70,9 +83,19 @@ def do_capabilities(repo, args):
 
     print "import"
     print "export"
-    print "gitdir"
     print "refspec refs/heads/*:%s*" % repo.prefix
 
+    dirname = repo.get_base_path(repo.gitdir)
+
+    if not os.path.exists(dirname):
+        os.makedirs(dirname)
+
+    path = os.path.join(dirname, 'testgit.marks')
+
+    print "*export-marks %s" % path
+    if os.path.exists(path):
+        print "*import-marks %s" % path
+
     print # end capabilities
 
 
@@ -121,8 +144,24 @@ def do_import(repo, args):
     if not repo.gitdir:
         die("Need gitdir to import")
 
+    ref = args[0]
+    refs = [ref]
+
+    while True:
+        line = sys.stdin.readline()
+        if line == '\n':
+            break
+        if not line.startswith('import '):
+            die("Expected import line.")
+
+        # strip of leading 'import '
+        ref = line[7:].strip()
+        refs.append(ref)
+
     repo = update_local_repo(repo)
-    repo.exporter.export_repo(repo.gitdir)
+    repo.exporter.export_repo(repo.gitdir, refs)
+
+    print "done"
 
 
 def do_export(repo, args):
@@ -132,32 +171,15 @@ def do_export(repo, args):
     if not repo.gitdir:
         die("Need gitdir to export")
 
-    dirname = repo.get_base_path(repo.gitdir)
-
-    if not os.path.exists(dirname):
-        os.makedirs(dirname)
-
-    path = os.path.join(dirname, 'testgit.marks')
-    print path
-    if os.path.exists(path):
-        print path
-    else:
-        print ""
-    sys.stdout.flush()
-
     update_local_repo(repo)
-    repo.importer.do_import(repo.gitdir)
-    repo.non_local.push(repo.gitdir)
-
-
-def do_gitdir(repo, args):
-    """Stores the location of the gitdir.
-    """
+    changed = repo.importer.do_import(repo.gitdir)
 
-    if not args:
-        die("gitdir needs an argument")
+    if not repo.local:
+        repo.non_local.push(repo.gitdir)
 
-    repo.gitdir = ' '.join(args)
+    for ref in changed:
+        print "ok %s" % ref
+    print
 
 
 COMMANDS = {
@@ -165,7 +187,6 @@ COMMANDS = {
     'list': do_list,
     'import': do_import,
     'export': do_export,
-    'gitdir': do_gitdir,
 }
 
 
index 32ca59de82b6a26879128650bf40882f4ed16aa0..e672366f0c3db3b547233af146cd1bba275c0042 100644 (file)
@@ -11,19 +11,38 @@ then
                printf "%s" "$1"
        }
 
+       gettextln() {
+               printf "%s\n" "$1"
+       }
+
        eval_gettext () {
                printf "%s" "$1" | (
                        export PATH $(git sh-i18n--envsubst --variables "$1");
                        git sh-i18n--envsubst "$1"
                )
        }
+
+       eval_gettextln () {
+               printf "%s\n" "$1" | (
+                       export PATH $(git sh-i18n--envsubst --variables "$1");
+                       git sh-i18n--envsubst "$1"
+               )
+       }
 else
        gettext () {
                printf "%s" "# GETTEXT POISON #"
        }
 
+       gettextln () {
+               printf "%s\n" "# GETTEXT POISON #"
+       }
+
        eval_gettext () {
                printf "%s" "# GETTEXT POISON #"
        }
+
+       eval_gettextln () {
+               printf "%s\n" "# GETTEXT POISON #"
+       }
 fi
 
index 94e26ed5e8dcf84c4f238c76b6c508dc84d0b7ea..8e427dab31c27bd78d9eac1ad15bc1f5dcc044c4 100644 (file)
@@ -39,9 +39,15 @@ git_broken_path_fix () {
 
 # @@BROKEN_PATH_FIX@@
 
-die() {
-       echo >&2 "$@"
-       exit 1
+die () {
+       die_with_status 1 "$@"
+}
+
+die_with_status () {
+       status=$1
+       shift
+       echo >&2 "$*"
+       exit "$status"
 }
 
 GIT_QUIET=
index 0a9403653d7dbbb6927973dcfcc41bfd9a904e05..31dec0abf13ee7d6ded313b24253cd12254f1685 100755 (executable)
@@ -7,13 +7,15 @@ USAGE="list [<options>]
    or: $dashless drop [-q|--quiet] [<stash>]
    or: $dashless ( pop | apply ) [--index] [-q|--quiet] [<stash>]
    or: $dashless branch <branchname> [<stash>]
-   or: $dashless [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]]
+   or: $dashless [save [--patch] [-k|--[no-]keep-index] [-q|--quiet]
+                      [-u|--include-untracked] [-a|--all] [<message>]]
    or: $dashless clear"
 
 SUBDIRECTORY_OK=Yes
 OPTIONS_SPEC=
 START_DIR=`pwd`
 . git-sh-setup
+. git-sh-i18n
 require_work_tree
 cd_to_toplevel
 
@@ -33,13 +35,20 @@ fi
 
 no_changes () {
        git diff-index --quiet --cached HEAD --ignore-submodules -- &&
-       git diff-files --quiet --ignore-submodules
+       git diff-files --quiet --ignore-submodules &&
+       (test -z "$untracked" || test -z "$(untracked_files)")
+}
+
+untracked_files () {
+       excl_opt=--exclude-standard
+       test "$untracked" = "all" && excl_opt=
+       git ls-files -o -z $excl_opt
 }
 
 clear_stash () {
        if test $# != 0
        then
-               die "git stash clear with parameters is unimplemented"
+               die "$(gettext "git stash clear with parameters is unimplemented")"
        fi
        if current=$(git rev-parse --verify $ref_stash 2>/dev/null)
        then
@@ -49,6 +58,7 @@ clear_stash () {
 
 create_stash () {
        stash_msg="$1"
+       untracked="$2"
 
        git update-index -q --refresh
        if no_changes
@@ -61,7 +71,7 @@ create_stash () {
        then
                head=$(git rev-list --oneline -n 1 HEAD --)
        else
-               die "You do not have the initial commit yet"
+               die "$(gettext "You do not have the initial commit yet")"
        fi
 
        if branch=$(git symbolic-ref -q HEAD)
@@ -76,7 +86,26 @@ create_stash () {
        i_tree=$(git write-tree) &&
        i_commit=$(printf 'index on %s\n' "$msg" |
                git commit-tree $i_tree -p $b_commit) ||
-               die "Cannot save the current index state"
+               die "$(gettext "Cannot save the current index state")"
+
+       if test -n "$untracked"
+       then
+               # Untracked files are stored by themselves in a parentless commit, for
+               # ease of unpacking later.
+               u_commit=$(
+                       untracked_files | (
+                               export GIT_INDEX_FILE="$TMPindex"
+                               rm -f "$TMPindex" &&
+                               git update-index -z --add --remove --stdin &&
+                               u_tree=$(git write-tree) &&
+                               printf 'untracked files on %s\n' "$msg" | git commit-tree $u_tree  &&
+                               rm -f "$TMPindex"
+               ) ) || die "Cannot save the untracked files"
+
+               untracked_commit_option="-p $u_commit";
+       else
+               untracked_commit_option=
+       fi
 
        if test -z "$patch_mode"
        then
@@ -90,7 +119,7 @@ create_stash () {
                        git write-tree &&
                        rm -f "$TMPindex"
                ) ) ||
-                       die "Cannot save the current worktree state"
+                       die "$(gettext "Cannot save the current worktree state")"
 
        else
 
@@ -103,14 +132,14 @@ create_stash () {
 
                # state of the working tree
                w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) ||
-               die "Cannot save the current worktree state"
+               die "$(gettext "Cannot save the current worktree state")"
 
                git diff-tree -p HEAD $w_tree > "$TMP-patch" &&
                test -s "$TMP-patch" ||
-               die "No changes selected"
+               die "$(gettext "No changes selected")"
 
                rm -f "$TMP-index" ||
-               die "Cannot remove temporary index (can't happen)"
+               die "$(gettext "Cannot remove temporary index (can't happen)")"
 
        fi
 
@@ -122,13 +151,14 @@ create_stash () {
                stash_msg=$(printf 'On %s: %s' "$branch" "$stash_msg")
        fi
        w_commit=$(printf '%s\n' "$stash_msg" |
-               git commit-tree $w_tree -p $b_commit -p $i_commit) ||
-               die "Cannot record working tree state"
+       git commit-tree $w_tree -p $b_commit -p $i_commit $untracked_commit_option) ||
+       die "$(gettext "Cannot record working tree state")"
 }
 
 save_stash () {
        keep_index=
        patch_mode=
+       untracked=
        while test $# != 0
        do
                case "$1" in
@@ -146,13 +176,30 @@ save_stash () {
                -q|--quiet)
                        GIT_QUIET=t
                        ;;
+               -u|--include-untracked)
+                       untracked=untracked
+                       ;;
+               -a|--all)
+                       untracked=all
+                       ;;
                --)
                        shift
                        break
                        ;;
                -*)
-                       echo "error: unknown option for 'stash save': $1"
-                       echo "       To provide a message, use git stash save -- '$1'"
+                       option="$1"
+                       # TRANSLATORS: $option is an invalid option, like
+                       # `--blah-blah'. The 7 spaces at the beginning of the
+                       # second line correspond to "error: ". So you should line
+                       # up the second line with however many characters the
+                       # translation of "error: " takes in your language. E.g. in
+                       # English this is:
+                       #
+                       #    $ git stash save --blah-blah 2>&1 | head -n 2
+                       #    error: unknown option for 'stash save': --blah-blah
+                       #           To provide a message, use git stash save -- '--blah-blah'
+                       eval_gettextln "$("error: unknown option for 'stash save': \$option
+       To provide a message, use git stash save -- '\$option'")"
                        usage
                        ;;
                *)
@@ -162,29 +209,39 @@ save_stash () {
                shift
        done
 
+       if test -n "$patch_mode" && test -n "$untracked"
+       then
+           die "Can't use --patch and ---include-untracked or --all at the same time"
+       fi
+
        stash_msg="$*"
 
        git update-index -q --refresh
        if no_changes
        then
-               say 'No local changes to save'
+               say "$(gettext "No local changes to save")"
                exit 0
        fi
        test -f "$GIT_DIR/logs/$ref_stash" ||
-               clear_stash || die "Cannot initialize stash"
+               clear_stash || die "$(gettext "Cannot initialize stash")"
 
-       create_stash "$stash_msg"
+       create_stash "$stash_msg" $untracked
 
        # Make sure the reflog for stash is kept.
        : >>"$GIT_DIR/logs/$ref_stash"
 
        git update-ref -m "$stash_msg" $ref_stash $w_commit ||
-               die "Cannot save the current status"
+               die "$(gettext "Cannot save the current status")"
        say Saved working directory and index state "$stash_msg"
 
        if test -z "$patch_mode"
        then
                git reset --hard ${GIT_QUIET:+-q}
+               test "$untracked" = "all" && CLEAN_X_OPTION=-x || CLEAN_X_OPTION=
+               if test -n "$untracked"
+               then
+                       git clean --force --quiet $CLEAN_X_OPTION
+               fi
 
                if test "$keep_index" = "t" && test -n $i_tree
                then
@@ -192,7 +249,7 @@ save_stash () {
                fi
        else
                git apply -R < "$TMP-patch" ||
-               die "Cannot remove worktree changes"
+               die "$(gettext "Cannot remove worktree changes")"
 
                if test "$keep_index" != "t"
                then
@@ -234,9 +291,11 @@ show_stash () {
 #   w_commit is set to the commit containing the working tree
 #   b_commit is set to the base commit
 #   i_commit is set to the commit containing the index tree
+#   u_commit is set to the commit containing the untracked files tree
 #   w_tree is set to the working tree
 #   b_tree is set to the base tree
 #   i_tree is set to the index tree
+#   u_tree is set to the untracked files tree
 #
 #   GIT_QUIET is set to t if -q is specified
 #   INDEX_OPTION is set to --index if --index is specified.
@@ -261,9 +320,11 @@ parse_flags_and_rev()
        w_commit=
        b_commit=
        i_commit=
+       u_commit=
        w_tree=
        b_tree=
        i_tree=
+       u_tree=
 
        REV=$(git rev-parse --no-flags --symbolic "$@") || exit 1
 
@@ -287,18 +348,21 @@ parse_flags_and_rev()
 
        case $# in
                0)
-                       have_stash || die "No stash found."
+                       have_stash || die "$(gettext "No stash found.")"
                        set -- ${ref_stash}@{0}
                ;;
                1)
                        :
                ;;
                *)
-                       die "Too many revisions specified: $REV"
+                       die "$(eval_gettext "Too many revisions specified: \$REV")"
                ;;
        esac
 
-       REV=$(git rev-parse --quiet --symbolic --verify $1 2>/dev/null) || die "$1 is not valid reference"
+       REV=$(git rev-parse --quiet --symbolic --verify $1 2>/dev/null) || {
+               reference="$1"
+               die "$(eval_gettext "\$reference is not valid reference")"
+       }
 
        i_commit=$(git rev-parse --quiet --verify $REV^2 2>/dev/null) &&
        set -- $(git rev-parse $REV $REV^1 $REV: $REV^1: $REV^2: 2>/dev/null) &&
@@ -311,6 +375,9 @@ parse_flags_and_rev()
        IS_STASH_LIKE=t &&
        test "$ref_stash" = "$(git rev-parse --symbolic-full-name "${REV%@*}")" &&
        IS_STASH_REF=t
+
+       u_commit=$(git rev-parse --quiet --verify $REV^3 2>/dev/null) &&
+       u_tree=$(git rev-parse $REV^3: 2>/dev/null)
 }
 
 is_stash_like()
@@ -320,7 +387,10 @@ is_stash_like()
 }
 
 assert_stash_like() {
-       is_stash_like "$@" || die "'$*' is not a stash-like commit"
+       is_stash_like "$@" || {
+               args="$*"
+               die "$(eval_gettext "'\$args' is not a stash-like commit")"
+       }
 }
 
 is_stash_ref() {
@@ -328,18 +398,21 @@ is_stash_ref() {
 }
 
 assert_stash_ref() {
-       is_stash_ref "$@" || die "'$*' is not a stash reference"
+       is_stash_ref "$@" || {
+               args="$*"
+               die "$(eval_gettext "'\$args' is not a stash reference")"
+       }
 }
 
 apply_stash () {
 
        assert_stash_like "$@"
 
-       git update-index -q --refresh || die 'unable to refresh index'
+       git update-index -q --refresh || die "$(gettext "unable to refresh index")"
 
        # current index state
        c_tree=$(git write-tree) ||
-               die 'Cannot apply a stash in the middle of a merge'
+               die "$(gettext "Cannot apply a stash in the middle of a merge")"
 
        unstashed_index_tree=
        if test -n "$INDEX_OPTION" && test "$b_tree" != "$i_tree" &&
@@ -347,12 +420,20 @@ apply_stash () {
        then
                git diff-tree --binary $s^2^..$s^2 | git apply --cached
                test $? -ne 0 &&
-                       die 'Conflicts in index. Try without --index.'
+                       die "$(gettext "Conflicts in index. Try without --index.")"
                unstashed_index_tree=$(git write-tree) ||
-                       die 'Could not save index tree'
+                       die "$(gettext "Could not save index tree")"
                git reset
        fi
 
+       if test -n "$u_tree"
+       then
+               GIT_INDEX_FILE="$TMPindex" git-read-tree "$u_tree" &&
+               GIT_INDEX_FILE="$TMPindex" git checkout-index --all &&
+               rm -f "$TMPindex" ||
+               die 'Could not restore untracked files from stash'
+       fi
+
        eval "
                GITHEAD_$w_tree='Stashed changes' &&
                GITHEAD_$c_tree='Updated upstream' &&
@@ -375,7 +456,7 @@ apply_stash () {
                        git diff-index --cached --name-only --diff-filter=A $c_tree >"$a" &&
                        git read-tree --reset $c_tree &&
                        git update-index --add --stdin <"$a" ||
-                               die "Cannot unstage modified files"
+                               die "$(gettext "Cannot unstage modified files")"
                        rm -f "$a"
                fi
                squelch=
@@ -389,7 +470,7 @@ apply_stash () {
                status=$?
                if test -n "$INDEX_OPTION"
                then
-                       echo >&2 'Index was not unstashed.'
+                       gettextln "Index was not unstashed." >&2
                fi
                exit $status
        fi
@@ -406,14 +487,15 @@ drop_stash () {
        assert_stash_ref "$@"
 
        git reflog delete --updateref --rewrite "${REV}" &&
-               say "Dropped ${REV} ($s)" || die "${REV}: Could not drop stash entry"
+               say "$(eval_gettext "Dropped \${REV} (\$s)")" ||
+               die "$(eval_gettext "\${REV}: Could not drop stash entry")"
 
        # clear_stash if we just dropped the last stash entry
        git rev-parse --verify "$ref_stash@{0}" > /dev/null 2>&1 || clear_stash
 }
 
 apply_to_branch () {
-       test -n "$1" || die 'No branch name specified'
+       test -n "$1" || die "$(gettext "No branch name specified")"
        branch=$1
        shift 1
 
@@ -484,7 +566,7 @@ branch)
        case $# in
        0)
                save_stash &&
-               say '(To restore them type "git stash apply")'
+               say "$(gettext "(To restore them type \"git stash apply\")")"
                ;;
        *)
                usage
index d189a24c71c44806a9c1381e2a8e5993269e568a..814d0d914eaf994462e61559b0692c958f97513b 100755 (executable)
@@ -14,6 +14,7 @@ USAGE="[--quiet] add [-b branch] [-f|--force] [--reference <repository>] [--] <r
    or: $dashless [--quiet] sync [--] [<path>...]"
 OPTIONS_SPEC=
 . git-sh-setup
+. git-sh-i18n
 . git-parse-remote
 require_work_tree
 
@@ -34,7 +35,7 @@ resolve_relative_url ()
 {
        remote=$(get_default_remote)
        remoteurl=$(git config "remote.$remote.url") ||
-               die "remote ($remote) does not have a url defined in .git/config"
+               remoteurl=$(pwd) # the repository is its own authoritative upstream
        url="$1"
        remoteurl=${remoteurl%/}
        sep=/
@@ -52,7 +53,7 @@ resolve_relative_url ()
                                sep=:
                                ;;
                        *)
-                               die "cannot strip one component off url '$remoteurl'"
+                               die "$(eval_gettext "cannot strip one component off url '\$remoteurl'")"
                                ;;
                        esac
                        ;;
@@ -104,7 +105,7 @@ module_name()
        name=$( git config -f .gitmodules --get-regexp '^submodule\..*\.path$' |
                sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' )
        test -z "$name" &&
-       die "No submodule mapping found in .gitmodules for path '$path'"
+       die "$(eval_gettext "No submodule mapping found in .gitmodules for path '\$path'")"
        echo "$name"
 }
 
@@ -121,14 +122,19 @@ module_clone()
        path=$1
        url=$2
        reference="$3"
+       quiet=
+       if test -n "$GIT_QUIET"
+       then
+               quiet=-q
+       fi
 
        if test -n "$reference"
        then
-               git-clone "$reference" -n "$url" "$path"
+               git-clone $quiet "$reference" -n "$url" "$path"
        else
-               git-clone -n "$url" "$path"
+               git-clone $quiet -n "$url" "$path"
        fi ||
-       die "Clone of '$url' into submodule path '$path' failed"
+       die "$(eval_gettext "Clone of '\$url' into submodule path '\$path' failed")"
 }
 
 #
@@ -201,7 +207,7 @@ cmd_add()
                realrepo=$repo
                ;;
        *)
-               die "repo URL: '$repo' must be absolute or begin with ./|../"
+               die "$(eval_gettext "repo URL: '\$repo' must be absolute or begin with ./|../")"
        ;;
        esac
 
@@ -218,13 +224,13 @@ cmd_add()
                        s|/*$||
                ')
        git ls-files --error-unmatch "$path" > /dev/null 2>&1 &&
-       die "'$path' already exists in the index"
+       die "$(eval_gettext "'\$path' already exists in the index")"
 
        if test -z "$force" && ! git add --dry-run --ignore-missing "$path" > /dev/null 2>&1
        then
-               echo >&2 "The following path is ignored by one of your .gitignore files:" &&
-               echo >&2 $path &&
-               echo >&2 "Use -f if you really want to add it."
+               eval_gettextln "The following path is ignored by one of your .gitignore files:
+\$path
+Use -f if you really want to add it." >&2
                exit 1
        fi
 
@@ -233,20 +239,11 @@ cmd_add()
        then
                if test -d "$path"/.git -o -f "$path"/.git
                then
-                       echo "Adding existing repo at '$path' to the index"
+                       eval_gettextln "Adding existing repo at '\$path' to the index"
                else
-                       die "'$path' already exists and is not a valid git repo"
+                       die "$(eval_gettext "'\$path' already exists and is not a valid git repo")"
                fi
 
-               case "$repo" in
-               ./*|../*)
-                       url=$(resolve_relative_url "$repo") || exit
-                   ;;
-               *)
-                       url="$repo"
-                       ;;
-               esac
-               git config submodule."$path".url "$url"
        else
 
                module_clone "$path" "$realrepo" "$reference" || exit
@@ -258,16 +255,17 @@ cmd_add()
                        '') git checkout -f -q ;;
                        ?*) git checkout -f -q -B "$branch" "origin/$branch" ;;
                        esac
-               ) || die "Unable to checkout submodule '$path'"
+               ) || die "$(eval_gettext "Unable to checkout submodule '\$path'")"
        fi
+       git config submodule."$path".url "$realrepo"
 
        git add $force "$path" ||
-       die "Failed to add submodule '$path'"
+       die "$(eval_gettext "Failed to add submodule '\$path'")"
 
        git config -f .gitmodules submodule."$path".path "$path" &&
        git config -f .gitmodules submodule."$path".url "$repo" &&
        git add --force .gitmodules ||
-       die "Failed to register submodule '$path'"
+       die "$(eval_gettext "Failed to register submodule '\$path'")"
 }
 
 #
@@ -300,12 +298,16 @@ cmd_foreach()
 
        toplevel=$(pwd)
 
+       # dup stdin so that it can be restored when running the external
+       # command in the subshell (and a recursive call to this function)
+       exec 3<&0
+
        module_list |
        while read mode sha1 stage path
        do
                if test -e "$path"/.git
                then
-                       say "Entering '$prefix$path'"
+                       say "$(eval_gettext "Entering '\$prefix\$path'")"
                        name=$(module_name "$path")
                        (
                                prefix="$prefix$path/"
@@ -316,8 +318,8 @@ cmd_foreach()
                                then
                                        cmd_foreach "--recursive" "$@"
                                fi
-                       ) ||
-                       die "Stopping at '$path'; script returned non-zero status."
+                       ) <&3 3<&- ||
+                       die "$(eval_gettext "Stopping at '\$path'; script returned non-zero status.")"
                fi
        done
 }
@@ -355,29 +357,30 @@ cmd_init()
        do
                # Skip already registered paths
                name=$(module_name "$path") || exit
-               url=$(git config submodule."$name".url)
-               test -z "$url" || continue
-
-               url=$(git config -f .gitmodules submodule."$name".url)
-               test -z "$url" &&
-               die "No url found for submodule path '$path' in .gitmodules"
-
-               # Possibly a url relative to parent
-               case "$url" in
-               ./*|../*)
-                       url=$(resolve_relative_url "$url") || exit
-                       ;;
-               esac
-
-               git config submodule."$name".url "$url" ||
-               die "Failed to register url for submodule path '$path'"
+               if test -z "$(git config "submodule.$name.url")"
+               then
+                       url=$(git config -f .gitmodules submodule."$name".url)
+                       test -z "$url" &&
+                       die "$(eval_gettext "No url found for submodule path '\$path' in .gitmodules")"
+
+                       # Possibly a url relative to parent
+                       case "$url" in
+                       ./*|../*)
+                               url=$(resolve_relative_url "$url") || exit
+                               ;;
+                       esac
+                       git config submodule."$name".url "$url" ||
+                       die "$(eval_gettext "Failed to register url for submodule path '\$path'")"
+               fi
 
+               # Copy "update" setting when it is not set yet
                upd="$(git config -f .gitmodules submodule."$name".update)"
                test -z "$upd" ||
+               test -n "$(git config submodule."$name".update)" ||
                git config submodule."$name".update "$upd" ||
-               die "Failed to register update mode for submodule path '$path'"
+               die "$(eval_gettext "Failed to register update mode for submodule path '\$path'")"
 
-               say "Submodule '$name' ($url) registered for path '$path'"
+               say "$(eval_gettext "Submodule '\$name' (\$url) registered for path '\$path'")"
        done
 }
 
@@ -444,7 +447,8 @@ cmd_update()
        fi
 
        cloned_modules=
-       module_list "$@" |
+       module_list "$@" | {
+       err=
        while read mode sha1 stage path
        do
                if test "$stage" = U
@@ -460,8 +464,8 @@ cmd_update()
                        # Only mention uninitialized submodules when its
                        # path have been specified
                        test "$#" != "0" &&
-                       say "Submodule path '$path' not initialized" &&
-                       say "Maybe you want to use 'update --init'?"
+                       say "$(eval_gettext "Submodule path '\$path' not initialized
+Maybe you want to use 'update --init'?")"
                        continue
                fi
 
@@ -473,7 +477,7 @@ cmd_update()
                else
                        subsha1=$(clear_local_git_env; cd "$path" &&
                                git rev-parse --verify HEAD) ||
-                       die "Unable to find current revision in submodule path '$path'"
+                       die "$(eval_gettext "Unable to find current revision in submodule path '\$path'")"
                fi
 
                if ! test -z "$update"
@@ -497,7 +501,7 @@ cmd_update()
                                (clear_local_git_env; cd "$path" &&
                                        ( (rev=$(git rev-list -n 1 $sha1 --not --all 2>/dev/null) &&
                                         test -z "$rev") || git-fetch)) ||
-                               die "Unable to fetch in submodule path '$path'"
+                               die "$(eval_gettext "Unable to fetch in submodule path '\$path'")"
                        fi
 
                        # Is this something we just cloned?
@@ -507,35 +511,72 @@ cmd_update()
                                update_module= ;;
                        esac
 
+                       must_die_on_failure=
                        case "$update_module" in
                        rebase)
                                command="git rebase"
-                               action="rebase"
-                               msg="rebased onto"
+                               die_msg="$(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$path'")"
+                               say_msg="$(eval_gettext "Submodule path '\$path': rebased into '\$sha1'")"
+                               must_die_on_failure=yes
                                ;;
                        merge)
                                command="git merge"
-                               action="merge"
-                               msg="merged in"
+                               die_msg="$(eval_gettext "Unable to merge '\$sha1' in submodule path '\$path'")"
+                               say_msg="$(eval_gettext "Submodule path '\$path': merged in '\$sha1'")"
+                               must_die_on_failure=yes
                                ;;
                        *)
                                command="git checkout $subforce -q"
-                               action="checkout"
-                               msg="checked out"
+                               die_msg="$(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$path'")"
+                               say_msg="$(eval_gettext "Submodule path '\$path': checked out '\$sha1'")"
                                ;;
                        esac
 
-                       (clear_local_git_env; cd "$path" && $command "$sha1") ||
-                       die "Unable to $action '$sha1' in submodule path '$path'"
-                       say "Submodule path '$path': $msg '$sha1'"
+                       if (clear_local_git_env; cd "$path" && $command "$sha1")
+                       then
+                               say "$say_msg"
+                       elif test -n "$must_die_on_failure"
+                       then
+                               die_with_status 2 "$die_msg"
+                       else
+                               err="${err};$die_msg"
+                               continue
+                       fi
                fi
 
                if test -n "$recursive"
                then
-                       (clear_local_git_env; cd "$path" && eval cmd_update "$orig_flags") ||
-                       die "Failed to recurse into submodule path '$path'"
+                       (clear_local_git_env; cd "$path" && eval cmd_update "$orig_flags")
+                       res=$?
+                       if test $res -gt 0
+                       then
+                               die_msg="$(eval_gettext "Failed to recurse into submodule path '\$path'")"
+                               if test $res -eq 1
+                               then
+                                       err="${err};$die_msg"
+                                       continue
+                               else
+                                       die_with_status $res "$die_msg"
+                               fi
+                       fi
                fi
        done
+
+       if test -n "$err"
+       then
+               OIFS=$IFS
+               IFS=';'
+               for e in $err
+               do
+                       if test -n "$e"
+                       then
+                               echo >&2 "$e"
+                       fi
+               done
+               IFS=$OIFS
+               exit 1
+       fi
+       }
 }
 
 set_name_rev () {
@@ -617,7 +658,7 @@ cmd_summary() {
        if [ -n "$files" ]
        then
                test -n "$cached" &&
-               die "--cached cannot be used with --files"
+               die "$(gettext -- "--cached cannot be used with --files")"
                diff_cmd=diff-files
                head=
        fi
@@ -657,7 +698,7 @@ cmd_summary() {
                                ;; # removed
                        *)
                                # unexpected type
-                               echo >&2 "unexpected mode $mod_dst"
+                               eval_gettextln "unexpected mode \$mod_dst" >&2
                                continue ;;
                        esac
                fi
@@ -675,13 +716,13 @@ cmd_summary() {
                total_commits=
                case "$missing_src,$missing_dst" in
                t,)
-                       errmsg="  Warn: $name doesn't contain commit $sha1_src"
+                       errmsg="$(eval_gettext "  Warn: \$name doesn't contain commit \$sha1_src")"
                        ;;
                ,t)
-                       errmsg="  Warn: $name doesn't contain commit $sha1_dst"
+                       errmsg="$(eval_gettext "  Warn: \$name doesn't contain commit \$sha1_dst")"
                        ;;
                t,t)
-                       errmsg="  Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
+                       errmsg="$(eval_gettext "  Warn: \$name doesn't contain commits \$sha1_src and \$sha1_dst")"
                        ;;
                *)
                        errmsg=
@@ -706,11 +747,13 @@ cmd_summary() {
                sha1_abbr_dst=$(echo $sha1_dst | cut -c1-7)
                if test $status = T
                then
+                       blob="$(gettext "blob")"
+                       submodule="$(gettext "submodule")"
                        if test $mod_dst = 160000
                        then
-                               echo "* $name $sha1_abbr_src(blob)->$sha1_abbr_dst(submodule)$total_commits:"
+                               echo "* $name $sha1_abbr_src($blob)->$sha1_abbr_dst($submodule)$total_commits:"
                        else
-                               echo "* $name $sha1_abbr_src(submodule)->$sha1_abbr_dst(blob)$total_commits:"
+                               echo "* $name $sha1_abbr_src($submodule)->$sha1_abbr_dst($blob)$total_commits:"
                        fi
                else
                        echo "* $name $sha1_abbr_src...$sha1_abbr_dst$total_commits:"
@@ -742,9 +785,9 @@ cmd_summary() {
        done |
        if test -n "$for_status"; then
                if [ -n "$files" ]; then
-                       echo "# Submodules changed but not updated:"
+                       gettextln "# Submodules changed but not updated:"
                else
-                       echo "# Submodule changes to be committed:"
+                       gettextln "# Submodule changes to be committed:"
                fi
                echo "#"
                sed -e 's|^|# |' -e 's|^# $|#|'
@@ -830,7 +873,7 @@ cmd_status()
                                cd "$path" &&
                                eval cmd_status "$orig_args"
                        ) ||
-                       die "Failed to recurse into submodule path '$path'"
+                       die "$(eval_gettext "Failed to recurse into submodule path '\$path'")"
                fi
        done
 }
@@ -874,17 +917,20 @@ cmd_sync()
                        ;;
                esac
 
-               say "Synchronizing submodule url for '$name'"
-               git config submodule."$name".url "$url"
-
-               if test -e "$path"/.git
+               if git config "submodule.$name.url" >/dev/null 2>/dev/null
                then
-               (
-                       clear_local_git_env
-                       cd "$path"
-                       remote=$(get_default_remote)
-                       git config remote."$remote".url "$url"
-               )
+                       say "$(eval_gettext "Synchronizing submodule url for '\$name'")"
+                       git config submodule."$name".url "$url"
+
+                       if test -e "$path"/.git
+                       then
+                       (
+                               clear_local_git_env
+                               cd "$path"
+                               remote=$(get_default_remote)
+                               git config remote."$remote".url "$url"
+                       )
+                       fi
                fi
        done
 }
index 7849cfc141d384bc28479c2f37fd128c77fe0fbe..351e743a902568c8b6f68ffaf4030f087ba8568d 100755 (executable)
@@ -89,6 +89,7 @@ my ($_stdin, $_help, $_edit,
        $_prefix, $_no_checkout, $_url, $_verbose,
        $_git_format, $_commit_url, $_tag, $_merge_info);
 $Git::SVN::_follow_parent = 1;
+$SVN::Git::Fetcher::_placeholder_filename = ".gitignore";
 $_q ||= 0;
 my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
                     'config-dir=s' => \$Git::SVN::Ra::config_dir,
@@ -139,6 +140,10 @@ my %cmd = (
                           %fc_opts } ],
        clone => [ \&cmd_clone, "Initialize and fetch revisions",
                        { 'revision|r=s' => \$_revision,
+                         'preserve-empty-dirs' =>
+                               \$SVN::Git::Fetcher::_preserve_empty_dirs,
+                         'placeholder-filename=s' =>
+                               \$SVN::Git::Fetcher::_placeholder_filename,
                           %fc_opts, %init_opts } ],
        init => [ \&cmd_init, "Initialize a repo for tracking" .
                          " (requires URL argument)",
@@ -386,6 +391,12 @@ sub do_git_init_db {
        my $ignore_regex = \$SVN::Git::Fetcher::_ignore_regex;
        command_noisy('config', "$pfx.ignore-paths", $$ignore_regex)
                if defined $$ignore_regex;
+
+       if (defined $SVN::Git::Fetcher::_preserve_empty_dirs) {
+               my $fname = \$SVN::Git::Fetcher::_placeholder_filename;
+               command_noisy('config', "$pfx.preserve-empty-dirs", 'true');
+               command_noisy('config', "$pfx.placeholder-filename", $$fname);
+       }
 }
 
 sub init_subdir {
@@ -497,6 +508,195 @@ sub cmd_set_tree {
        unlink $gs->{index};
 }
 
+sub split_merge_info_range {
+       my ($range) = @_;
+       if ($range =~ /(\d+)-(\d+)/) {
+               return (int($1), int($2));
+       } else {
+               return (int($range), int($range));
+       }
+}
+
+sub combine_ranges {
+       my ($in) = @_;
+
+       my @fnums = ();
+       my @arr = split(/,/, $in);
+       for my $element (@arr) {
+               my ($start, $end) = split_merge_info_range($element);
+               push @fnums, $start;
+       }
+
+       my @sorted = @arr [ sort {
+               $fnums[$a] <=> $fnums[$b]
+       } 0..$#arr ];
+
+       my @return = ();
+       my $last = -1;
+       my $first = -1;
+       for my $element (@sorted) {
+               my ($start, $end) = split_merge_info_range($element);
+
+               if ($last == -1) {
+                       $first = $start;
+                       $last = $end;
+                       next;
+               }
+               if ($start <= $last+1) {
+                       if ($end > $last) {
+                               $last = $end;
+                       }
+                       next;
+               }
+               if ($first == $last) {
+                       push @return, "$first";
+               } else {
+                       push @return, "$first-$last";
+               }
+               $first = $start;
+               $last = $end;
+       }
+
+       if ($first != -1) {
+               if ($first == $last) {
+                       push @return, "$first";
+               } else {
+                       push @return, "$first-$last";
+               }
+       }
+
+       return join(',', @return);
+}
+
+sub merge_revs_into_hash {
+       my ($hash, $minfo) = @_;
+       my @lines = split(' ', $minfo);
+
+       for my $line (@lines) {
+               my ($branchpath, $revs) = split(/:/, $line);
+
+               if (exists($hash->{$branchpath})) {
+                       # Merge the two revision sets
+                       my $combined = "$hash->{$branchpath},$revs";
+                       $hash->{$branchpath} = combine_ranges($combined);
+               } else {
+                       # Just do range combining for consolidation
+                       $hash->{$branchpath} = combine_ranges($revs);
+               }
+       }
+}
+
+sub merge_merge_info {
+       my ($mergeinfo_one, $mergeinfo_two) = @_;
+       my %result_hash = ();
+
+       merge_revs_into_hash(\%result_hash, $mergeinfo_one);
+       merge_revs_into_hash(\%result_hash, $mergeinfo_two);
+
+       my $result = '';
+       # Sort below is for consistency's sake
+       for my $branchname (sort keys(%result_hash)) {
+               my $revlist = $result_hash{$branchname};
+               $result .= "$branchname:$revlist\n"
+       }
+       return $result;
+}
+
+sub populate_merge_info {
+       my ($d, $gs, $uuid, $linear_refs, $rewritten_parent) = @_;
+
+       my %parentshash;
+       read_commit_parents(\%parentshash, $d);
+       my @parents = @{$parentshash{$d}};
+       if ($#parents > 0) {
+               # Merge commit
+               my $all_parents_ok = 1;
+               my $aggregate_mergeinfo = '';
+               my $rooturl = $gs->repos_root;
+
+               if (defined($rewritten_parent)) {
+                       # Replace first parent with newly-rewritten version
+                       shift @parents;
+                       unshift @parents, $rewritten_parent;
+               }
+
+               foreach my $parent (@parents) {
+                       my ($branchurl, $svnrev, $paruuid) =
+                               cmt_metadata($parent);
+
+                       unless (defined($svnrev)) {
+                               # Should have been caught be preflight check
+                               fatal "merge commit $d has ancestor $parent, but that change "
+                     ."does not have git-svn metadata!";
+                       }
+                       unless ($branchurl =~ /^$rooturl(.*)/) {
+                               fatal "commit $parent git-svn metadata changed mid-run!";
+                       }
+                       my $branchpath = $1;
+
+                       my $ra = Git::SVN::Ra->new($branchurl);
+                       my (undef, undef, $props) =
+                               $ra->get_dir(canonicalize_path("."), $svnrev);
+                       my $par_mergeinfo = $props->{'svn:mergeinfo'};
+                       unless (defined $par_mergeinfo) {
+                               $par_mergeinfo = '';
+                       }
+                       # Merge previous mergeinfo values
+                       $aggregate_mergeinfo =
+                               merge_merge_info($aggregate_mergeinfo,
+                                                                $par_mergeinfo, 0);
+
+                       next if $parent eq $parents[0]; # Skip first parent
+                       # Add new changes being placed in tree by merge
+                       my @cmd = (qw/rev-list --reverse/,
+                                          $parent, qw/--not/);
+                       foreach my $par (@parents) {
+                               unless ($par eq $parent) {
+                                       push @cmd, $par;
+                               }
+                       }
+                       my @revsin = ();
+                       my ($revlist, $ctx) = command_output_pipe(@cmd);
+                       while (<$revlist>) {
+                               my $irev = $_;
+                               chomp $irev;
+                               my (undef, $csvnrev, undef) =
+                                       cmt_metadata($irev);
+                               unless (defined $csvnrev) {
+                                       # A child is missing SVN annotations...
+                                       # this might be OK, or might not be.
+                                       warn "W:child $irev is merged into revision "
+                                                ."$d but does not have git-svn metadata. "
+                                                ."This means git-svn cannot determine the "
+                                                ."svn revision numbers to place into the "
+                                                ."svn:mergeinfo property. You must ensure "
+                                                ."a branch is entirely committed to "
+                                                ."SVN before merging it in order for "
+                                                ."svn:mergeinfo population to function "
+                                                ."properly";
+                               }
+                               push @revsin, $csvnrev;
+                       }
+                       command_close_pipe($revlist, $ctx);
+
+                       last unless $all_parents_ok;
+
+                       # We now have a list of all SVN revnos which are
+                       # merged by this particular parent. Integrate them.
+                       next if $#revsin == -1;
+                       my $newmergeinfo = "$branchpath:" . join(',', @revsin);
+                       $aggregate_mergeinfo =
+                               merge_merge_info($aggregate_mergeinfo,
+                                                                $newmergeinfo, 1);
+               }
+               if ($all_parents_ok and $aggregate_mergeinfo) {
+                       return $aggregate_mergeinfo;
+               }
+       }
+
+       return undef;
+}
+
 sub cmd_dcommit {
        my $head = shift;
        command_noisy(qw/update-index --refresh/);
@@ -547,7 +747,66 @@ sub cmd_dcommit {
                     "without --no-rebase may be required."
        }
        my $expect_url = $url;
+
+       my $push_merge_info = eval {
+               command_oneline(qw/config --get svn.pushmergeinfo/)
+               };
+       if (not defined($push_merge_info)
+                       or $push_merge_info eq "false"
+                       or $push_merge_info eq "no"
+                       or $push_merge_info eq "never") {
+               $push_merge_info = 0;
+       }
+
+       unless (defined($_merge_info) || ! $push_merge_info) {
+               # Preflight check of changes to ensure no issues with mergeinfo
+               # This includes check for uncommitted-to-SVN parents
+               # (other than the first parent, which we will handle),
+               # information from different SVN repos, and paths
+               # which are not underneath this repository root.
+               my $rooturl = $gs->repos_root;
+               foreach my $d (@$linear_refs) {
+                       my %parentshash;
+                       read_commit_parents(\%parentshash, $d);
+                       my @realparents = @{$parentshash{$d}};
+                       if ($#realparents > 0) {
+                               # Merge commit
+                               shift @realparents; # Remove/ignore first parent
+                               foreach my $parent (@realparents) {
+                                       my ($branchurl, $svnrev, $paruuid) = cmt_metadata($parent);
+                                       unless (defined $paruuid) {
+                                               # A parent is missing SVN annotations...
+                                               # abort the whole operation.
+                                               fatal "$parent is merged into revision $d, "
+                                                        ."but does not have git-svn metadata. "
+                                                        ."Either dcommit the branch or use a "
+                                                        ."local cherry-pick, FF merge, or rebase "
+                                                        ."instead of an explicit merge commit.";
+                                       }
+
+                                       unless ($paruuid eq $uuid) {
+                                               # Parent has SVN metadata from different repository
+                                               fatal "merge parent $parent for change $d has "
+                                                        ."git-svn uuid $paruuid, while current change "
+                                                        ."has uuid $uuid!";
+                                       }
+
+                                       unless ($branchurl =~ /^$rooturl(.*)/) {
+                                               # This branch is very strange indeed.
+                                               fatal "merge parent $parent for $d is on branch "
+                                                        ."$branchurl, which is not under the "
+                                                        ."git-svn root $rooturl!";
+                                       }
+                               }
+                       }
+               }
+       }
+
+       my $rewritten_parent;
        Git::SVN::remove_username($expect_url);
+       if (defined($_merge_info)) {
+               $_merge_info =~ tr{ }{\n};
+       }
        while (1) {
                my $d = shift @$linear_refs or last;
                unless (defined $last_rev) {
@@ -561,6 +820,14 @@ sub cmd_dcommit {
                        print "diff-tree $d~1 $d\n";
                } else {
                        my $cmt_rev;
+
+                       unless (defined($_merge_info) || ! $push_merge_info) {
+                               $_merge_info = populate_merge_info($d, $gs,
+                                                            $uuid,
+                                                            $linear_refs,
+                                                            $rewritten_parent);
+                       }
+
                        my %ed_opts = ( r => $last_rev,
                                        log => get_commit_entry($d)->{log},
                                        ra => Git::SVN::Ra->new($url),
@@ -603,6 +870,9 @@ sub cmd_dcommit {
                                @finish = qw/reset --mixed/;
                        }
                        command_noisy(@finish, $gs->refname);
+
+                       $rewritten_parent = command_oneline(qw/rev-parse HEAD/);
+
                        if (@diff) {
                                @refs = ();
                                my ($url_, $rev_, $uuid_, $gs_) =
@@ -3011,7 +3281,7 @@ sub other_gs {
                        my (undef, $max_commit) = $gs->rev_map_max(1);
                        last if (!$max_commit);
                        my ($url) = ::cmt_metadata($max_commit);
-                       last if ($url eq $gs->full_url);
+                       last if ($url eq $gs->metadata_url);
                        $ref_id .= '-';
                }
                print STDERR "Initializing parent: $ref_id\n" unless $::_q > 1;
@@ -3124,8 +3394,12 @@ sub lookup_svn_merge {
                        next;
                }
 
-               push @merged_commit_ranges,
-                       "$bottom_commit^..$top_commit";
+               if (scalar(command('rev-parse', "$bottom_commit^@"))) {
+                       push @merged_commit_ranges,
+                            "$bottom_commit^..$top_commit";
+               } else {
+                       push @merged_commit_ranges, "$top_commit";
+               }
 
                if ( !defined $tip or $top > $tip ) {
                        $tip = $top;
@@ -3154,9 +3428,9 @@ sub check_cherry_pick {
        my $parents = shift;
        my @ranges = @_;
        my %commits = map { $_ => 1 }
-               _rev_list("--no-merges", $tip, "--not", $base, @$parents);
+               _rev_list("--no-merges", $tip, "--not", $base, @$parents, "--");
        for my $range ( @ranges ) {
-               delete @commits{_rev_list($range)};
+               delete @commits{_rev_list($range, "--")};
        }
        for my $commit (keys %commits) {
                if (has_no_changes($commit)) {
@@ -4076,12 +4350,13 @@ sub _read_password {
 }
 
 package SVN::Git::Fetcher;
-use vars qw/@ISA/;
+use vars qw/@ISA $_ignore_regex $_preserve_empty_dirs $_placeholder_filename
+            @deleted_gpath %added_placeholder $repo_id/;
 use strict;
 use warnings;
 use Carp qw/croak/;
+use File::Basename qw/dirname/;
 use IO::File qw//;
-use vars qw/$_ignore_regex/;
 
 # file baton members: path, mode_a, mode_b, pool, fh, blob, base
 sub new {
@@ -4093,8 +4368,34 @@ sub new {
                $self->{empty_symlinks} =
                                  _mark_empty_symlinks($git_svn, $switch_path);
        }
-       $self->{ignore_regex} = eval { command_oneline('config', '--get',
-                            "svn-remote.$git_svn->{repo_id}.ignore-paths") };
+
+       # some options are read globally, but can be overridden locally
+       # per [svn-remote "..."] section.  Command-line options will *NOT*
+       # override options set in an [svn-remote "..."] section
+       $repo_id = $git_svn->{repo_id};
+       my $k = "svn-remote.$repo_id.ignore-paths";
+       my $v = eval { command_oneline('config', '--get', $k) };
+       $self->{ignore_regex} = $v;
+
+       $k = "svn-remote.$repo_id.preserve-empty-dirs";
+       $v = eval { command_oneline('config', '--get', '--bool', $k) };
+       if ($v && $v eq 'true') {
+               $_preserve_empty_dirs = 1;
+               $k = "svn-remote.$repo_id.placeholder-filename";
+               $v = eval { command_oneline('config', '--get', $k) };
+               $_placeholder_filename = $v;
+       }
+
+       # Load the list of placeholder files added during previous invocations.
+       $k = "svn-remote.$repo_id.added-placeholder";
+       $v = eval { command_oneline('config', '--get-all', $k) };
+       if ($_preserve_empty_dirs && $v) {
+               # command() prints errors to stderr, so we only call it if
+               # command_oneline() succeeded.
+               my @v = command('config', '--get-all', $k);
+               $added_placeholder{ dirname($_) } = $_ foreach @v;
+       }
+
        $self->{empty} = {};
        $self->{dir_prop} = {};
        $self->{file_prop} = {};
@@ -4223,6 +4524,8 @@ sub delete_entry {
                $self->{gii}->remove($gpath);
                print "\tD\t$gpath\n" unless $::_q;
        }
+       # Don't add to @deleted_gpath if we're deleting a placeholder file.
+       push @deleted_gpath, $gpath unless $added_placeholder{dirname($path)};
        $self->{empty}->{$path} = 0;
        undef;
 }
@@ -4255,7 +4558,15 @@ sub add_file {
                my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
                delete $self->{empty}->{$dir};
                $mode = '100644';
+
+               if ($added_placeholder{$dir}) {
+                       # Remove our placeholder file, if we created one.
+                       delete_entry($self, $added_placeholder{$dir})
+                               unless $path eq $added_placeholder{$dir};
+                       delete $added_placeholder{$dir}
+               }
        }
+
        { path => $path, mode_a => $mode, mode_b => $mode,
          pool => SVN::Pool->new, action => 'A' };
 }
@@ -4273,6 +4584,7 @@ sub add_directory {
                        chomp;
                        $self->{gii}->remove($_);
                        print "\tD\t$_\n" unless $::_q;
+                       push @deleted_gpath, $gpath;
                }
                command_close_pipe($ls, $ctx);
                $self->{empty}->{$path} = 0;
@@ -4280,6 +4592,13 @@ sub add_directory {
        my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
        delete $self->{empty}->{$dir};
        $self->{empty}->{$path} = 1;
+
+       if ($added_placeholder{$dir}) {
+               # Remove our placeholder file, if we created one.
+               delete_entry($self, $added_placeholder{$dir});
+               delete $added_placeholder{$dir}
+       }
+
 out:
        { path => $path };
 }
@@ -4443,12 +4762,96 @@ sub abort_edit {
 
 sub close_edit {
        my $self = shift;
+
+       if ($_preserve_empty_dirs) {
+               my @empty_dirs;
+
+               # Any entry flagged as empty that also has an associated
+               # dir_prop represents a newly created empty directory.
+               foreach my $i (keys %{$self->{empty}}) {
+                       push @empty_dirs, $i if exists $self->{dir_prop}->{$i};
+               }
+
+               # Search for directories that have become empty due subsequent
+               # file deletes.
+               push @empty_dirs, $self->find_empty_directories();
+
+               # Finally, add a placeholder file to each empty directory.
+               $self->add_placeholder_file($_) foreach (@empty_dirs);
+
+               $self->stash_placeholder_list();
+       }
+
        $self->{git_commit_ok} = 1;
        $self->{nr} = $self->{gii}->{nr};
        delete $self->{gii};
        $self->SUPER::close_edit(@_);
 }
 
+sub find_empty_directories {
+       my ($self) = @_;
+       my @empty_dirs;
+       my %dirs = map { dirname($_) => 1 } @deleted_gpath;
+
+       foreach my $dir (sort keys %dirs) {
+               next if $dir eq ".";
+
+               # If there have been any additions to this directory, there is
+               # no reason to check if it is empty.
+               my $skip_added = 0;
+               foreach my $t (qw/dir_prop file_prop/) {
+                       foreach my $path (keys %{ $self->{$t} }) {
+                               if (exists $self->{$t}->{dirname($path)}) {
+                                       $skip_added = 1;
+                                       last;
+                               }
+                       }
+                       last if $skip_added;
+               }
+               next if $skip_added;
+
+               # Use `git ls-tree` to get the filenames of this directory
+               # that existed prior to this particular commit.
+               my $ls = command('ls-tree', '-z', '--name-only',
+                                $self->{c}, "$dir/");
+               my %files = map { $_ => 1 } split(/\0/, $ls);
+
+               # Remove the filenames that were deleted during this commit.
+               delete $files{$_} foreach (@deleted_gpath);
+
+               # Report the directory if there are no filenames left.
+               push @empty_dirs, $dir unless (scalar %files);
+       }
+       @empty_dirs;
+}
+
+sub add_placeholder_file {
+       my ($self, $dir) = @_;
+       my $path = "$dir/$_placeholder_filename";
+       my $gpath = $self->git_path($path);
+
+       my $fh = $::_repository->temp_acquire($gpath);
+       my $hash = $::_repository->hash_and_insert_object(Git::temp_path($fh));
+       Git::temp_release($fh, 1);
+       $self->{gii}->update('100644', $hash, $gpath) or croak $!;
+
+       # The directory should no longer be considered empty.
+       delete $self->{empty}->{$dir} if exists $self->{empty}->{$dir};
+
+       # Keep track of any placeholder files we create.
+       $added_placeholder{$dir} = $path;
+}
+
+sub stash_placeholder_list {
+       my ($self) = @_;
+       my $k = "svn-remote.$repo_id.added-placeholder";
+       my $v = eval { command_oneline('config', '--get-all', $k) };
+       command_noisy('config', '--unset-all', $k) if $v;
+       foreach (values %added_placeholder) {
+               command_noisy('config', '--add', $k, $_);
+       }
+}
+
 package SVN::Git::Editor;
 use vars qw/@ISA $_rmdir $_cp_similarity $_find_copies_harder $_rename_limit/;
 use strict;
diff --git a/git.c b/git.c
index 89721d420a09bfc8eb6a2d4010f86f5685cfd255..8e34903a65c8775b19b993d3e9ddf47c23d5254e 100644 (file)
--- a/git.c
+++ b/git.c
@@ -7,8 +7,8 @@
 
 const char git_usage_string[] =
        "git [--version] [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n"
-       "           [-p|--paginate|--no-pager] [--no-replace-objects]\n"
-       "           [--bare] [--git-dir=<path>] [--work-tree=<path>]\n"
+       "           [-p|--paginate|--no-pager] [--no-replace-objects] [--bare]\n"
+       "           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\n"
        "           [-c name=value] [--help]\n"
        "           <command> [<args>]";
 
@@ -126,6 +126,20 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
                        setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1);
                        if (envchanged)
                                *envchanged = 1;
+               } else if (!strcmp(cmd, "--namespace")) {
+                       if (*argc < 2) {
+                               fprintf(stderr, "No namespace given for --namespace.\n" );
+                               usage(git_usage_string);
+                       }
+                       setenv(GIT_NAMESPACE_ENVIRONMENT, (*argv)[1], 1);
+                       if (envchanged)
+                               *envchanged = 1;
+                       (*argv)++;
+                       (*argc)--;
+               } else if (!prefixcmp(cmd, "--namespace=")) {
+                       setenv(GIT_NAMESPACE_ENVIRONMENT, cmd + 12, 1);
+                       if (envchanged)
+                               *envchanged = 1;
                } else if (!strcmp(cmd, "--work-tree")) {
                        if (*argc < 2) {
                                fprintf(stderr, "No directory given for --work-tree.\n" );
@@ -183,8 +197,6 @@ static int handle_alias(int *argcp, const char ***argv)
                if (alias_string[0] == '!') {
                        const char **alias_argv;
                        int argc = *argcp, i;
-                       struct strbuf sb = STRBUF_INIT;
-                       const char *env[2];
 
                        commit_pager_choice();
 
@@ -195,13 +207,7 @@ static int handle_alias(int *argcp, const char ***argv)
                                alias_argv[i] = (*argv)[i];
                        alias_argv[argc] = NULL;
 
-                       strbuf_addstr(&sb, "GIT_PREFIX=");
-                       if (subdir)
-                               strbuf_addstr(&sb, subdir);
-                       env[0] = sb.buf;
-                       env[1] = NULL;
-                       ret = run_command_v_opt_cd_env(alias_argv, RUN_USING_SHELL, NULL, env);
-                       strbuf_release(&sb);
+                       ret = run_command_v_opt(alias_argv, RUN_USING_SHELL);
                        if (ret >= 0)   /* normal exit */
                                exit(ret);
 
@@ -328,7 +334,7 @@ static void handle_internal_command(int argc, const char **argv)
                { "annotate", cmd_annotate, RUN_SETUP },
                { "apply", cmd_apply, RUN_SETUP_GENTLY },
                { "archive", cmd_archive },
-               { "bisect--helper", cmd_bisect__helper, RUN_SETUP | NEED_WORK_TREE },
+               { "bisect--helper", cmd_bisect__helper, RUN_SETUP },
                { "blame", cmd_blame, RUN_SETUP },
                { "branch", cmd_branch, RUN_SETUP },
                { "bundle", cmd_bundle, RUN_SETUP_GENTLY },
@@ -467,6 +473,8 @@ static void execv_dashed_external(const char **argv)
        const char *tmp;
        int status;
 
+       if (use_pager == -1)
+               use_pager = check_pager_config(argv[0]);
        commit_pager_choice();
 
        strbuf_addf(&cmd, "git-%s", argv[0]);
index f40f9d6a29cda39ee7f989e11ef66cdf3d54d625..9ee5f96d4ce313f4f94505ff65b560943bfd21cb 100644 (file)
@@ -2,6 +2,8 @@ import os
 import subprocess
 import sys
 
+from git_remote_helpers.util import check_call
+
 
 class GitExporter(object):
     """An exporter for testgit repositories.
@@ -15,7 +17,7 @@ class GitExporter(object):
 
         self.repo = repo
 
-    def export_repo(self, base):
+    def export_repo(self, base, refs=None):
         """Exports a fast-export stream for the given directory.
 
         Simply delegates to git fast-epxort and pipes it through sed
@@ -23,8 +25,13 @@ class GitExporter(object):
         default refs/heads. This is to demonstrate how the export
         data can be stored under it's own ref (using the refspec
         capability).
+
+        If None, refs defaults to ["HEAD"].
         """
 
+        if not refs:
+            refs = ["HEAD"]
+
         dirname = self.repo.get_base_path(base)
         path = os.path.abspath(os.path.join(dirname, 'testgit.marks'))
 
@@ -42,12 +49,10 @@ class GitExporter(object):
         if os.path.exists(path):
             args.append("--import-marks=" + path)
 
-        args.append("HEAD")
+        args.extend(refs)
 
         p1 = subprocess.Popen(args, stdout=subprocess.PIPE)
 
         args = ["sed", "s_refs/heads/_" + self.repo.prefix + "_g"]
 
-        child = subprocess.Popen(args, stdin=p1.stdout)
-        if child.wait() != 0:
-            raise CalledProcessError
+        check_call(args, stdin=p1.stdout)
index 70a712729b63047b9760b72d3e2b0a45ff176646..5c6b595e16665bc508625ab0e96c95776bacba1a 100644 (file)
@@ -1,6 +1,8 @@
 import os
 import subprocess
 
+from git_remote_helpers.util import check_call, check_output
+
 
 class GitImporter(object):
     """An importer for testgit repositories.
@@ -14,6 +16,18 @@ class GitImporter(object):
 
         self.repo = repo
 
+    def get_refs(self, gitdir):
+        """Returns a dictionary with refs.
+        """
+        args = ["git", "--git-dir=" + gitdir, "for-each-ref", "refs/heads"]
+        lines = check_output(args).strip().split('\n')
+        refs = {}
+        for line in lines:
+            value, name = line.split(' ')
+            name = name.strip('commit\t')
+            refs[name] = value
+        return refs
+
     def do_import(self, base):
         """Imports a fast-import stream to the given directory.
 
@@ -30,11 +44,23 @@ class GitImporter(object):
         if not os.path.exists(dirname):
             os.makedirs(dirname)
 
+        refs_before = self.get_refs(gitdir)
+
         args = ["git", "--git-dir=" + gitdir, "fast-import", "--quiet", "--export-marks=" + path]
 
         if os.path.exists(path):
             args.append("--import-marks=" + path)
 
-        child = subprocess.Popen(args)
-        if child.wait() != 0:
-            raise CalledProcessError
+        check_call(args)
+
+        refs_after = self.get_refs(gitdir)
+
+        changed = {}
+
+        for name, value in refs_after.iteritems():
+            if refs_before.get(name) == value:
+                continue
+
+            changed[name] = value
+
+        return changed
index f27389bb945ef423dc412c2368762439086f593e..e70025095dcfb31d3944e72ac1f83dd7d4109103 100644 (file)
@@ -1,7 +1,7 @@
 import os
 import subprocess
 
-from git_remote_helpers.util import die, warn
+from git_remote_helpers.util import check_call, die, warn
 
 
 class NonLocalGit(object):
@@ -29,9 +29,7 @@ class NonLocalGit(object):
         os.makedirs(path)
         args = ["git", "clone", "--bare", "--quiet", self.repo.gitpath, path]
 
-        child = subprocess.Popen(args)
-        if child.wait() != 0:
-            raise CalledProcessError
+        check_call(args)
 
         return path
 
@@ -45,14 +43,10 @@ class NonLocalGit(object):
             die("could not find repo at %s", path)
 
         args = ["git", "--git-dir=" + path, "fetch", "--quiet", self.repo.gitpath]
-        child = subprocess.Popen(args)
-        if child.wait() != 0:
-            raise CalledProcessError
+        check_call(args)
 
         args = ["git", "--git-dir=" + path, "update-ref", "refs/heads/master", "FETCH_HEAD"]
-        child = subprocess.Popen(args)
-        if child.wait() != 0:
-            raise CalledProcessError
+        child = check_call(args)
 
     def push(self, base):
         """Pushes from the non-local repo to base.
@@ -63,7 +57,5 @@ class NonLocalGit(object):
         if not os.path.exists(path):
             die("could not find repo at %s", path)
 
-        args = ["git", "--git-dir=" + path, "push", "--quiet", self.repo.gitpath]
-        child = subprocess.Popen(args)
-        if child.wait() != 0:
-            raise CalledProcessError
+        args = ["git", "--git-dir=" + path, "push", "--quiet", self.repo.gitpath, "--all"]
+        child = check_call(args)
index 58e1cdb560fa0fe1a4745f971064e5e967408502..acbf8d7785e2253777456f8910e2352992dda474 100644 (file)
@@ -1,6 +1,9 @@
 import os
 import subprocess
 
+from git_remote_helpers.util import check_call
+
+
 def sanitize(rev, sep='\t'):
     """Converts a for-each-ref line to a name/value pair.
     """
@@ -53,9 +56,7 @@ class GitRepo(object):
         path = ".cached_revs"
         ofile = open(path, "w")
 
-        child = subprocess.Popen(args, stdout=ofile)
-        if child.wait() != 0:
-            raise CalledProcessError
+        check_call(args, stdout=ofile)
         output = open(path).readlines()
         self.revmap = dict(sanitize(i) for i in output)
         if "HEAD" in self.revmap:
index dce83e60660825ba115c503ce0776955d90c1a44..fbbb01b14619c1d2ed6bcc8f304f019fbe98697f 100644 (file)
@@ -11,6 +11,21 @@ import sys
 import os
 import subprocess
 
+try:
+    from subprocess import CalledProcessError
+except ImportError:
+    # from python2.7:subprocess.py
+    # Exception classes used by this module.
+    class CalledProcessError(Exception):
+        """This exception is raised when a process run by check_call() returns
+        a non-zero exit status.  The exit status will be stored in the
+        returncode attribute."""
+        def __init__(self, returncode, cmd):
+            self.returncode = returncode
+            self.cmd = cmd
+        def __str__(self):
+            return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
+
 
 # Whether or not to show debug messages
 DEBUG = False
@@ -128,6 +143,72 @@ def run_command (args, cwd = None, shell = False, add_env = None,
     return (exit_code, output, errors)
 
 
+# from python2.7:subprocess.py
+def call(*popenargs, **kwargs):
+    """Run command with arguments.  Wait for command to complete, then
+    return the returncode attribute.
+
+    The arguments are the same as for the Popen constructor.  Example:
+
+    retcode = call(["ls", "-l"])
+    """
+    return subprocess.Popen(*popenargs, **kwargs).wait()
+
+
+# from python2.7:subprocess.py
+def check_call(*popenargs, **kwargs):
+    """Run command with arguments.  Wait for command to complete.  If
+    the exit code was zero then return, otherwise raise
+    CalledProcessError.  The CalledProcessError object will have the
+    return code in the returncode attribute.
+
+    The arguments are the same as for the Popen constructor.  Example:
+
+    check_call(["ls", "-l"])
+    """
+    retcode = call(*popenargs, **kwargs)
+    if retcode:
+        cmd = kwargs.get("args")
+        if cmd is None:
+            cmd = popenargs[0]
+        raise CalledProcessError(retcode, cmd)
+    return 0
+
+
+# from python2.7:subprocess.py
+def check_output(*popenargs, **kwargs):
+    r"""Run command with arguments and return its output as a byte string.
+
+    If the exit code was non-zero it raises a CalledProcessError.  The
+    CalledProcessError object will have the return code in the returncode
+    attribute and output in the output attribute.
+
+    The arguments are the same as for the Popen constructor.  Example:
+
+    >>> check_output(["ls", "-l", "/dev/null"])
+    'crw-rw-rw- 1 root root 1, 3 Oct 18  2007 /dev/null\n'
+
+    The stdout argument is not allowed as it is used internally.
+    To capture standard error in the result, use stderr=STDOUT.
+
+    >>> check_output(["/bin/sh", "-c",
+    ...               "ls -l non_existent_file ; exit 0"],
+    ...              stderr=STDOUT)
+    'ls: non_existent_file: No such file or directory\n'
+    """
+    if 'stdout' in kwargs:
+        raise ValueError('stdout argument not allowed, it will be overridden.')
+    process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
+    output, unused_err = process.communicate()
+    retcode = process.poll()
+    if retcode:
+        cmd = kwargs.get("args")
+        if cmd is None:
+            cmd = popenargs[0]
+        raise subprocess.CalledProcessError(retcode, cmd)
+    return output
+
+
 def file_reader_method (missing_ok = False):
     """Decorator for simplifying reading of files.
 
index c5236fee9dced35ab35d02eb7d42109f0107a165..f5efe7454ca42decbe7b113388bc6233e2311c1e 100644 (file)
@@ -231,7 +231,7 @@ Gitweb config file
 See also "Runtime gitweb configuration" section in README file
 for gitweb (in gitweb/README).
 
-- You can configure gitweb further using the gitweb configuration file;
+- You can configure gitweb further using the per-instance gitweb configuration file;
   by default this is a file named gitweb_config.perl in the same place as
   gitweb.cgi script. You can control the default place for the config file
   using the GITWEB_CONFIG build configuration variable, and you can set it
@@ -241,6 +241,17 @@ for gitweb (in gitweb/README).
   GITWEB_CONFIG_SYSTEM build configuration variable, and override it
   through the GITWEB_CONFIG_SYSTEM environment variable.
 
+  Note that if per-instance configuration file exists, then system-wide
+  configuration is _not used at all_.  This is quite untypical and suprising
+  behavior.  On the other hand changing current behavior would break backwards
+  compatibility and can lead to unexpected changes in gitweb behavior.
+  Therefore gitweb also looks for common system-wide configuration file,
+  normally /etc/gitweb-common.conf (set during build time using build time
+  configuration variable GITWEB_CONFIG_COMMON, set it at runtime using
+  environment variable with the same name).  Settings from per-instance or
+  system-wide configuration file override those from common system-wide
+  configuration file.
+
 - The gitweb config file is a fragment of perl code. You can set variables
   using "our $variable = value"; text from "#" character until the end
   of a line is ignored. See perlsyn(1) for details.
index 5d20515fba9251123e29be623178047d4131fc01..1c85b5fda8bc994e0ecd249e11e8f2331098bea9 100644 (file)
@@ -20,6 +20,7 @@ INSTALL ?= install
 # default configuration for gitweb
 GITWEB_CONFIG = gitweb_config.perl
 GITWEB_CONFIG_SYSTEM = /etc/gitweb.conf
+GITWEB_CONFIG_COMMON = /etc/gitweb-common.conf
 GITWEB_HOME_LINK_STR = projects
 GITWEB_SITENAME =
 GITWEB_PROJECTROOT = /pub/git
@@ -129,6 +130,7 @@ GITWEB_REPLACE = \
        -e 's|++GIT_BINDIR++|$(bindir)|g' \
        -e 's|++GITWEB_CONFIG++|$(GITWEB_CONFIG)|g' \
        -e 's|++GITWEB_CONFIG_SYSTEM++|$(GITWEB_CONFIG_SYSTEM)|g' \
+       -e 's|++GITWEB_CONFIG_COMMON++|$(GITWEB_CONFIG_COMMON)|g' \
        -e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \
        -e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \
        -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \
index a4cfcb42cd989c063c49b6511393ba936e93978a..a9988200d6b67066b837e7298e0e1529057a4bca 100644 (file)
@@ -10,9 +10,30 @@ From the git version 1.4.0 gitweb is bundled with git.
 Runtime gitweb configuration
 ----------------------------
 
-You can adjust gitweb behaviour using the file specified in `GITWEB_CONFIG`
-(defaults to 'gitweb_config.perl' in the same directory as the CGI), and
-as a fallback `GITWEB_CONFIG_SYSTEM` (defaults to /etc/gitweb.conf).
+Gitweb obtains configuration data from the following sources in the
+following order:
+
+1. built-in values (some set during build stage),
+2. common system-wide configuration file (`GITWEB_CONFIG_COMMON`,
+   defaults to '/etc/gitweb-common.conf'),
+3. either per-instance configuration file (`GITWEB_CONFIG`, defaults to
+   'gitweb_config.perl' in the same directory as the installed gitweb),
+   or if it does not exists then system-wide configuration file
+   (`GITWEB_CONFIG_SYSTEM`, defaults to '/etc/gitweb.conf').
+
+Values obtained in later configuration files override values obtained earlier
+in above sequence.
+
+You can read defaults in system-wide GITWEB_CONFIG_SYSTEM from GITWEB_CONFIG
+by adding
+
+  read_config_file($GITWEB_CONFIG_SYSTEM);
+
+at very beginning of per-instance GITWEB_CONFIG file.  In this case
+settings in said per-instance file will override settings from
+system-wide configuration file.  Note that read_config_file checks
+itself that the $GITWEB_CONFIG_SYSTEM file exists.
+
 The most notable thing that is not configurable at compile time are the
 optional features, stored in the '%features' variable.
 
index 81dacf2b6a5d98550fa56adac9450560c550eee1..70a576a626ac67516af7b980c64ec33da6b7c19b 100755 (executable)
@@ -321,6 +321,10 @@ our %feature = (
        # Enable text search, which will list the commits which match author,
        # committer or commit text to a given string.  Enabled by default.
        # Project specific override is not supported.
+       #
+       # Note that this controls all search features, which means that if
+       # it is disabled, then 'grep' and 'pickaxe' search would also be
+       # disabled.
        'search' => {
                'override' => 0,
                'default' => [1]},
@@ -661,13 +665,25 @@ sub read_config_file {
        return;
 }
 
-our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM);
+our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM, $GITWEB_CONFIG_COMMON);
 sub evaluate_gitweb_config {
        our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
        our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++";
+       our $GITWEB_CONFIG_COMMON = $ENV{'GITWEB_CONFIG_COMMON'} || "++GITWEB_CONFIG_COMMON++";
+
+       # Protect agains duplications of file names, to not read config twice.
+       # Only one of $GITWEB_CONFIG and $GITWEB_CONFIG_SYSTEM is used, so
+       # there possibility of duplication of filename there doesn't matter.
+       $GITWEB_CONFIG = ""        if ($GITWEB_CONFIG eq $GITWEB_CONFIG_COMMON);
+       $GITWEB_CONFIG_SYSTEM = "" if ($GITWEB_CONFIG_SYSTEM eq $GITWEB_CONFIG_COMMON);
+
+       # Common system-wide settings for convenience.
+       # Those settings can be ovverriden by GITWEB_CONFIG or GITWEB_CONFIG_SYSTEM.
+       read_config_file($GITWEB_CONFIG_COMMON);
 
-       # use first config file that exists
-       read_config_file($GITWEB_CONFIG) or
+       # Use first config file that exists.  This means use the per-instance
+       # GITWEB_CONFIG if exists, otherwise use GITWEB_SYSTEM_CONFIG.
+       read_config_file($GITWEB_CONFIG) and return;
        read_config_file($GITWEB_CONFIG_SYSTEM);
 }
 
@@ -2510,6 +2526,13 @@ sub git_get_project_config {
 
        # key sanity check
        return unless ($key);
+       # only subsection, if exists, is case sensitive,
+       # and not lowercased by 'git config -z -l'
+       if (my ($hi, $mi, $lo) = ($key =~ /^([^.]*)\.(.*)\.([^.]*)$/)) {
+               $key = join(".", lc($hi), $mi, lc($lo));
+       } else {
+               $key = lc($key);
+       }
        $key =~ s/^gitweb\.//;
        return if ($key =~ m/\W/);
 
@@ -3562,12 +3585,9 @@ sub mimetype_guess_file {
        open(my $mh, '<', $mimemap) or return undef;
        while (<$mh>) {
                next if m/^#/; # skip comments
-               my ($mimetype, $exts) = split(/\t+/);
-               if (defined $exts) {
-                       my @exts = split(/\s+/, $exts);
-                       foreach my $ext (@exts) {
-                               $mimemap{$ext} = $mimetype;
-                       }
+               my ($mimetype, @exts) = split(/\s+/);
+               foreach my $ext (@exts) {
+                       $mimemap{$ext} = $mimetype;
                }
        }
        close($mh);
@@ -3683,6 +3703,20 @@ sub get_page_title {
        return $title;
 }
 
+sub get_content_type_html {
+       # require explicit support from the UA if we are to send the page as
+       # 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
+       # we have to do this because MSIE sometimes globs '*/*', pretending to
+       # support xhtml+xml but choking when it gets what it asked for.
+       if (defined $cgi->http('HTTP_ACCEPT') &&
+           $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ &&
+           $cgi->Accept('application/xhtml+xml') != 0) {
+               return 'application/xhtml+xml';
+       } else {
+               return 'text/html';
+       }
+}
+
 sub print_feed_meta {
        if (defined $project) {
                my %href_params = get_feed_info();
@@ -3728,24 +3762,90 @@ sub print_feed_meta {
        }
 }
 
+sub print_header_links {
+       my $status = shift;
+
+       # 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="'.esc_url($stylesheet).'"/>'."\n";
+       } else {
+               foreach my $stylesheet (@stylesheets) {
+                       next unless $stylesheet;
+                       print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
+               }
+       }
+       print_feed_meta()
+               if ($status eq '200 OK');
+       if (defined $favicon) {
+               print qq(<link rel="shortcut icon" href=").esc_url($favicon).qq(" type="image/png" />\n);
+       }
+}
+
+sub print_nav_breadcrumbs {
+       my %opts = @_;
+
+       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) {
+                       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";
+       }
+}
+
+sub print_search_form {
+       if (!defined $searchtext) {
+               $searchtext = "";
+       }
+       my $search_hash;
+       if (defined $hash_base) {
+               $search_hash = $hash_base;
+       } elsif (defined $hash) {
+               $search_hash = $hash;
+       } else {
+               $search_hash = "HEAD";
+       }
+       my $action = $my_uri;
+       my $use_pathinfo = gitweb_check_feature('pathinfo');
+       if ($use_pathinfo) {
+               $action .= "/".esc_url($project);
+       }
+       print $cgi->startform(-method => "get", -action => $action) .
+             "<div class=\"search\">\n" .
+             (!$use_pathinfo &&
+             $cgi->input({-name=>"p", -value=>$project, -type=>"hidden"}) . "\n") .
+             $cgi->input({-name=>"a", -value=>"search", -type=>"hidden"}) . "\n" .
+             $cgi->input({-name=>"h", -value=>$search_hash, -type=>"hidden"}) . "\n" .
+             $cgi->popup_menu(-name => 'st', -default => 'commit',
+                              -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
+             $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
+             " search:\n",
+             $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
+             "<span title=\"Extended regular expression\">" .
+             $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
+                            -checked => $search_use_regexp) .
+             "</span>" .
+             "</div>" .
+             $cgi->end_form() . "\n";
+}
+
 sub git_header_html {
        my $status = shift || "200 OK";
        my $expires = shift;
        my %opts = @_;
 
        my $title = get_page_title();
-       my $content_type;
-       # require explicit support from the UA if we are to send the page as
-       # 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
-       # we have to do this because MSIE sometimes globs '*/*', pretending to
-       # support xhtml+xml but choking when it gets what it asked for.
-       if (defined $cgi->http('HTTP_ACCEPT') &&
-           $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ &&
-           $cgi->Accept('application/xhtml+xml') != 0) {
-               $content_type = 'application/xhtml+xml';
-       } else {
-               $content_type = 'text/html';
-       }
+       my $content_type = get_content_type_html();
        print $cgi->header(-type=>$content_type, -charset => 'utf-8',
                           -status=> $status, -expires => $expires)
                unless ($opts{'-no_http_header'});
@@ -3767,22 +3867,7 @@ EOF
        if ($ENV{'PATH_INFO'}) {
                print "<base href=\"".esc_url($base_url)."\" />\n";
        }
-       # 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="'.esc_url($stylesheet).'"/>'."\n";
-       } else {
-               foreach my $stylesheet (@stylesheets) {
-                       next unless $stylesheet;
-                       print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
-               }
-       }
-       print_feed_meta()
-               if ($status eq '200 OK');
-       if (defined $favicon) {
-               print qq(<link rel="shortcut icon" href=").esc_url($favicon).qq(" type="image/png" />\n);
-       }
-
+       print_header_links($status);
        print "</head>\n" .
              "<body>\n";
 
@@ -3799,59 +3884,12 @@ EOF
                                         -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) {
-                       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";
-       }
+       print_nav_breadcrumbs(%opts);
        print "</div>\n";
 
        my $have_search = gitweb_check_feature('search');
        if (defined $project && $have_search) {
-               if (!defined $searchtext) {
-                       $searchtext = "";
-               }
-               my $search_hash;
-               if (defined $hash_base) {
-                       $search_hash = $hash_base;
-               } elsif (defined $hash) {
-                       $search_hash = $hash;
-               } else {
-                       $search_hash = "HEAD";
-               }
-               my $action = $my_uri;
-               my $use_pathinfo = gitweb_check_feature('pathinfo');
-               if ($use_pathinfo) {
-                       $action .= "/".esc_url($project);
-               }
-               print $cgi->startform(-method => "get", -action => $action) .
-                     "<div class=\"search\">\n" .
-                     (!$use_pathinfo &&
-                     $cgi->input({-name=>"p", -value=>$project, -type=>"hidden"}) . "\n") .
-                     $cgi->input({-name=>"a", -value=>"search", -type=>"hidden"}) . "\n" .
-                     $cgi->input({-name=>"h", -value=>$search_hash, -type=>"hidden"}) . "\n" .
-                     $cgi->popup_menu(-name => 'st', -default => 'commit',
-                                      -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
-                     $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
-                     " search:\n",
-                     $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
-                     "<span title=\"Extended regular expression\">" .
-                     $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
-                                    -checked => $search_use_regexp) .
-                     "</span>" .
-                     "</div>" .
-                     $cgi->end_form() . "\n";
+               print_search_form();
        }
 }
 
@@ -5506,6 +5544,216 @@ sub git_remotes_body {
        }
 }
 
+sub git_search_message {
+       my %co = @_;
+
+       my $greptype;
+       if ($searchtype eq 'commit') {
+               $greptype = "--grep=";
+       } elsif ($searchtype eq 'author') {
+               $greptype = "--author=";
+       } elsif ($searchtype eq 'committer') {
+               $greptype = "--committer=";
+       }
+       $greptype .= $searchtext;
+       my @commitlist = parse_commits($hash, 101, (100 * $page), undef,
+                                      $greptype, '--regexp-ignore-case',
+                                      $search_use_regexp ? '--extended-regexp' : '--fixed-strings');
+
+       my $paging_nav = '';
+       if ($page > 0) {
+               $paging_nav .=
+                       $cgi->a({-href => href(-replay=>1, page=>undef)},
+                               "first") .
+                       " &sdot; " .
+                       $cgi->a({-href => href(-replay=>1, page=>$page-1),
+                                -accesskey => "p", -title => "Alt-p"}, "prev");
+       } else {
+               $paging_nav .= "first &sdot; prev";
+       }
+       my $next_link = '';
+       if ($#commitlist >= 100) {
+               $next_link =
+                       $cgi->a({-href => href(-replay=>1, page=>$page+1),
+                                -accesskey => "n", -title => "Alt-n"}, "next");
+               $paging_nav .= " &sdot; $next_link";
+       } else {
+               $paging_nav .= " &sdot; next";
+       }
+
+       git_header_html();
+
+       git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);
+       git_print_header_div('commit', esc_html($co{'title'}), $hash);
+       if ($page == 0 && !@commitlist) {
+               print "<p>No match.</p>\n";
+       } else {
+               git_search_grep_body(\@commitlist, 0, 99, $next_link);
+       }
+
+       git_footer_html();
+}
+
+sub git_search_changes {
+       my %co = @_;
+
+       local $/ = "\n";
+       open my $fd, '-|', git_cmd(), '--no-pager', 'log', @diff_opts,
+               '--pretty=format:%H', '--no-abbrev', '--raw', "-S$searchtext",
+               ($search_use_regexp ? '--pickaxe-regex' : ())
+                       or die_error(500, "Open git-log failed");
+
+       git_header_html();
+
+       git_print_page_nav('','', $hash,$co{'tree'},$hash);
+       git_print_header_div('commit', esc_html($co{'title'}), $hash);
+
+       print "<table class=\"pickaxe search\">\n";
+       my $alternate = 1;
+       undef %co;
+       my @files;
+       while (my $line = <$fd>) {
+               chomp $line;
+               next unless $line;
+
+               my %set = parse_difftree_raw_line($line);
+               if (defined $set{'commit'}) {
+                       # finish previous commit
+                       if (%co) {
+                               print "</td>\n" .
+                                     "<td class=\"link\">" .
+                                     $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})},
+                                             "commit") .
+                                     " | " .
+                                     $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'},
+                                                            hash_base=>$co{'id'})},
+                                             "tree") .
+                                     "</td>\n" .
+                                     "</tr>\n";
+                       }
+
+                       if ($alternate) {
+                               print "<tr class=\"dark\">\n";
+                       } else {
+                               print "<tr class=\"light\">\n";
+                       }
+                       $alternate ^= 1;
+                       %co = parse_commit($set{'commit'});
+                       my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
+                       print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
+                             "<td><i>$author</i></td>\n" .
+                             "<td>" .
+                             $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
+                                     -class => "list subject"},
+                                     chop_and_escape_str($co{'title'}, 50) . "<br/>");
+               } elsif (defined $set{'to_id'}) {
+                       next if ($set{'to_id'} =~ m/^0{40}$/);
+
+                       print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'},
+                                                    hash=>$set{'to_id'}, file_name=>$set{'to_file'}),
+                                     -class => "list"},
+                                     "<span class=\"match\">" . esc_path($set{'file'}) . "</span>") .
+                             "<br/>\n";
+               }
+       }
+       close $fd;
+
+       # finish last commit (warning: repetition!)
+       if (%co) {
+               print "</td>\n" .
+                     "<td class=\"link\">" .
+                     $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})},
+                             "commit") .
+                     " | " .
+                     $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'},
+                                            hash_base=>$co{'id'})},
+                             "tree") .
+                     "</td>\n" .
+                     "</tr>\n";
+       }
+
+       print "</table>\n";
+
+       git_footer_html();
+}
+
+sub git_search_files {
+       my %co = @_;
+
+       local $/ = "\n";
+       open my $fd, "-|", git_cmd(), 'grep', '-n',
+               $search_use_regexp ? ('-E', '-i') : '-F',
+               $searchtext, $co{'tree'}
+                       or die_error(500, "Open git-grep failed");
+
+       git_header_html();
+
+       git_print_page_nav('','', $hash,$co{'tree'},$hash);
+       git_print_header_div('commit', esc_html($co{'title'}), $hash);
+
+       print "<table class=\"grep_search\">\n";
+       my $alternate = 1;
+       my $matches = 0;
+       my $lastfile = '';
+       while (my $line = <$fd>) {
+               chomp $line;
+               my ($file, $lno, $ltext, $binary);
+               last if ($matches++ > 1000);
+               if ($line =~ /^Binary file (.+) matches$/) {
+                       $file = $1;
+                       $binary = 1;
+               } else {
+                       (undef, $file, $lno, $ltext) = split(/:/, $line, 4);
+               }
+               if ($file ne $lastfile) {
+                       $lastfile and print "</td></tr>\n";
+                       if ($alternate++) {
+                               print "<tr class=\"dark\">\n";
+                       } else {
+                               print "<tr class=\"light\">\n";
+                       }
+                       print "<td class=\"list\">".
+                               $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
+                                                      file_name=>"$file"),
+                                       -class => "list"}, esc_path($file));
+                       print "</td><td>\n";
+                       $lastfile = $file;
+               }
+               if ($binary) {
+                       print "<div class=\"binary\">Binary file</div>\n";
+               } else {
+                       $ltext = untabify($ltext);
+                       if ($ltext =~ m/^(.*)($search_regexp)(.*)$/i) {
+                               $ltext = esc_html($1, -nbsp=>1);
+                               $ltext .= '<span class="match">';
+                               $ltext .= esc_html($2, -nbsp=>1);
+                               $ltext .= '</span>';
+                               $ltext .= esc_html($3, -nbsp=>1);
+                       } else {
+                               $ltext = esc_html($ltext, -nbsp=>1);
+                       }
+                       print "<div class=\"pre\">" .
+                               $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
+                                                      file_name=>"$file").'#l'.$lno,
+                                       -class => "linenr"}, sprintf('%4i', $lno))
+                               . ' ' .  $ltext . "</div>\n";
+               }
+       }
+       if ($lastfile) {
+               print "</td></tr>\n";
+               if ($matches > 1000) {
+                       print "<div class=\"diff nodifferences\">Too many matches, listing trimmed</div>\n";
+               }
+       } else {
+               print "<div class=\"diff nodifferences\">No matches found</div>\n";
+       }
+       close $fd;
+
+       print "</table>\n";
+
+       git_footer_html();
+}
+
 sub git_search_grep_body {
        my ($commitlist, $from, $to, $extra) = @_;
        $from = 0 unless defined $from;
@@ -6129,7 +6377,16 @@ sub git_blob_plain {
        # want to be sure not to break that by serving the image as an
        # attachment (though Firefox 3 doesn't seem to care).
        my $sandbox = $prevent_xss &&
-               $type !~ m!^(?:text/plain|image/(?:gif|png|jpeg))(?:[ ;]|$)!;
+               $type !~ m!^(?:text/[a-z]+|image/(?:gif|png|jpeg))(?:[ ;]|$)!;
+
+       # serve text/* as text/plain
+       if ($prevent_xss &&
+           ($type =~ m!^text/[a-z]+\b(.*)$! ||
+            ($type =~ m!^[a-z]+/[a-z]\+xml\b(.*)$! && -T $fd))) {
+               my $rest = $1;
+               $rest = defined $rest ? $rest : '';
+               $type = "text/plain$rest";
+       }
 
        print $cgi->header(
                -type => $type,
@@ -6227,7 +6484,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, esc_attr(href(-replay => 1)), $nr, $nr, $syntax ? $line : esc_html($line, -nbsp=>1);
+                              $nr, esc_attr(href(-replay => 1)), $nr, $nr, $syntax ? to_utf8($line) : esc_html($line, -nbsp=>1);
                }
        }
        close $fd
@@ -7052,7 +7309,23 @@ sub git_history {
 }
 
 sub git_search {
-       gitweb_check_feature('search') or die_error(403, "Search is disabled");
+       $searchtype ||= 'commit';
+
+       # check if appropriate features are enabled
+       gitweb_check_feature('search')
+               or die_error(403, "Search is disabled");
+       if ($searchtype eq 'pickaxe') {
+               # pickaxe may take all resources of your box and run for several minutes
+               # with every query - so decide by yourself how public you make this feature
+               gitweb_check_feature('pickaxe')
+                       or die_error(403, "Pickaxe search is disabled");
+       }
+       if ($searchtype eq 'grep') {
+               # grep search might be potentially CPU-intensive, too
+               gitweb_check_feature('grep')
+                       or die_error(403, "Grep search is disabled");
+       }
+
        if (!defined $searchtext) {
                die_error(400, "Text field is empty");
        }
@@ -7067,205 +7340,17 @@ sub git_search {
                $page = 0;
        }
 
-       $searchtype ||= 'commit';
-       if ($searchtype eq 'pickaxe') {
-               # pickaxe may take all resources of your box and run for several minutes
-               # with every query - so decide by yourself how public you make this feature
-               gitweb_check_feature('pickaxe')
-                   or die_error(403, "Pickaxe is disabled");
-       }
-       if ($searchtype eq 'grep') {
-               gitweb_check_feature('grep')
-                   or die_error(403, "Grep is disabled");
-       }
-
-       git_header_html();
-
-       if ($searchtype eq 'commit' or $searchtype eq 'author' or $searchtype eq 'committer') {
-               my $greptype;
-               if ($searchtype eq 'commit') {
-                       $greptype = "--grep=";
-               } elsif ($searchtype eq 'author') {
-                       $greptype = "--author=";
-               } elsif ($searchtype eq 'committer') {
-                       $greptype = "--committer=";
-               }
-               $greptype .= $searchtext;
-               my @commitlist = parse_commits($hash, 101, (100 * $page), undef,
-                                              $greptype, '--regexp-ignore-case',
-                                              $search_use_regexp ? '--extended-regexp' : '--fixed-strings');
-
-               my $paging_nav = '';
-               if ($page > 0) {
-                       $paging_nav .=
-                               $cgi->a({-href => href(action=>"search", hash=>$hash,
-                                                      searchtext=>$searchtext,
-                                                      searchtype=>$searchtype)},
-                                       "first");
-                       $paging_nav .= " &sdot; " .
-                               $cgi->a({-href => href(-replay=>1, page=>$page-1),
-                                        -accesskey => "p", -title => "Alt-p"}, "prev");
-               } else {
-                       $paging_nav .= "first";
-                       $paging_nav .= " &sdot; prev";
-               }
-               my $next_link = '';
-               if ($#commitlist >= 100) {
-                       $next_link =
-                               $cgi->a({-href => href(-replay=>1, page=>$page+1),
-                                        -accesskey => "n", -title => "Alt-n"}, "next");
-                       $paging_nav .= " &sdot; $next_link";
-               } else {
-                       $paging_nav .= " &sdot; next";
-               }
-
-               git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);
-               git_print_header_div('commit', esc_html($co{'title'}), $hash);
-               if ($page == 0 && !@commitlist) {
-                       print "<p>No match.</p>\n";
-               } else {
-                       git_search_grep_body(\@commitlist, 0, 99, $next_link);
-               }
-       }
-
-       if ($searchtype eq 'pickaxe') {
-               git_print_page_nav('','', $hash,$co{'tree'},$hash);
-               git_print_header_div('commit', esc_html($co{'title'}), $hash);
-
-               print "<table class=\"pickaxe search\">\n";
-               my $alternate = 1;
-               local $/ = "\n";
-               open my $fd, '-|', git_cmd(), '--no-pager', 'log', @diff_opts,
-                       '--pretty=format:%H', '--no-abbrev', '--raw', "-S$searchtext",
-                       ($search_use_regexp ? '--pickaxe-regex' : ());
-               undef %co;
-               my @files;
-               while (my $line = <$fd>) {
-                       chomp $line;
-                       next unless $line;
-
-                       my %set = parse_difftree_raw_line($line);
-                       if (defined $set{'commit'}) {
-                               # finish previous commit
-                               if (%co) {
-                                       print "</td>\n" .
-                                             "<td class=\"link\">" .
-                                             $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
-                                             " | " .
-                                             $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
-                                       print "</td>\n" .
-                                             "</tr>\n";
-                               }
-
-                               if ($alternate) {
-                                       print "<tr class=\"dark\">\n";
-                               } else {
-                                       print "<tr class=\"light\">\n";
-                               }
-                               $alternate ^= 1;
-                               %co = parse_commit($set{'commit'});
-                               my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
-                               print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
-                                     "<td><i>$author</i></td>\n" .
-                                     "<td>" .
-                                     $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
-                                             -class => "list subject"},
-                                             chop_and_escape_str($co{'title'}, 50) . "<br/>");
-                       } elsif (defined $set{'to_id'}) {
-                               next if ($set{'to_id'} =~ m/^0{40}$/);
-
-                               print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'},
-                                                            hash=>$set{'to_id'}, file_name=>$set{'to_file'}),
-                                             -class => "list"},
-                                             "<span class=\"match\">" . esc_path($set{'file'}) . "</span>") .
-                                     "<br/>\n";
-                       }
-               }
-               close $fd;
-
-               # finish last commit (warning: repetition!)
-               if (%co) {
-                       print "</td>\n" .
-                             "<td class=\"link\">" .
-                             $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
-                             " | " .
-                             $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
-                       print "</td>\n" .
-                             "</tr>\n";
-               }
-
-               print "</table>\n";
-       }
-
-       if ($searchtype eq 'grep') {
-               git_print_page_nav('','', $hash,$co{'tree'},$hash);
-               git_print_header_div('commit', esc_html($co{'title'}), $hash);
-
-               print "<table class=\"grep_search\">\n";
-               my $alternate = 1;
-               my $matches = 0;
-               local $/ = "\n";
-               open my $fd, "-|", git_cmd(), 'grep', '-n',
-                       $search_use_regexp ? ('-E', '-i') : '-F',
-                       $searchtext, $co{'tree'};
-               my $lastfile = '';
-               while (my $line = <$fd>) {
-                       chomp $line;
-                       my ($file, $lno, $ltext, $binary);
-                       last if ($matches++ > 1000);
-                       if ($line =~ /^Binary file (.+) matches$/) {
-                               $file = $1;
-                               $binary = 1;
-                       } else {
-                               (undef, $file, $lno, $ltext) = split(/:/, $line, 4);
-                       }
-                       if ($file ne $lastfile) {
-                               $lastfile and print "</td></tr>\n";
-                               if ($alternate++) {
-                                       print "<tr class=\"dark\">\n";
-                               } else {
-                                       print "<tr class=\"light\">\n";
-                               }
-                               print "<td class=\"list\">".
-                                       $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
-                                                              file_name=>"$file"),
-                                               -class => "list"}, esc_path($file));
-                               print "</td><td>\n";
-                               $lastfile = $file;
-                       }
-                       if ($binary) {
-                               print "<div class=\"binary\">Binary file</div>\n";
-                       } else {
-                               $ltext = untabify($ltext);
-                               if ($ltext =~ m/^(.*)($search_regexp)(.*)$/i) {
-                                       $ltext = esc_html($1, -nbsp=>1);
-                                       $ltext .= '<span class="match">';
-                                       $ltext .= esc_html($2, -nbsp=>1);
-                                       $ltext .= '</span>';
-                                       $ltext .= esc_html($3, -nbsp=>1);
-                               } else {
-                                       $ltext = esc_html($ltext, -nbsp=>1);
-                               }
-                               print "<div class=\"pre\">" .
-                                       $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
-                                                              file_name=>"$file").'#l'.$lno,
-                                               -class => "linenr"}, sprintf('%4i', $lno))
-                                       . ' ' .  $ltext . "</div>\n";
-                       }
-               }
-               if ($lastfile) {
-                       print "</td></tr>\n";
-                       if ($matches > 1000) {
-                               print "<div class=\"diff nodifferences\">Too many matches, listing trimmed</div>\n";
-                       }
-               } else {
-                       print "<div class=\"diff nodifferences\">No matches found</div>\n";
-               }
-               close $fd;
-
-               print "</table>\n";
+       if ($searchtype eq 'commit' ||
+           $searchtype eq 'author' ||
+           $searchtype eq 'committer') {
+               git_search_message(%co);
+       } elsif ($searchtype eq 'pickaxe') {
+               git_search_changes(%co);
+       } elsif ($searchtype eq 'grep') {
+               git_search_files(%co);
+       } else {
+               die_error(400, "Unknown search type");
        }
-       git_footer_html();
 }
 
 sub git_search_help {
index 676da6b590bb9335d8fe8d65d588fc72289b871c..db6eb505846aedfaca1cdbaf6f4d399d049de777 100644 (file)
@@ -29,7 +29,6 @@
 /* ............................................................ */
 /* utility/helper functions (and variables) */
 
-var xhr;        // XMLHttpRequest object
 var projectUrl; // partial query + separator ('?' or ';')
 
 // 'commits' is an associative map. It maps SHA1s to Commit objects.
@@ -420,8 +419,6 @@ function handleLine(commit, group) {
 
 // ----------------------------------------------------------------------
 
-var inProgress = false;   // are we processing response
-
 /**#@+
  * @constant
  */
@@ -433,8 +430,6 @@ var endRe  = /^END ?([^ ]*) ?(.*)/;
 var curCommit = new Commit();
 var curGroup  = {};
 
-var pollTimer = null;
-
 /**
  * Parse output from 'git blame --incremental [...]', received via
  * XMLHttpRequest from server (blamedataUrl), and call handleLine
@@ -535,43 +530,51 @@ function processData(unprocessed, nextReadPos) {
  * Handle XMLHttpRequest errors
  *
  * @param {XMLHttpRequest} xhr: XMLHttpRequest object
+ * @param {Number} [xhr.pollTimer] ID of the timeout to clear
  *
- * @globals pollTimer, commits, inProgress
+ * @globals commits
  */
 function handleError(xhr) {
        errorInfo('Server error: ' +
                xhr.status + ' - ' + (xhr.statusText || 'Error contacting server'));
 
-       clearInterval(pollTimer);
+       if (typeof xhr.pollTimer === "number") {
+               clearTimeout(xhr.pollTimer);
+               delete xhr.pollTimer;
+       }
        commits = {}; // free memory
-
-       inProgress = false;
 }
 
 /**
  * Called after XMLHttpRequest finishes (loads)
  *
- * @param {XMLHttpRequest} xhr: XMLHttpRequest object (unused)
+ * @param {XMLHttpRequest} xhr: XMLHttpRequest object
+ * @param {Number} [xhr.pollTimer] ID of the timeout to clear
  *
- * @globals pollTimer, commits, inProgress
+ * @globals commits
  */
 function responseLoaded(xhr) {
-       clearInterval(pollTimer);
+       if (typeof xhr.pollTimer === "number") {
+               clearTimeout(xhr.pollTimer);
+               delete xhr.pollTimer;
+       }
 
        fixColorsAndGroups();
        writeTimeInterval();
        commits = {}; // free memory
-
-       inProgress = false;
 }
 
 /**
  * handler for XMLHttpRequest onreadystatechange event
  * @see startBlame
  *
- * @globals xhr, inProgress
+ * @param {XMLHttpRequest} xhr: XMLHttpRequest object
+ * @param {Number} xhr.prevDataLength: previous value of xhr.responseText.length
+ * @param {Number} xhr.nextReadPos: start of unread part of xhr.responseText
+ * @param {Number} [xhr.pollTimer] ID of the timeout (to reset or cancel)
+ * @param {Boolean} fromTimer: if handler was called from timer
  */
-function handleResponse() {
+function handleResponse(xhr, fromTimer) {
 
        /*
         * xhr.readyState
@@ -609,32 +612,31 @@ function handleResponse() {
                return;
        }
 
-       // in case we were called before finished processing
-       if (inProgress) {
-               return;
-       } else {
-               inProgress = true;
-       }
 
        // extract new whole (complete) lines, and process them
-       while (xhr.prevDataLength !== xhr.responseText.length) {
-               if (xhr.readyState === 4 &&
-                   xhr.prevDataLength === xhr.responseText.length) {
-                       break;
-               }
-
+       if (xhr.prevDataLength !== xhr.responseText.length) {
                xhr.prevDataLength = xhr.responseText.length;
                var unprocessed = xhr.responseText.substring(xhr.nextReadPos);
                xhr.nextReadPos = processData(unprocessed, xhr.nextReadPos);
-       } // end while
+       }
 
        // did we finish work?
-       if (xhr.readyState === 4 &&
-           xhr.prevDataLength === xhr.responseText.length) {
+       if (xhr.readyState === 4) {
                responseLoaded(xhr);
+               return;
        }
 
-       inProgress = false;
+       // if we get from timer, we have to restart it
+       // otherwise onreadystatechange gives us partial response, timer not needed
+       if (fromTimer) {
+               setTimeout(function () {
+                       handleResponse(xhr, true);
+               }, 1000);
+
+       } else if (typeof xhr.pollTimer === "number") {
+               clearTimeout(xhr.pollTimer);
+               delete xhr.pollTimer;
+       }
 }
 
 // ============================================================
@@ -649,11 +651,11 @@ function handleResponse() {
  * Called from 'blame_incremental' view after loading table with
  * file contents, a base for blame view.
  *
- * @globals xhr, t0, projectUrl, div_progress_bar, totalLines, pollTimer
+ * @globals t0, projectUrl, div_progress_bar, totalLines
 */
 function startBlame(blamedataUrl, bUrl) {
 
-       xhr = createRequestObject();
+       var xhr = createRequestObject();
        if (!xhr) {
                errorInfo('ERROR: XMLHttpRequest not supported');
                return;
@@ -672,8 +674,9 @@ function startBlame(blamedataUrl, bUrl) {
        xhr.prevDataLength = -1;  // used to detect if we have new data
        xhr.nextReadPos = 0;      // where unread part of response starts
 
-       xhr.onreadystatechange = handleResponse;
-       //xhr.onreadystatechange = function () { handleResponse(xhr); };
+       xhr.onreadystatechange = function () {
+               handleResponse(xhr, false);
+       };
 
        xhr.open('GET', blamedataUrl);
        xhr.setRequestHeader('Accept', 'text/plain');
@@ -681,7 +684,9 @@ function startBlame(blamedataUrl, bUrl) {
 
        // not all browsers call onreadystatechange event on each server flush
        // poll response using timer every second to handle this issue
-       pollTimer = setInterval(xhr.onreadystatechange, 1000);
+       xhr.pollTimer = setTimeout(function () {
+               handleResponse(xhr, true);
+       }, 1000);
 }
 
 /* end of blame_incremental.js */
index 93dd2bdd91b2b4b851f4ddc3b806e58bde792d56..fa2596f77c645b87e79aceb242d6074d6b2764ea 100644 (file)
@@ -16,7 +16,7 @@
  * and other reasons to not add 'js=1' param at the end of link
  * @constant
  */
-var jsExceptionsRe = /[;?]js=[01]$/;
+var jsExceptionsRe = /[;?]js=[01](#.*)?$/;
 
 /**
  * Add '?js=1' or ';js=1' to the end of every link in the document
@@ -33,9 +33,9 @@ function fixLinks() {
        var allLinks = document.getElementsByTagName("a") || document.links;
        for (var i = 0, len = allLinks.length; i < len; i++) {
                var link = allLinks[i];
-               if (!jsExceptionsRe.test(link)) { // =~ /[;?]js=[01]$/;
-                       link.href +=
-                               (link.href.indexOf('?') === -1 ? '?' : ';') + 'js=1';
+               if (!jsExceptionsRe.test(link)) {
+                       link.href = link.href.replace(/(#|$)/,
+                               (link.href.indexOf('?') === -1 ? '?' : ';') + 'js=1$1');
                }
        }
 }
diff --git a/graph.c b/graph.c
index 2f6893dc4f97dfbccad6f22f66861c24021a3c46..7358416a72e855b406e026036cf61bcdd15e5142 100644 (file)
--- a/graph.c
+++ b/graph.c
@@ -347,7 +347,7 @@ static struct commit_list *first_interesting_parent(struct git_graph *graph)
 
 static unsigned short graph_get_current_column_color(const struct git_graph *graph)
 {
-       if (!DIFF_OPT_TST(&graph->revs->diffopt, COLOR_DIFF))
+       if (!want_color(graph->revs->diffopt.use_color))
                return column_colors_max;
        return graph->default_column_color;
 }
diff --git a/grep.c b/grep.c
index d03d9e24c23eff2d60ae7226a412f3ccf66670fd..b29d09c7f6a5a7f9621fb5287eb07e72ece7f484 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -137,16 +137,50 @@ static void free_pcre_regexp(struct grep_pat *p)
 }
 #endif /* !USE_LIBPCRE */
 
+static int is_fixed(const char *s, size_t len)
+{
+       size_t i;
+
+       /* regcomp cannot accept patterns with NULs so we
+        * consider any pattern containing a NUL fixed.
+        */
+       if (memchr(s, 0, len))
+               return 1;
+
+       for (i = 0; i < len; i++) {
+               if (is_regex_special(s[i]))
+                       return 0;
+       }
+
+       return 1;
+}
+
 static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
 {
        int err;
 
        p->word_regexp = opt->word_regexp;
        p->ignore_case = opt->ignore_case;
-       p->fixed = opt->fixed;
 
-       if (p->fixed)
+       if (opt->fixed || is_fixed(p->pattern, p->patternlen))
+               p->fixed = 1;
+       else
+               p->fixed = 0;
+
+       if (p->fixed) {
+               if (opt->regflags & REG_ICASE || p->ignore_case) {
+                       static char trans[256];
+                       int i;
+                       for (i = 0; i < 256; i++)
+                               trans[i] = tolower(i);
+                       p->kws = kwsalloc(trans);
+               } else {
+                       p->kws = kwsalloc(NULL);
+               }
+               kwsincr(p->kws, p->pattern, p->patternlen);
+               kwsprep(p->kws);
                return;
+       }
 
        if (opt->pcre) {
                compile_pcre_regexp(p, opt);
@@ -395,7 +429,9 @@ void free_grep_patterns(struct grep_opt *opt)
                case GREP_PATTERN: /* atom */
                case GREP_PATTERN_HEAD:
                case GREP_PATTERN_BODY:
-                       if (p->pcre_regexp)
+                       if (p->kws)
+                               kwsfree(p->kws);
+                       else if (p->pcre_regexp)
                                free_pcre_regexp(p);
                        else
                                regfree(&p->regexp);
@@ -430,7 +466,7 @@ static int word_char(char ch)
 static void output_color(struct grep_opt *opt, const void *data, size_t size,
                         const char *color)
 {
-       if (opt->color && color && color[0]) {
+       if (want_color(opt->color) && color && color[0]) {
                opt->output(opt, color, strlen(color));
                opt->output(opt, data, size);
                opt->output(opt, GIT_COLOR_RESET, strlen(GIT_COLOR_RESET));
@@ -455,26 +491,14 @@ static void show_name(struct grep_opt *opt, const char *name)
 static int fixmatch(struct grep_pat *p, char *line, char *eol,
                    regmatch_t *match)
 {
-       char *hit;
-
-       if (p->ignore_case) {
-               char *s = line;
-               do {
-                       hit = strcasestr(s, p->pattern);
-                       if (hit)
-                               break;
-                       s += strlen(s) + 1;
-               } while (s < eol);
-       } else
-               hit = memmem(line, eol - line, p->pattern, p->patternlen);
-
-       if (!hit) {
+       struct kwsmatch kwsm;
+       size_t offset = kwsexec(p->kws, line, eol - line, &kwsm);
+       if (offset == -1) {
                match->rm_so = match->rm_eo = -1;
                return REG_NOMATCH;
-       }
-       else {
-               match->rm_so = hit - line;
-               match->rm_eo = match->rm_so + p->patternlen;
+       } else {
+               match->rm_so = offset;
+               match->rm_eo = match->rm_so + kwsm.size[0];
                return 0;
        }
 }
@@ -721,7 +745,10 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
        int rest = eol - bol;
        char *line_color = NULL;
 
-       if (opt->pre_context || opt->post_context) {
+       if (opt->file_break && opt->last_shown == 0) {
+               if (opt->show_hunk_mark)
+                       opt->output(opt, "\n", 1);
+       } else if (opt->pre_context || opt->post_context || opt->funcbody) {
                if (opt->last_shown == 0) {
                        if (opt->show_hunk_mark) {
                                output_color(opt, "--", 2, opt->color_sep);
@@ -732,9 +759,13 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
                        opt->output(opt, "\n", 1);
                }
        }
+       if (opt->heading && opt->last_shown == 0) {
+               output_color(opt, name, strlen(name), opt->color_filename);
+               opt->output(opt, "\n", 1);
+       }
        opt->last_shown = lno;
 
-       if (opt->pathname) {
+       if (!opt->heading && opt->pathname) {
                output_color(opt, name, strlen(name), opt->color_filename);
                output_sep(opt, sign);
        }
@@ -812,10 +843,13 @@ static void show_funcname_line(struct grep_opt *opt, const char *name,
 }
 
 static void show_pre_context(struct grep_opt *opt, const char *name, char *buf,
-                            char *bol, unsigned lno)
+                            char *bol, char *end, unsigned lno)
 {
        unsigned cur = lno, from = 1, funcname_lno = 0;
-       int funcname_needed = opt->funcname;
+       int funcname_needed = !!opt->funcname;
+
+       if (opt->funcbody && !match_funcname(opt, bol, end))
+               funcname_needed = 2;
 
        if (opt->pre_context < lno)
                from = lno - opt->pre_context;
@@ -823,7 +857,8 @@ static void show_pre_context(struct grep_opt *opt, const char *name, char *buf,
                from = opt->last_shown + 1;
 
        /* Rewind. */
-       while (bol > buf && cur > from) {
+       while (bol > buf &&
+              cur > (funcname_needed == 2 ? opt->last_shown + 1 : from)) {
                char *eol = --bol;
 
                while (bol > buf && bol[-1] != '\n')
@@ -935,15 +970,26 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
        int binary_match_only = 0;
        unsigned count = 0;
        int try_lookahead = 0;
+       int show_function = 0;
        enum grep_context ctx = GREP_CONTEXT_HEAD;
        xdemitconf_t xecfg;
 
        if (!opt->output)
                opt->output = std_output;
 
-       if (opt->last_shown && (opt->pre_context || opt->post_context) &&
-           opt->output == std_output)
-               opt->show_hunk_mark = 1;
+       if (opt->pre_context || opt->post_context || opt->file_break ||
+           opt->funcbody) {
+               /* Show hunk marks, except for the first file. */
+               if (opt->last_shown)
+                       opt->show_hunk_mark = 1;
+               /*
+                * If we're using threads then we can't easily identify
+                * the first file.  Always put hunk marks in that case
+                * and skip the very first one later in work_done().
+                */
+               if (opt->output != std_output)
+                       opt->show_hunk_mark = 1;
+       }
        opt->last_shown = 0;
 
        switch (opt->binary) {
@@ -988,7 +1034,8 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
                 */
                if (try_lookahead
                    && !(last_hit
-                        && lno <= last_hit + opt->post_context)
+                        && (show_function ||
+                            lno <= last_hit + opt->post_context))
                    && look_ahead(opt, &left, &lno, &bol))
                        break;
                eol = end_of_line(bol, &left);
@@ -1035,15 +1082,20 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
                        /* Hit at this line.  If we haven't shown the
                         * pre-context lines, we would need to show them.
                         */
-                       if (opt->pre_context)
-                               show_pre_context(opt, name, buf, bol, lno);
+                       if (opt->pre_context || opt->funcbody)
+                               show_pre_context(opt, name, buf, bol, eol, lno);
                        else if (opt->funcname)
                                show_funcname_line(opt, name, buf, bol, lno);
                        show_line(opt, bol, eol, name, lno, ':');
                        last_hit = lno;
+                       if (opt->funcbody)
+                               show_function = 1;
+                       goto next_line;
                }
-               else if (last_hit &&
-                        lno <= last_hit + opt->post_context) {
+               if (show_function && match_funcname(opt, bol, eol))
+                       show_function = 0;
+               if (show_function ||
+                   (last_hit && lno <= last_hit + opt->post_context)) {
                        /* If the last hit is within the post context,
                         * we need to show this line.
                         */
diff --git a/grep.h b/grep.h
index cd055cdfa8cac903382d592f1ec7e2a22bf7f897..a65280026d5dee8ab059bead79f05d6a1111147a 100644 (file)
--- a/grep.h
+++ b/grep.h
@@ -7,6 +7,7 @@
 typedef int pcre;
 typedef int pcre_extra;
 #endif
+#include "kwset.h"
 
 enum grep_pat_token {
        GREP_PATTERN,
@@ -41,6 +42,7 @@ struct grep_pat {
        regex_t regexp;
        pcre *pcre_regexp;
        pcre_extra *pcre_extra_info;
+       kwset_t kws;
        unsigned fixed:1;
        unsigned ignore_case:1;
        unsigned word_regexp:1;
@@ -98,6 +100,7 @@ struct grep_opt {
        int color;
        int max_depth;
        int funcname;
+       int funcbody;
        char color_context[COLOR_MAXLEN];
        char color_filename[COLOR_MAXLEN];
        char color_function[COLOR_MAXLEN];
@@ -110,6 +113,8 @@ struct grep_opt {
        unsigned post_context;
        unsigned last_shown;
        int show_hunk_mark;
+       int file_break;
+       int heading;
        void *priv;
 
        void (*output)(struct grep_opt *opt, const void *data, size_t size);
diff --git a/help.c b/help.c
index 7654f1bfd608d151a213937614449cc497bb638b..cbbe966f685b276cac702bb0fd9a44bfbf5f0e79 100644 (file)
--- a/help.c
+++ b/help.c
@@ -127,7 +127,10 @@ static int is_executable(const char *name)
            !S_ISREG(st.st_mode))
                return 0;
 
-#ifdef WIN32
+#if defined(WIN32) || defined(__CYGWIN__)
+#if defined(__CYGWIN__)
+if ((st.st_mode & S_IXUSR) == 0)
+#endif
 {      /* cannot trust the executable bit, peek into the file instead */
        char buf[3] = { 0 };
        int n;
@@ -302,6 +305,10 @@ static void add_cmd_list(struct cmdnames *cmds, struct cmdnames *old)
 #define SIMILARITY_FLOOR 7
 #define SIMILAR_ENOUGH(x) ((x) < SIMILARITY_FLOOR)
 
+static const char bad_interpreter_advice[] =
+       N_("'%s' appears to be a git command, but we were not\n"
+       "able to execute it. Maybe git-%s is broken?");
+
 const char *help_unknown_cmd(const char *cmd)
 {
        int i, n, best_similarity = 0;
@@ -326,6 +333,14 @@ const char *help_unknown_cmd(const char *cmd)
                int cmp = 0; /* avoid compiler stupidity */
                const char *candidate = main_cmds.names[i]->name;
 
+               /*
+                * An exact match means we have the command, but
+                * for some reason exec'ing it gave us ENOENT; probably
+                * it's a bad interpreter in the #! line.
+                */
+               if (!strcmp(candidate, cmd))
+                       die(_(bad_interpreter_advice), cmd, cmd);
+
                /* Does the candidate appear in common_cmds list? */
                while (n < ARRAY_SIZE(common_cmds) &&
                       (cmp = strcmp(common_cmds[n].name, candidate)) < 0)
index 85015048dd466fb2f2afe12086379ecf579d0a56..59ad7da605f711af6970d6832db32efd62b6ea97 100644 (file)
@@ -271,16 +271,13 @@ static struct rpc_service *select_service(const char *name)
 
 static void inflate_request(const char *prog_name, int out)
 {
-       z_stream stream;
+       git_zstream stream;
        unsigned char in_buf[8192];
        unsigned char out_buf[8192];
        unsigned long cnt = 0;
-       int ret;
 
        memset(&stream, 0, sizeof(stream));
-       ret = inflateInit2(&stream, (15 + 16));
-       if (ret != Z_OK)
-               die("cannot start zlib inflater, zlib err %d", ret);
+       git_inflate_init_gzip_only(&stream);
 
        while (1) {
                ssize_t n = xread(0, in_buf, sizeof(in_buf));
@@ -296,7 +293,7 @@ static void inflate_request(const char *prog_name, int out)
                        stream.next_out = out_buf;
                        stream.avail_out = sizeof(out_buf);
 
-                       ret = inflate(&stream, Z_NO_FLUSH);
+                       ret = git_inflate(&stream, Z_NO_FLUSH);
                        if (ret != Z_OK && ret != Z_STREAM_END)
                                die("zlib error inflating request, result %d", ret);
 
@@ -311,7 +308,7 @@ static void inflate_request(const char *prog_name, int out)
        }
 
 done:
-       inflateEnd(&stream);
+       git_inflate_end(&stream);
        close(out);
 }
 
index 3af4c71bd0fb8370c6f08b0e3d43adc722cac2f4..8c4c5d2224a2493a648e6a34257bc150f2712dd0 100644 (file)
@@ -56,6 +56,10 @@ int main(int argc, const char **argv)
                commits = 1;
        }
 
+       if (get_all == 0)
+               warning("http-fetch: use without -a is deprecated.\n"
+                       "In a future release, -a will become the default.");
+
        if (argv[arg])
                str_end_url_with_slash(argv[arg], &url);
 
index 28bfe768f7749e455f522fcfedeb2350da646416..44f814cda2a9756a55cb7f332d5fef0e5484256b 100644 (file)
@@ -377,15 +377,15 @@ static void start_put(struct transfer_request *request)
        unsigned long len;
        int hdrlen;
        ssize_t size;
-       z_stream stream;
+       git_zstream stream;
 
        unpacked = read_sha1_file(request->obj->sha1, &type, &len);
        hdrlen = sprintf(hdr, "%s %lu", typename(type), len) + 1;
 
        /* Set it up */
        memset(&stream, 0, sizeof(stream));
-       deflateInit(&stream, zlib_compression_level);
-       size = deflateBound(&stream, len + hdrlen);
+       git_deflate_init(&stream, zlib_compression_level);
+       size = git_deflate_bound(&stream, len + hdrlen);
        strbuf_init(&request->buffer.buf, size);
        request->buffer.posn = 0;
 
@@ -396,15 +396,15 @@ static void start_put(struct transfer_request *request)
        /* First header.. */
        stream.next_in = (void *)hdr;
        stream.avail_in = hdrlen;
-       while (deflate(&stream, 0) == Z_OK)
-               /* nothing */;
+       while (git_deflate(&stream, 0) == Z_OK)
+               ; /* nothing */
 
        /* Then the data itself.. */
        stream.next_in = unpacked;
        stream.avail_in = len;
-       while (deflate(&stream, Z_FINISH) == Z_OK)
-               /* nothing */;
-       deflateEnd(&stream);
+       while (git_deflate(&stream, Z_FINISH) == Z_OK)
+               ; /* nothing */
+       git_deflate_end(&stream);
        free(unpacked);
 
        request->buffer.buf.len = stream.total_out;
@@ -1606,10 +1606,10 @@ static void fetch_symref(const char *path, char **symref, unsigned char *sha1)
        strbuf_release(&buffer);
 }
 
-static int verify_merge_base(unsigned char *head_sha1, unsigned char *branch_sha1)
+static int verify_merge_base(unsigned char *head_sha1, struct ref *remote)
 {
-       struct commit *head = lookup_commit(head_sha1);
-       struct commit *branch = lookup_commit(branch_sha1);
+       struct commit *head = lookup_commit_or_die(head_sha1, "HEAD");
+       struct commit *branch = lookup_commit_or_die(remote->old_sha1, remote->name);
        struct commit_list *merge_bases = get_merge_bases(head, branch, 1);
 
        return (merge_bases && !merge_bases->next && merge_bases->item == branch);
@@ -1655,7 +1655,7 @@ static int delete_remote_branch(const char *pattern, int force)
                return error("Remote HEAD is not a symref");
 
        /* Remote branch must not be the remote HEAD */
-       for (i=0; symref && i<MAXDEPTH; i++) {
+       for (i = 0; symref && i < MAXDEPTH; i++) {
                if (!strcmp(remote_ref->name, symref))
                        return error("Remote branch %s is the current HEAD",
                                     remote_ref->name);
@@ -1680,7 +1680,7 @@ static int delete_remote_branch(const char *pattern, int force)
                        return error("Remote branch %s resolves to object %s\nwhich does not exist locally, perhaps you need to fetch?", remote_ref->name, sha1_to_hex(remote_ref->old_sha1));
 
                /* Remote branch must be an ancestor of remote HEAD */
-               if (!verify_merge_base(head_sha1, remote_ref->old_sha1)) {
+               if (!verify_merge_base(head_sha1, remote_ref)) {
                        return error("The branch '%s' is not an ancestor "
                                     "of your current HEAD.\n"
                                     "If you are sure you want to delete it,"
diff --git a/http.c b/http.c
index b2ae8de16db3abe2cad27249ae767f421aa6bb24..a59cac45d7a6c159e740cab9b20bb63ab91f7c48 100644 (file)
--- a/http.c
+++ b/http.c
@@ -41,6 +41,7 @@ static long curl_low_speed_limit = -1;
 static long curl_low_speed_time = -1;
 static int curl_ftp_no_epsv;
 static const char *curl_http_proxy;
+static const char *curl_cookie_file;
 static char *user_name, *user_pass;
 static const char *user_agent;
 
@@ -191,6 +192,9 @@ static int http_options(const char *var, const char *value, void *cb)
        if (!strcmp("http.proxy", var))
                return git_config_string(&curl_http_proxy, var, value);
 
+       if (!strcmp("http.cookiefile", var))
+               return git_config_string(&curl_cookie_file, var, value);
+
        if (!strcmp("http.postbuffer", var)) {
                http_post_buffer = git_config_int(var, value);
                if (http_post_buffer < LARGE_PACKET_MAX)
@@ -531,6 +535,7 @@ struct active_request_slot *get_active_slot(void)
        slot->finished = NULL;
        slot->callback_data = NULL;
        slot->callback_func = NULL;
+       curl_easy_setopt(slot->curl, CURLOPT_COOKIEFILE, curl_cookie_file);
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header);
        curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr);
        curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, NULL);
@@ -1116,9 +1121,8 @@ struct http_pack_request *new_http_pack_request(
        struct strbuf buf = STRBUF_INIT;
        struct http_pack_request *preq;
 
-       preq = xmalloc(sizeof(*preq));
+       preq = xcalloc(1, sizeof(*preq));
        preq->target = target;
-       preq->range_header = NULL;
 
        end_url_with_slash(&buf, base_url);
        strbuf_addf(&buf, "objects/pack/pack-%s.pack",
@@ -1210,7 +1214,7 @@ struct http_object_request *new_http_object_request(const char *base_url,
        struct curl_slist *range_header = NULL;
        struct http_object_request *freq;
 
-       freq = xmalloc(sizeof(*freq));
+       freq = xcalloc(1, sizeof(*freq));
        hashcpy(freq->sha1, sha1);
        freq->localfile = -1;
 
@@ -1248,8 +1252,6 @@ struct http_object_request *new_http_object_request(const char *base_url,
                goto abort;
        }
 
-       memset(&freq->stream, 0, sizeof(freq->stream));
-
        git_inflate_init(&freq->stream);
 
        git_SHA1_Init(&freq->c);
@@ -1324,7 +1326,6 @@ struct http_object_request *new_http_object_request(const char *base_url,
        return freq;
 
 abort:
-       free(filename);
        free(freq->url);
        free(freq);
        return NULL;
diff --git a/http.h b/http.h
index 19b7134fa5c7ff9b65565dcbddccae1354f024c7..0bf8592dc45209c9be5b8ddac3a53f6fc11e29be 100644 (file)
--- a/http.h
+++ b/http.h
@@ -172,7 +172,7 @@ struct http_object_request {
        unsigned char sha1[20];
        unsigned char real_sha1[20];
        git_SHA_CTX c;
-       z_stream stream;
+       git_zstream stream;
        int zret;
        int rename;
        struct active_request_slot *slot;
diff --git a/kwset.c b/kwset.c
new file mode 100644 (file)
index 0000000..956ae72
--- /dev/null
+++ b/kwset.c
@@ -0,0 +1,771 @@
+/*
+ * This file has been copied from commit e7ac713d^ in the GNU grep git
+ * repository. A few small changes have been made to adapt the code to
+ * Git.
+ */
+
+/* kwset.c - search for any of a set of keywords.
+   Copyright 1989, 1998, 2000, 2005 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA
+   02110-1301, USA.  */
+
+/* Written August 1989 by Mike Haertel.
+   The author may be reached (Email) at the address mike@ai.mit.edu,
+   or (US mail) as Mike Haertel c/o Free Software Foundation. */
+
+/* The algorithm implemented by these routines bears a startling resemblence
+   to one discovered by Beate Commentz-Walter, although it is not identical.
+   See "A String Matching Algorithm Fast on the Average," Technical Report,
+   IBM-Germany, Scientific Center Heidelberg, Tiergartenstrasse 15, D-6900
+   Heidelberg, Germany.  See also Aho, A.V., and M. Corasick, "Efficient
+   String Matching:  An Aid to Bibliographic Search," CACM June 1975,
+   Vol. 18, No. 6, which describes the failure function used below. */
+
+#include "cache.h"
+
+#include "kwset.h"
+#include "compat/obstack.h"
+
+#define NCHAR (UCHAR_MAX + 1)
+#define obstack_chunk_alloc xmalloc
+#define obstack_chunk_free free
+
+#define U(c) ((unsigned char) (c))
+
+/* Balanced tree of edges and labels leaving a given trie node. */
+struct tree
+{
+  struct tree *llink;          /* Left link; MUST be first field. */
+  struct tree *rlink;          /* Right link (to larger labels). */
+  struct trie *trie;           /* Trie node pointed to by this edge. */
+  unsigned char label;         /* Label on this edge. */
+  char balance;                        /* Difference in depths of subtrees. */
+};
+
+/* Node of a trie representing a set of reversed keywords. */
+struct trie
+{
+  unsigned int accepting;      /* Word index of accepted word, or zero. */
+  struct tree *links;          /* Tree of edges leaving this node. */
+  struct trie *parent;         /* Parent of this node. */
+  struct trie *next;           /* List of all trie nodes in level order. */
+  struct trie *fail;           /* Aho-Corasick failure function. */
+  int depth;                   /* Depth of this node from the root. */
+  int shift;                   /* Shift function for search failures. */
+  int maxshift;                        /* Max shift of self and descendents. */
+};
+
+/* Structure returned opaquely to the caller, containing everything. */
+struct kwset
+{
+  struct obstack obstack;      /* Obstack for node allocation. */
+  int words;                   /* Number of words in the trie. */
+  struct trie *trie;           /* The trie itself. */
+  int mind;                    /* Minimum depth of an accepting node. */
+  int maxd;                    /* Maximum depth of any node. */
+  unsigned char delta[NCHAR];  /* Delta table for rapid search. */
+  struct trie *next[NCHAR];    /* Table of children of the root. */
+  char *target;                        /* Target string if there's only one. */
+  int mind2;                   /* Used in Boyer-Moore search for one string. */
+  char const *trans;           /* Character translation table. */
+};
+
+/* Allocate and initialize a keyword set object, returning an opaque
+   pointer to it.  Return NULL if memory is not available. */
+kwset_t
+kwsalloc (char const *trans)
+{
+  struct kwset *kwset;
+
+  kwset = (struct kwset *) xmalloc(sizeof (struct kwset));
+
+  obstack_init(&kwset->obstack);
+  kwset->words = 0;
+  kwset->trie
+    = (struct trie *) obstack_alloc(&kwset->obstack, sizeof (struct trie));
+  if (!kwset->trie)
+    {
+      kwsfree((kwset_t) kwset);
+      return NULL;
+    }
+  kwset->trie->accepting = 0;
+  kwset->trie->links = NULL;
+  kwset->trie->parent = NULL;
+  kwset->trie->next = NULL;
+  kwset->trie->fail = NULL;
+  kwset->trie->depth = 0;
+  kwset->trie->shift = 0;
+  kwset->mind = INT_MAX;
+  kwset->maxd = -1;
+  kwset->target = NULL;
+  kwset->trans = trans;
+
+  return (kwset_t) kwset;
+}
+
+/* This upper bound is valid for CHAR_BIT >= 4 and
+   exact for CHAR_BIT in { 4..11, 13, 15, 17, 19 }. */
+#define DEPTH_SIZE (CHAR_BIT + CHAR_BIT/2)
+
+/* Add the given string to the contents of the keyword set.  Return NULL
+   for success, an error message otherwise. */
+const char *
+kwsincr (kwset_t kws, char const *text, size_t len)
+{
+  struct kwset *kwset;
+  register struct trie *trie;
+  register unsigned char label;
+  register struct tree *link;
+  register int depth;
+  struct tree *links[DEPTH_SIZE];
+  enum { L, R } dirs[DEPTH_SIZE];
+  struct tree *t, *r, *l, *rl, *lr;
+
+  kwset = (struct kwset *) kws;
+  trie = kwset->trie;
+  text += len;
+
+  /* Descend the trie (built of reversed keywords) character-by-character,
+     installing new nodes when necessary. */
+  while (len--)
+    {
+      label = kwset->trans ? kwset->trans[U(*--text)] : *--text;
+
+      /* Descend the tree of outgoing links for this trie node,
+        looking for the current character and keeping track
+        of the path followed. */
+      link = trie->links;
+      links[0] = (struct tree *) &trie->links;
+      dirs[0] = L;
+      depth = 1;
+
+      while (link && label != link->label)
+       {
+         links[depth] = link;
+         if (label < link->label)
+           dirs[depth++] = L, link = link->llink;
+         else
+           dirs[depth++] = R, link = link->rlink;
+       }
+
+      /* The current character doesn't have an outgoing link at
+        this trie node, so build a new trie node and install
+        a link in the current trie node's tree. */
+      if (!link)
+       {
+         link = (struct tree *) obstack_alloc(&kwset->obstack,
+                                              sizeof (struct tree));
+         if (!link)
+           return "memory exhausted";
+         link->llink = NULL;
+         link->rlink = NULL;
+         link->trie = (struct trie *) obstack_alloc(&kwset->obstack,
+                                                    sizeof (struct trie));
+         if (!link->trie)
+           {
+             obstack_free(&kwset->obstack, link);
+             return "memory exhausted";
+           }
+         link->trie->accepting = 0;
+         link->trie->links = NULL;
+         link->trie->parent = trie;
+         link->trie->next = NULL;
+         link->trie->fail = NULL;
+         link->trie->depth = trie->depth + 1;
+         link->trie->shift = 0;
+         link->label = label;
+         link->balance = 0;
+
+         /* Install the new tree node in its parent. */
+         if (dirs[--depth] == L)
+           links[depth]->llink = link;
+         else
+           links[depth]->rlink = link;
+
+         /* Back up the tree fixing the balance flags. */
+         while (depth && !links[depth]->balance)
+           {
+             if (dirs[depth] == L)
+               --links[depth]->balance;
+             else
+               ++links[depth]->balance;
+             --depth;
+           }
+
+         /* Rebalance the tree by pointer rotations if necessary. */
+         if (depth && ((dirs[depth] == L && --links[depth]->balance)
+                       || (dirs[depth] == R && ++links[depth]->balance)))
+           {
+             switch (links[depth]->balance)
+               {
+               case (char) -2:
+                 switch (dirs[depth + 1])
+                   {
+                   case L:
+                     r = links[depth], t = r->llink, rl = t->rlink;
+                     t->rlink = r, r->llink = rl;
+                     t->balance = r->balance = 0;
+                     break;
+                   case R:
+                     r = links[depth], l = r->llink, t = l->rlink;
+                     rl = t->rlink, lr = t->llink;
+                     t->llink = l, l->rlink = lr, t->rlink = r, r->llink = rl;
+                     l->balance = t->balance != 1 ? 0 : -1;
+                     r->balance = t->balance != (char) -1 ? 0 : 1;
+                     t->balance = 0;
+                     break;
+                   default:
+                     abort ();
+                   }
+                 break;
+               case 2:
+                 switch (dirs[depth + 1])
+                   {
+                   case R:
+                     l = links[depth], t = l->rlink, lr = t->llink;
+                     t->llink = l, l->rlink = lr;
+                     t->balance = l->balance = 0;
+                     break;
+                   case L:
+                     l = links[depth], r = l->rlink, t = r->llink;
+                     lr = t->llink, rl = t->rlink;
+                     t->llink = l, l->rlink = lr, t->rlink = r, r->llink = rl;
+                     l->balance = t->balance != 1 ? 0 : -1;
+                     r->balance = t->balance != (char) -1 ? 0 : 1;
+                     t->balance = 0;
+                     break;
+                   default:
+                     abort ();
+                   }
+                 break;
+               default:
+                 abort ();
+               }
+
+             if (dirs[depth - 1] == L)
+               links[depth - 1]->llink = t;
+             else
+               links[depth - 1]->rlink = t;
+           }
+       }
+
+      trie = link->trie;
+    }
+
+  /* Mark the node we finally reached as accepting, encoding the
+     index number of this word in the keyword set so far. */
+  if (!trie->accepting)
+    trie->accepting = 1 + 2 * kwset->words;
+  ++kwset->words;
+
+  /* Keep track of the longest and shortest string of the keyword set. */
+  if (trie->depth < kwset->mind)
+    kwset->mind = trie->depth;
+  if (trie->depth > kwset->maxd)
+    kwset->maxd = trie->depth;
+
+  return NULL;
+}
+
+/* Enqueue the trie nodes referenced from the given tree in the
+   given queue. */
+static void
+enqueue (struct tree *tree, struct trie **last)
+{
+  if (!tree)
+    return;
+  enqueue(tree->llink, last);
+  enqueue(tree->rlink, last);
+  (*last) = (*last)->next = tree->trie;
+}
+
+/* Compute the Aho-Corasick failure function for the trie nodes referenced
+   from the given tree, given the failure function for their parent as
+   well as a last resort failure node. */
+static void
+treefails (register struct tree const *tree, struct trie const *fail,
+          struct trie *recourse)
+{
+  register struct tree *link;
+
+  if (!tree)
+    return;
+
+  treefails(tree->llink, fail, recourse);
+  treefails(tree->rlink, fail, recourse);
+
+  /* Find, in the chain of fails going back to the root, the first
+     node that has a descendent on the current label. */
+  while (fail)
+    {
+      link = fail->links;
+      while (link && tree->label != link->label)
+       if (tree->label < link->label)
+         link = link->llink;
+       else
+         link = link->rlink;
+      if (link)
+       {
+         tree->trie->fail = link->trie;
+         return;
+       }
+      fail = fail->fail;
+    }
+
+  tree->trie->fail = recourse;
+}
+
+/* Set delta entries for the links of the given tree such that
+   the preexisting delta value is larger than the current depth. */
+static void
+treedelta (register struct tree const *tree,
+          register unsigned int depth,
+          unsigned char delta[])
+{
+  if (!tree)
+    return;
+  treedelta(tree->llink, depth, delta);
+  treedelta(tree->rlink, depth, delta);
+  if (depth < delta[tree->label])
+    delta[tree->label] = depth;
+}
+
+/* Return true if A has every label in B. */
+static int
+hasevery (register struct tree const *a, register struct tree const *b)
+{
+  if (!b)
+    return 1;
+  if (!hasevery(a, b->llink))
+    return 0;
+  if (!hasevery(a, b->rlink))
+    return 0;
+  while (a && b->label != a->label)
+    if (b->label < a->label)
+      a = a->llink;
+    else
+      a = a->rlink;
+  return !!a;
+}
+
+/* Compute a vector, indexed by character code, of the trie nodes
+   referenced from the given tree. */
+static void
+treenext (struct tree const *tree, struct trie *next[])
+{
+  if (!tree)
+    return;
+  treenext(tree->llink, next);
+  treenext(tree->rlink, next);
+  next[tree->label] = tree->trie;
+}
+
+/* Compute the shift for each trie node, as well as the delta
+   table and next cache for the given keyword set. */
+const char *
+kwsprep (kwset_t kws)
+{
+  register struct kwset *kwset;
+  register int i;
+  register struct trie *curr;
+  register char const *trans;
+  unsigned char delta[NCHAR];
+
+  kwset = (struct kwset *) kws;
+
+  /* Initial values for the delta table; will be changed later.  The
+     delta entry for a given character is the smallest depth of any
+     node at which an outgoing edge is labeled by that character. */
+  memset(delta, kwset->mind < UCHAR_MAX ? kwset->mind : UCHAR_MAX, NCHAR);
+
+  /* Check if we can use the simple boyer-moore algorithm, instead
+     of the hairy commentz-walter algorithm. */
+  if (kwset->words == 1 && kwset->trans == NULL)
+    {
+      char c;
+
+      /* Looking for just one string.  Extract it from the trie. */
+      kwset->target = obstack_alloc(&kwset->obstack, kwset->mind);
+      if (!kwset->target)
+       return "memory exhausted";
+      for (i = kwset->mind - 1, curr = kwset->trie; i >= 0; --i)
+       {
+         kwset->target[i] = curr->links->label;
+         curr = curr->links->trie;
+       }
+      /* Build the Boyer Moore delta.  Boy that's easy compared to CW. */
+      for (i = 0; i < kwset->mind; ++i)
+       delta[U(kwset->target[i])] = kwset->mind - (i + 1);
+      /* Find the minimal delta2 shift that we might make after
+        a backwards match has failed. */
+      c = kwset->target[kwset->mind - 1];
+      for (i = kwset->mind - 2; i >= 0; --i)
+       if (kwset->target[i] == c)
+         break;
+      kwset->mind2 = kwset->mind - (i + 1);
+    }
+  else
+    {
+      register struct trie *fail;
+      struct trie *last, *next[NCHAR];
+
+      /* Traverse the nodes of the trie in level order, simultaneously
+        computing the delta table, failure function, and shift function. */
+      for (curr = last = kwset->trie; curr; curr = curr->next)
+       {
+         /* Enqueue the immediate descendents in the level order queue. */
+         enqueue(curr->links, &last);
+
+         curr->shift = kwset->mind;
+         curr->maxshift = kwset->mind;
+
+         /* Update the delta table for the descendents of this node. */
+         treedelta(curr->links, curr->depth, delta);
+
+         /* Compute the failure function for the decendents of this node. */
+         treefails(curr->links, curr->fail, kwset->trie);
+
+         /* Update the shifts at each node in the current node's chain
+            of fails back to the root. */
+         for (fail = curr->fail; fail; fail = fail->fail)
+           {
+             /* If the current node has some outgoing edge that the fail
+                doesn't, then the shift at the fail should be no larger
+                than the difference of their depths. */
+             if (!hasevery(fail->links, curr->links))
+               if (curr->depth - fail->depth < fail->shift)
+                 fail->shift = curr->depth - fail->depth;
+
+             /* If the current node is accepting then the shift at the
+                fail and its descendents should be no larger than the
+                difference of their depths. */
+             if (curr->accepting && fail->maxshift > curr->depth - fail->depth)
+               fail->maxshift = curr->depth - fail->depth;
+           }
+       }
+
+      /* Traverse the trie in level order again, fixing up all nodes whose
+        shift exceeds their inherited maxshift. */
+      for (curr = kwset->trie->next; curr; curr = curr->next)
+       {
+         if (curr->maxshift > curr->parent->maxshift)
+           curr->maxshift = curr->parent->maxshift;
+         if (curr->shift > curr->maxshift)
+           curr->shift = curr->maxshift;
+       }
+
+      /* Create a vector, indexed by character code, of the outgoing links
+        from the root node. */
+      for (i = 0; i < NCHAR; ++i)
+       next[i] = NULL;
+      treenext(kwset->trie->links, next);
+
+      if ((trans = kwset->trans) != NULL)
+       for (i = 0; i < NCHAR; ++i)
+         kwset->next[i] = next[U(trans[i])];
+      else
+       memcpy(kwset->next, next, NCHAR * sizeof(struct trie *));
+    }
+
+  /* Fix things up for any translation table. */
+  if ((trans = kwset->trans) != NULL)
+    for (i = 0; i < NCHAR; ++i)
+      kwset->delta[i] = delta[U(trans[i])];
+  else
+    memcpy(kwset->delta, delta, NCHAR);
+
+  return NULL;
+}
+
+/* Fast boyer-moore search. */
+static size_t
+bmexec (kwset_t kws, char const *text, size_t size)
+{
+  struct kwset const *kwset;
+  register unsigned char const *d1;
+  register char const *ep, *sp, *tp;
+  register int d, gc, i, len, md2;
+
+  kwset = (struct kwset const *) kws;
+  len = kwset->mind;
+
+  if (len == 0)
+    return 0;
+  if (len > size)
+    return -1;
+  if (len == 1)
+    {
+      tp = memchr (text, kwset->target[0], size);
+      return tp ? tp - text : -1;
+    }
+
+  d1 = kwset->delta;
+  sp = kwset->target + len;
+  gc = U(sp[-2]);
+  md2 = kwset->mind2;
+  tp = text + len;
+
+  /* Significance of 12: 1 (initial offset) + 10 (skip loop) + 1 (md2). */
+  if (size > 12 * len)
+    /* 11 is not a bug, the initial offset happens only once. */
+    for (ep = text + size - 11 * len;;)
+      {
+       while (tp <= ep)
+         {
+           d = d1[U(tp[-1])], tp += d;
+           d = d1[U(tp[-1])], tp += d;
+           if (d == 0)
+             goto found;
+           d = d1[U(tp[-1])], tp += d;
+           d = d1[U(tp[-1])], tp += d;
+           d = d1[U(tp[-1])], tp += d;
+           if (d == 0)
+             goto found;
+           d = d1[U(tp[-1])], tp += d;
+           d = d1[U(tp[-1])], tp += d;
+           d = d1[U(tp[-1])], tp += d;
+           if (d == 0)
+             goto found;
+           d = d1[U(tp[-1])], tp += d;
+           d = d1[U(tp[-1])], tp += d;
+         }
+       break;
+      found:
+       if (U(tp[-2]) == gc)
+         {
+           for (i = 3; i <= len && U(tp[-i]) == U(sp[-i]); ++i)
+             ;
+           if (i > len)
+             return tp - len - text;
+         }
+       tp += md2;
+      }
+
+  /* Now we have only a few characters left to search.  We
+     carefully avoid ever producing an out-of-bounds pointer. */
+  ep = text + size;
+  d = d1[U(tp[-1])];
+  while (d <= ep - tp)
+    {
+      d = d1[U((tp += d)[-1])];
+      if (d != 0)
+       continue;
+      if (U(tp[-2]) == gc)
+       {
+         for (i = 3; i <= len && U(tp[-i]) == U(sp[-i]); ++i)
+           ;
+         if (i > len)
+           return tp - len - text;
+       }
+      d = md2;
+    }
+
+  return -1;
+}
+
+/* Hairy multiple string search. */
+static size_t
+cwexec (kwset_t kws, char const *text, size_t len, struct kwsmatch *kwsmatch)
+{
+  struct kwset const *kwset;
+  struct trie * const *next;
+  struct trie const *trie;
+  struct trie const *accept;
+  char const *beg, *lim, *mch, *lmch;
+  register unsigned char c;
+  register unsigned char const *delta;
+  register int d;
+  register char const *end, *qlim;
+  register struct tree const *tree;
+  register char const *trans;
+
+  accept = NULL;
+
+  /* Initialize register copies and look for easy ways out. */
+  kwset = (struct kwset *) kws;
+  if (len < kwset->mind)
+    return -1;
+  next = kwset->next;
+  delta = kwset->delta;
+  trans = kwset->trans;
+  lim = text + len;
+  end = text;
+  if ((d = kwset->mind) != 0)
+    mch = NULL;
+  else
+    {
+      mch = text, accept = kwset->trie;
+      goto match;
+    }
+
+  if (len >= 4 * kwset->mind)
+    qlim = lim - 4 * kwset->mind;
+  else
+    qlim = NULL;
+
+  while (lim - end >= d)
+    {
+      if (qlim && end <= qlim)
+       {
+         end += d - 1;
+         while ((d = delta[c = *end]) && end < qlim)
+           {
+             end += d;
+             end += delta[U(*end)];
+             end += delta[U(*end)];
+           }
+         ++end;
+       }
+      else
+       d = delta[c = (end += d)[-1]];
+      if (d)
+       continue;
+      beg = end - 1;
+      trie = next[c];
+      if (trie->accepting)
+       {
+         mch = beg;
+         accept = trie;
+       }
+      d = trie->shift;
+      while (beg > text)
+       {
+         c = trans ? trans[U(*--beg)] : *--beg;
+         tree = trie->links;
+         while (tree && c != tree->label)
+           if (c < tree->label)
+             tree = tree->llink;
+           else
+             tree = tree->rlink;
+         if (tree)
+           {
+             trie = tree->trie;
+             if (trie->accepting)
+               {
+                 mch = beg;
+                 accept = trie;
+               }
+           }
+         else
+           break;
+         d = trie->shift;
+       }
+      if (mch)
+       goto match;
+    }
+  return -1;
+
+ match:
+  /* Given a known match, find the longest possible match anchored
+     at or before its starting point.  This is nearly a verbatim
+     copy of the preceding main search loops. */
+  if (lim - mch > kwset->maxd)
+    lim = mch + kwset->maxd;
+  lmch = 0;
+  d = 1;
+  while (lim - end >= d)
+    {
+      if ((d = delta[c = (end += d)[-1]]) != 0)
+       continue;
+      beg = end - 1;
+      if (!(trie = next[c]))
+       {
+         d = 1;
+         continue;
+       }
+      if (trie->accepting && beg <= mch)
+       {
+         lmch = beg;
+         accept = trie;
+       }
+      d = trie->shift;
+      while (beg > text)
+       {
+         c = trans ? trans[U(*--beg)] : *--beg;
+         tree = trie->links;
+         while (tree && c != tree->label)
+           if (c < tree->label)
+             tree = tree->llink;
+           else
+             tree = tree->rlink;
+         if (tree)
+           {
+             trie = tree->trie;
+             if (trie->accepting && beg <= mch)
+               {
+                 lmch = beg;
+                 accept = trie;
+               }
+           }
+         else
+           break;
+         d = trie->shift;
+       }
+      if (lmch)
+       {
+         mch = lmch;
+         goto match;
+       }
+      if (!d)
+       d = 1;
+    }
+
+  if (kwsmatch)
+    {
+      kwsmatch->index = accept->accepting / 2;
+      kwsmatch->offset[0] = mch - text;
+      kwsmatch->size[0] = accept->depth;
+    }
+  return mch - text;
+}
+
+/* Search through the given text for a match of any member of the
+   given keyword set.  Return a pointer to the first character of
+   the matching substring, or NULL if no match is found.  If FOUNDLEN
+   is non-NULL store in the referenced location the length of the
+   matching substring.  Similarly, if FOUNDIDX is non-NULL, store
+   in the referenced location the index number of the particular
+   keyword matched. */
+size_t
+kwsexec (kwset_t kws, char const *text, size_t size,
+        struct kwsmatch *kwsmatch)
+{
+  struct kwset const *kwset = (struct kwset *) kws;
+  if (kwset->words == 1 && kwset->trans == NULL)
+    {
+      size_t ret = bmexec (kws, text, size);
+      if (kwsmatch != NULL && ret != (size_t) -1)
+       {
+         kwsmatch->index = 0;
+         kwsmatch->offset[0] = ret;
+         kwsmatch->size[0] = kwset->mind;
+       }
+      return ret;
+    }
+  else
+    return cwexec(kws, text, size, kwsmatch);
+}
+
+/* Free the components of the given keyword set. */
+void
+kwsfree (kwset_t kws)
+{
+  struct kwset *kwset;
+
+  kwset = (struct kwset *) kws;
+  obstack_free(&kwset->obstack, NULL);
+  free(kws);
+}
diff --git a/kwset.h b/kwset.h
new file mode 100644 (file)
index 0000000..a21b2ea
--- /dev/null
+++ b/kwset.h
@@ -0,0 +1,63 @@
+/* This file has been copied from commit e7ac713d^ in the GNU grep git
+ * repository. A few small changes have been made to adapt the code to
+ * Git.
+ */
+
+/* kwset.h - header declaring the keyword set library.
+   Copyright (C) 1989, 1998, 2005 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA
+   02110-1301, USA.  */
+
+/* Written August 1989 by Mike Haertel.
+   The author may be reached (Email) at the address mike@ai.mit.edu,
+   or (US mail) as Mike Haertel c/o Free Software Foundation. */
+
+struct kwsmatch
+{
+  int index;                   /* Index number of matching keyword. */
+  size_t offset[1];            /* Offset of each submatch. */
+  size_t size[1];              /* Length of each submatch. */
+};
+
+struct kwset_t;
+typedef struct kwset_t* kwset_t;
+
+/* Return an opaque pointer to a newly allocated keyword set, or NULL
+   if enough memory cannot be obtained.  The argument if non-NULL
+   specifies a table of character translations to be applied to all
+   pattern and search text. */
+extern kwset_t kwsalloc(char const *);
+
+/* Incrementally extend the keyword set to include the given string.
+   Return NULL for success, or an error message.  Remember an index
+   number for each keyword included in the set. */
+extern const char *kwsincr(kwset_t, char const *, size_t);
+
+/* When the keyword set has been completely built, prepare it for
+   use.  Return NULL for success, or an error message. */
+extern const char *kwsprep(kwset_t);
+
+/* Search through the given buffer for a member of the keyword set.
+   Return a pointer to the leftmost longest match found, or NULL if
+   no match is found.  If foundlen is non-NULL, store the length of
+   the matching substring in the integer it points to.  Similarly,
+   if foundindex is non-NULL, store the index of the particular
+   keyword found therein. */
+extern size_t kwsexec(kwset_t, char const *, size_t, struct kwsmatch *);
+
+/* Deallocate the given keyword set and all its associated storage. */
+extern void kwsfree(kwset_t);
+
index 6ce512efc4cce8042481e8a6947d033c272e78e6..da59738c9b787ae082ad062a91345a60628e704a 100644 (file)
@@ -330,7 +330,7 @@ static int git_path_check_merge(const char *path, struct git_attr_check check[2]
                check[0].attr = git_attr("merge");
                check[1].attr = git_attr("conflict-marker-size");
        }
-       return git_checkattr(path, 2, check);
+       return git_check_attr(path, 2, check);
 }
 
 static void normalize_file(mmfile_t *mm, const char *path)
@@ -387,7 +387,7 @@ int ll_merge_marker_size(const char *path)
 
        if (!check.attr)
                check.attr = git_attr("conflict-marker-size");
-       if (!git_checkattr(path, 1, &check) && check.value) {
+       if (!git_check_attr(path, 1, &check) && check.value) {
                marker_size = atoi(check.value);
                if (marker_size <= 0)
                        marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
index e9457019d5ac7aff1e185e195f37b851732f6210..24c295ea1dc91180b9685299f48812593162bdfe 100644 (file)
@@ -18,6 +18,7 @@ enum decoration_type {
        DECORATION_REF_TAG,
        DECORATION_REF_STASH,
        DECORATION_REF_HEAD,
+       DECORATION_GRAFTED,
 };
 
 static char decoration_colors[][COLOR_MAXLEN] = {
@@ -27,11 +28,12 @@ static char decoration_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_BOLD_YELLOW,  /* REF_TAG */
        GIT_COLOR_BOLD_MAGENTA, /* REF_STASH */
        GIT_COLOR_BOLD_CYAN,    /* REF_HEAD */
+       GIT_COLOR_BOLD_BLUE,    /* GRAFTED */
 };
 
 static const char *decorate_get_color(int decorate_use_color, enum decoration_type ix)
 {
-       if (decorate_use_color)
+       if (want_color(decorate_use_color))
                return decoration_colors[ix];
        return "";
 }
@@ -77,7 +79,7 @@ int parse_decorate_color_config(const char *var, const int ofs, const char *valu
  * for showing the commit sha1, use the same check for --decorate
  */
 #define decorate_get_color_opt(o, ix) \
-       decorate_get_color(DIFF_OPT_TST((o), COLOR_DIFF), ix)
+       decorate_get_color((o)->use_color, ix)
 
 static void add_name_decoration(enum decoration_type type, const char *name, struct object *obj)
 {
@@ -90,16 +92,32 @@ static void add_name_decoration(enum decoration_type type, const char *name, str
 
 static int add_ref_decoration(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
 {
-       struct object *obj = parse_object(sha1);
+       struct object *obj;
        enum decoration_type type = DECORATION_NONE;
+
+       if (!prefixcmp(refname, "refs/replace/")) {
+               unsigned char original_sha1[20];
+               if (!read_replace_refs)
+                       return 0;
+               if (get_sha1_hex(refname + 13, original_sha1)) {
+                       warning("invalid replace ref %s", refname);
+                       return 0;
+               }
+               obj = parse_object(original_sha1);
+               if (obj)
+                       add_name_decoration(DECORATION_GRAFTED, "replaced", obj);
+               return 0;
+       }
+
+       obj = parse_object(sha1);
        if (!obj)
                return 0;
 
-       if (!prefixcmp(refname, "refs/heads"))
+       if (!prefixcmp(refname, "refs/heads/"))
                type = DECORATION_REF_LOCAL;
-       else if (!prefixcmp(refname, "refs/remotes"))
+       else if (!prefixcmp(refname, "refs/remotes/"))
                type = DECORATION_REF_REMOTE;
-       else if (!prefixcmp(refname, "refs/tags"))
+       else if (!prefixcmp(refname, "refs/tags/"))
                type = DECORATION_REF_TAG;
        else if (!prefixcmp(refname, "refs/stash"))
                type = DECORATION_REF_STASH;
@@ -118,6 +136,15 @@ static int add_ref_decoration(const char *refname, const unsigned char *sha1, in
        return 0;
 }
 
+static int add_graft_decoration(const struct commit_graft *graft, void *cb_data)
+{
+       struct commit *commit = lookup_commit(graft->sha1);
+       if (!commit)
+               return 0;
+       add_name_decoration(DECORATION_GRAFTED, "grafted", &commit->object);
+       return 0;
+}
+
 void load_ref_decorations(int flags)
 {
        static int loaded;
@@ -125,6 +152,7 @@ void load_ref_decorations(int flags)
                loaded = 1;
                for_each_ref(add_ref_decoration, &flags);
                head_ref(add_ref_decoration, &flags);
+               for_each_commit_graft(add_graft_decoration, NULL);
        }
 }
 
index db9ba19ddf94fec3a5cfff450b35f5bb7b46c35e..c34a4f148b65cf81f28e2aed6c35e141e175b324 100644 (file)
@@ -66,10 +66,12 @@ static int sha_eq(const unsigned char *a, const unsigned char *b)
 enum rename_type {
        RENAME_NORMAL = 0,
        RENAME_DELETE,
-       RENAME_ONE_FILE_TO_TWO
+       RENAME_ONE_FILE_TO_ONE,
+       RENAME_ONE_FILE_TO_TWO,
+       RENAME_TWO_FILES_TO_ONE
 };
 
-struct rename_df_conflict_info {
+struct rename_conflict_info {
        enum rename_type rename_type;
        struct diff_filepair *pair1;
        struct diff_filepair *pair2;
@@ -77,6 +79,8 @@ struct rename_df_conflict_info {
        const char *branch2;
        struct stage_data *dst_entry1;
        struct stage_data *dst_entry2;
+       struct diff_filespec ren1_other;
+       struct diff_filespec ren2_other;
 };
 
 /*
@@ -88,34 +92,54 @@ struct stage_data {
                unsigned mode;
                unsigned char sha[20];
        } stages[4];
-       struct rename_df_conflict_info *rename_df_conflict_info;
+       struct rename_conflict_info *rename_conflict_info;
        unsigned processed:1;
 };
 
-static inline void setup_rename_df_conflict_info(enum rename_type rename_type,
-                                                struct diff_filepair *pair1,
-                                                struct diff_filepair *pair2,
-                                                const char *branch1,
-                                                const char *branch2,
-                                                struct stage_data *dst_entry1,
-                                                struct stage_data *dst_entry2)
+static inline void setup_rename_conflict_info(enum rename_type rename_type,
+                                             struct diff_filepair *pair1,
+                                             struct diff_filepair *pair2,
+                                             const char *branch1,
+                                             const char *branch2,
+                                             struct stage_data *dst_entry1,
+                                             struct stage_data *dst_entry2,
+                                             struct merge_options *o,
+                                             struct stage_data *src_entry1,
+                                             struct stage_data *src_entry2)
 {
-       struct rename_df_conflict_info *ci = xcalloc(1, sizeof(struct rename_df_conflict_info));
+       struct rename_conflict_info *ci = xcalloc(1, sizeof(struct rename_conflict_info));
        ci->rename_type = rename_type;
        ci->pair1 = pair1;
        ci->branch1 = branch1;
        ci->branch2 = branch2;
 
        ci->dst_entry1 = dst_entry1;
-       dst_entry1->rename_df_conflict_info = ci;
+       dst_entry1->rename_conflict_info = ci;
        dst_entry1->processed = 0;
 
        assert(!pair2 == !dst_entry2);
        if (dst_entry2) {
                ci->dst_entry2 = dst_entry2;
                ci->pair2 = pair2;
-               dst_entry2->rename_df_conflict_info = ci;
-               dst_entry2->processed = 0;
+               dst_entry2->rename_conflict_info = ci;
+       }
+
+       if (rename_type == RENAME_TWO_FILES_TO_ONE) {
+               /*
+                * For each rename, there could have been
+                * modifications on the side of history where that
+                * file was not renamed.
+                */
+               int ostage1 = o->branch1 == branch1 ? 3 : 2;
+               int ostage2 = ostage1 ^ 1;
+
+               ci->ren1_other.path = pair1->one->path;
+               hashcpy(ci->ren1_other.sha1, src_entry1->stages[ostage1].sha);
+               ci->ren1_other.mode = src_entry1->stages[ostage1].mode;
+
+               ci->ren2_other.path = pair2->one->path;
+               hashcpy(ci->ren2_other.sha1, src_entry2->stages[ostage2].sha);
+               ci->ren2_other.mode = src_entry2->stages[ostage2].mode;
        }
 }
 
@@ -230,7 +254,7 @@ struct tree *write_tree_from_memory(struct merge_options *o)
                for (i = 0; i < active_nr; i++) {
                        struct cache_entry *ce = active_cache[i];
                        if (ce_stage(ce))
-                               fprintf(stderr, "BUG: %d %.*s", ce_stage(ce),
+                               fprintf(stderr, "BUG: %d %.*s\n", ce_stage(ce),
                                        (int)ce_namelen(ce), ce->name);
                }
                die("Bug in merge-recursive.c");
@@ -333,44 +357,90 @@ static struct string_list *get_unmerged(void)
        return unmerged;
 }
 
-static void make_room_for_directories_of_df_conflicts(struct merge_options *o,
-                                                     struct string_list *entries)
+static int string_list_df_name_compare(const void *a, const void *b)
 {
-       /* If there are D/F conflicts, and the paths currently exist
-        * in the working copy as a file, we want to remove them to
-        * make room for the corresponding directory.  Such paths will
-        * later be processed in process_df_entry() at the end.  If
-        * the corresponding directory ends up being removed by the
-        * merge, then the file will be reinstated at that time;
-        * otherwise, if the file is not supposed to be removed by the
-        * merge, the contents of the file will be placed in another
-        * unique filename.
+       const struct string_list_item *one = a;
+       const struct string_list_item *two = b;
+       int onelen = strlen(one->string);
+       int twolen = strlen(two->string);
+       /*
+        * Here we only care that entries for D/F conflicts are
+        * adjacent, in particular with the file of the D/F conflict
+        * appearing before files below the corresponding directory.
+        * The order of the rest of the list is irrelevant for us.
         *
-        * NOTE: This function relies on the fact that entries for a
-        * D/F conflict will appear adjacent in the index, with the
-        * entries for the file appearing before entries for paths
-        * below the corresponding directory.
+        * To achieve this, we sort with df_name_compare and provide
+        * the mode S_IFDIR so that D/F conflicts will sort correctly.
+        * We use the mode S_IFDIR for everything else for simplicity,
+        * since in other cases any changes in their order due to
+        * sorting cause no problems for us.
+        */
+       int cmp = df_name_compare(one->string, onelen, S_IFDIR,
+                                 two->string, twolen, S_IFDIR);
+       /*
+        * Now that 'foo' and 'foo/bar' compare equal, we have to make sure
+        * that 'foo' comes before 'foo/bar'.
         */
+       if (cmp)
+               return cmp;
+       return onelen - twolen;
+}
+
+static void record_df_conflict_files(struct merge_options *o,
+                                    struct string_list *entries)
+{
+       /* If there is a D/F conflict and the file for such a conflict
+        * currently exist in the working tree, we want to allow it to be
+        * removed to make room for the corresponding directory if needed.
+        * The files underneath the directories of such D/F conflicts will
+        * be processed before the corresponding file involved in the D/F
+        * conflict.  If the D/F directory ends up being removed by the
+        * merge, then we won't have to touch the D/F file.  If the D/F
+        * directory needs to be written to the working copy, then the D/F
+        * file will simply be removed (in make_room_for_path()) to make
+        * room for the necessary paths.  Note that if both the directory
+        * and the file need to be present, then the D/F file will be
+        * reinstated with a new unique name at the time it is processed.
+        */
+       struct string_list df_sorted_entries;
        const char *last_file = NULL;
        int last_len = 0;
        int i;
 
+       /*
+        * If we're merging merge-bases, we don't want to bother with
+        * any working directory changes.
+        */
+       if (o->call_depth)
+               return;
+
+       /* Ensure D/F conflicts are adjacent in the entries list. */
+       memset(&df_sorted_entries, 0, sizeof(struct string_list));
        for (i = 0; i < entries->nr; i++) {
-               const char *path = entries->items[i].string;
+               struct string_list_item *next = &entries->items[i];
+               string_list_append(&df_sorted_entries, next->string)->util =
+                                  next->util;
+       }
+       qsort(df_sorted_entries.items, entries->nr, sizeof(*entries->items),
+             string_list_df_name_compare);
+
+       string_list_clear(&o->df_conflict_file_set, 1);
+       for (i = 0; i < df_sorted_entries.nr; i++) {
+               const char *path = df_sorted_entries.items[i].string;
                int len = strlen(path);
-               struct stage_data *e = entries->items[i].util;
+               struct stage_data *e = df_sorted_entries.items[i].util;
 
                /*
                 * Check if last_file & path correspond to a D/F conflict;
                 * i.e. whether path is last_file+'/'+<something>.
-                * If so, remove last_file to make room for path and friends.
+                * If so, record that it's okay to remove last_file to make
+                * room for path and friends if needed.
                 */
                if (last_file &&
                    len > last_len &&
                    memcmp(path, last_file, last_len) == 0 &&
                    path[last_len] == '/') {
-                       output(o, 3, "Removing %s to make room for subdirectory; may re-add later.", last_file);
-                       unlink(last_file);
+                       string_list_insert(&o->df_conflict_file_set, last_file);
                }
 
                /*
@@ -386,6 +456,7 @@ static void make_room_for_directories_of_df_conflicts(struct merge_options *o,
                        last_file = NULL;
                }
        }
+       string_list_clear(&df_sorted_entries, 0);
 }
 
 struct rename {
@@ -461,10 +532,21 @@ static struct string_list *get_renames(struct merge_options *o,
        return renames;
 }
 
-static int update_stages_options(const char *path, struct diff_filespec *o,
-                        struct diff_filespec *a, struct diff_filespec *b,
-                        int clear, int options)
+static int update_stages(const char *path, const struct diff_filespec *o,
+                        const struct diff_filespec *a,
+                        const struct diff_filespec *b)
 {
+
+       /*
+        * NOTE: It is usually a bad idea to call update_stages on a path
+        * before calling update_file on that same path, since it can
+        * sometimes lead to spurious "refusing to lose untracked file..."
+        * messages from update_file (via make_room_for path via
+        * would_lose_untracked).  Instead, reverse the order of the calls
+        * (executing update_file first and then update_stages).
+        */
+       int clear = 1;
+       int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;
        if (clear)
                if (remove_file_from_cache(path))
                        return -1;
@@ -480,23 +562,11 @@ static int update_stages_options(const char *path, struct diff_filespec *o,
        return 0;
 }
 
-static int update_stages(const char *path, struct diff_filespec *o,
-                        struct diff_filespec *a, struct diff_filespec *b,
-                        int clear)
+static void update_entry(struct stage_data *entry,
+                        struct diff_filespec *o,
+                        struct diff_filespec *a,
+                        struct diff_filespec *b)
 {
-       int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;
-       return update_stages_options(path, o, a, b, clear, options);
-}
-
-static int update_stages_and_entry(const char *path,
-                                  struct stage_data *entry,
-                                  struct diff_filespec *o,
-                                  struct diff_filespec *a,
-                                  struct diff_filespec *b,
-                                  int clear)
-{
-       int options;
-
        entry->processed = 0;
        entry->stages[1].mode = o->mode;
        entry->stages[2].mode = a->mode;
@@ -504,8 +574,6 @@ static int update_stages_and_entry(const char *path,
        hashcpy(entry->stages[1].sha, o->sha1);
        hashcpy(entry->stages[2].sha, a->sha1);
        hashcpy(entry->stages[3].sha, b->sha1);
-       options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;
-       return update_stages_options(path, o, a, b, clear, options);
 }
 
 static int remove_file(struct merge_options *o, int clean,
@@ -563,7 +631,31 @@ static void flush_buffer(int fd, const char *buf, unsigned long size)
        }
 }
 
-static int would_lose_untracked(const char *path)
+static int dir_in_way(const char *path, int check_working_copy)
+{
+       int pos, pathlen = strlen(path);
+       char *dirpath = xmalloc(pathlen + 2);
+       struct stat st;
+
+       strcpy(dirpath, path);
+       dirpath[pathlen] = '/';
+       dirpath[pathlen+1] = '\0';
+
+       pos = cache_name_pos(dirpath, pathlen+1);
+
+       if (pos < 0)
+               pos = -1 - pos;
+       if (pos < active_nr &&
+           !strncmp(dirpath, active_cache[pos]->name, pathlen+1)) {
+               free(dirpath);
+               return 1;
+       }
+
+       free(dirpath);
+       return check_working_copy && !lstat(path, &st) && S_ISDIR(st.st_mode);
+}
+
+static int was_tracked(const char *path)
 {
        int pos = cache_name_pos(path, strlen(path));
 
@@ -580,18 +672,42 @@ static int would_lose_untracked(const char *path)
                switch (ce_stage(active_cache[pos])) {
                case 0:
                case 2:
-                       return 0;
+                       return 1;
                }
                pos++;
        }
-       return file_exists(path);
+       return 0;
 }
 
-static int make_room_for_path(const char *path)
+static int would_lose_untracked(const char *path)
 {
-       int status;
+       return !was_tracked(path) && file_exists(path);
+}
+
+static int make_room_for_path(struct merge_options *o, const char *path)
+{
+       int status, i;
        const char *msg = "failed to create path '%s'%s";
 
+       /* Unlink any D/F conflict files that are in the way */
+       for (i = 0; i < o->df_conflict_file_set.nr; i++) {
+               const char *df_path = o->df_conflict_file_set.items[i].string;
+               size_t pathlen = strlen(path);
+               size_t df_pathlen = strlen(df_path);
+               if (df_pathlen < pathlen &&
+                   path[df_pathlen] == '/' &&
+                   strncmp(path, df_path, df_pathlen) == 0) {
+                       output(o, 3,
+                              "Removing %s to make room for subdirectory\n",
+                              df_path);
+                       unlink(df_path);
+                       unsorted_string_list_delete_item(&o->df_conflict_file_set,
+                                                        i, 0);
+                       break;
+               }
+       }
+
+       /* Make sure leading directories are created */
        status = safe_create_leading_directories_const(path);
        if (status) {
                if (status == -3) {
@@ -659,7 +775,7 @@ static void update_file_flags(struct merge_options *o,
                        }
                }
 
-               if (make_room_for_path(path) < 0) {
+               if (make_room_for_path(o, path) < 0) {
                        update_wd = 0;
                        free(buf);
                        goto update_index;
@@ -712,9 +828,9 @@ struct merge_file_info {
 
 static int merge_3way(struct merge_options *o,
                      mmbuffer_t *result_buf,
-                     struct diff_filespec *one,
-                     struct diff_filespec *a,
-                     struct diff_filespec *b,
+                     const struct diff_filespec *one,
+                     const struct diff_filespec *a,
+                     const struct diff_filespec *b,
                      const char *branch1,
                      const char *branch2)
 {
@@ -771,12 +887,12 @@ static int merge_3way(struct merge_options *o,
        return merge_status;
 }
 
-static struct merge_file_info merge_file(struct merge_options *o,
-                                        struct diff_filespec *one,
-                                        struct diff_filespec *a,
-                                        struct diff_filespec *b,
-                                        const char *branch1,
-                                        const char *branch2)
+static struct merge_file_info merge_file_1(struct merge_options *o,
+                                          const struct diff_filespec *one,
+                                          const struct diff_filespec *a,
+                                          const struct diff_filespec *b,
+                                          const char *branch1,
+                                          const char *branch2)
 {
        struct merge_file_info result;
        result.merge = 0;
@@ -845,94 +961,303 @@ static struct merge_file_info merge_file(struct merge_options *o,
        return result;
 }
 
+static struct merge_file_info
+merge_file_special_markers(struct merge_options *o,
+                          const struct diff_filespec *one,
+                          const struct diff_filespec *a,
+                          const struct diff_filespec *b,
+                          const char *branch1,
+                          const char *filename1,
+                          const char *branch2,
+                          const char *filename2)
+{
+       char *side1 = NULL;
+       char *side2 = NULL;
+       struct merge_file_info mfi;
+
+       if (filename1) {
+               side1 = xmalloc(strlen(branch1) + strlen(filename1) + 2);
+               sprintf(side1, "%s:%s", branch1, filename1);
+       }
+       if (filename2) {
+               side2 = xmalloc(strlen(branch2) + strlen(filename2) + 2);
+               sprintf(side2, "%s:%s", branch2, filename2);
+       }
+
+       mfi = merge_file_1(o, one, a, b,
+                          side1 ? side1 : branch1, side2 ? side2 : branch2);
+       free(side1);
+       free(side2);
+       return mfi;
+}
+
+static struct merge_file_info merge_file(struct merge_options *o,
+                                        const char *path,
+                                        const unsigned char *o_sha, int o_mode,
+                                        const unsigned char *a_sha, int a_mode,
+                                        const unsigned char *b_sha, int b_mode,
+                                        const char *branch1,
+                                        const char *branch2)
+{
+       struct diff_filespec one, a, b;
+
+       one.path = a.path = b.path = (char *)path;
+       hashcpy(one.sha1, o_sha);
+       one.mode = o_mode;
+       hashcpy(a.sha1, a_sha);
+       a.mode = a_mode;
+       hashcpy(b.sha1, b_sha);
+       b.mode = b_mode;
+       return merge_file_1(o, &one, &a, &b, branch1, branch2);
+}
+
+static void handle_change_delete(struct merge_options *o,
+                                const char *path,
+                                const unsigned char *o_sha, int o_mode,
+                                const unsigned char *a_sha, int a_mode,
+                                const unsigned char *b_sha, int b_mode,
+                                const char *change, const char *change_past)
+{
+       char *renamed = NULL;
+       if (dir_in_way(path, !o->call_depth)) {
+               renamed = unique_path(o, path, a_sha ? o->branch1 : o->branch2);
+       }
+
+       if (o->call_depth) {
+               /*
+                * We cannot arbitrarily accept either a_sha or b_sha as
+                * correct; since there is no true "middle point" between
+                * them, simply reuse the base version for virtual merge base.
+                */
+               remove_file_from_cache(path);
+               update_file(o, 0, o_sha, o_mode, renamed ? renamed : path);
+       } else if (!a_sha) {
+               output(o, 1, "CONFLICT (%s/delete): %s deleted in %s "
+                      "and %s in %s. Version %s of %s left in tree%s%s.",
+                      change, path, o->branch1,
+                      change_past, o->branch2, o->branch2, path,
+                      NULL == renamed ? "" : " at ",
+                      NULL == renamed ? "" : renamed);
+               update_file(o, 0, b_sha, b_mode, renamed ? renamed : path);
+       } else {
+               output(o, 1, "CONFLICT (%s/delete): %s deleted in %s "
+                      "and %s in %s. Version %s of %s left in tree%s%s.",
+                      change, path, o->branch2,
+                      change_past, o->branch1, o->branch1, path,
+                      NULL == renamed ? "" : " at ",
+                      NULL == renamed ? "" : renamed);
+               if (renamed)
+                       update_file(o, 0, a_sha, a_mode, renamed);
+               /*
+                * No need to call update_file() on path when !renamed, since
+                * that would needlessly touch path.  We could call
+                * update_file_flags() with update_cache=0 and update_wd=0,
+                * but that's a no-op.
+                */
+       }
+       free(renamed);
+}
+
 static void conflict_rename_delete(struct merge_options *o,
                                   struct diff_filepair *pair,
                                   const char *rename_branch,
                                   const char *other_branch)
 {
-       char *dest_name = pair->two->path;
-       int df_conflict = 0;
-       struct stat st;
+       const struct diff_filespec *orig = pair->one;
+       const struct diff_filespec *dest = pair->two;
+       const unsigned char *a_sha = NULL;
+       const unsigned char *b_sha = NULL;
+       int a_mode = 0;
+       int b_mode = 0;
+
+       if (rename_branch == o->branch1) {
+               a_sha = dest->sha1;
+               a_mode = dest->mode;
+       } else {
+               b_sha = dest->sha1;
+               b_mode = dest->mode;
+       }
 
-       output(o, 1, "CONFLICT (rename/delete): Rename %s->%s in %s "
-              "and deleted in %s",
-              pair->one->path, pair->two->path, rename_branch,
-              other_branch);
-       if (!o->call_depth)
-               update_stages(dest_name, NULL,
-                             rename_branch == o->branch1 ? pair->two : NULL,
-                             rename_branch == o->branch1 ? NULL : pair->two,
-                             1);
-       if (lstat(dest_name, &st) == 0 && S_ISDIR(st.st_mode)) {
-               dest_name = unique_path(o, dest_name, rename_branch);
-               df_conflict = 1;
+       handle_change_delete(o,
+                            o->call_depth ? orig->path : dest->path,
+                            orig->sha1, orig->mode,
+                            a_sha, a_mode,
+                            b_sha, b_mode,
+                            "rename", "renamed");
+
+       if (o->call_depth) {
+               remove_file_from_cache(dest->path);
+       } else {
+               update_stages(dest->path, NULL,
+                             rename_branch == o->branch1 ? dest : NULL,
+                             rename_branch == o->branch1 ? NULL : dest);
        }
-       update_file(o, 0, pair->two->sha1, pair->two->mode, dest_name);
-       if (df_conflict)
-               free(dest_name);
+
 }
 
-static void conflict_rename_rename_1to2(struct merge_options *o,
-                                       struct diff_filepair *pair1,
-                                       const char *branch1,
-                                       struct diff_filepair *pair2,
-                                       const char *branch2)
+static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,
+                                                struct stage_data *entry,
+                                                int stage)
 {
-       /* One file was renamed in both branches, but to different names. */
-       char *del[2];
-       int delp = 0;
-       const char *ren1_dst = pair1->two->path;
-       const char *ren2_dst = pair2->two->path;
-       const char *dst_name1 = ren1_dst;
-       const char *dst_name2 = ren2_dst;
-       struct stat st;
-       if (lstat(ren1_dst, &st) == 0 && S_ISDIR(st.st_mode)) {
-               dst_name1 = del[delp++] = unique_path(o, ren1_dst, branch1);
-               output(o, 1, "%s is a directory in %s adding as %s instead",
-                      ren1_dst, branch2, dst_name1);
+       unsigned char *sha = entry->stages[stage].sha;
+       unsigned mode = entry->stages[stage].mode;
+       if (mode == 0 || is_null_sha1(sha))
+               return NULL;
+       hashcpy(target->sha1, sha);
+       target->mode = mode;
+       return target;
+}
+
+static void handle_file(struct merge_options *o,
+                       struct diff_filespec *rename,
+                       int stage,
+                       struct rename_conflict_info *ci)
+{
+       char *dst_name = rename->path;
+       struct stage_data *dst_entry;
+       const char *cur_branch, *other_branch;
+       struct diff_filespec other;
+       struct diff_filespec *add;
+
+       if (stage == 2) {
+               dst_entry = ci->dst_entry1;
+               cur_branch = ci->branch1;
+               other_branch = ci->branch2;
+       } else {
+               dst_entry = ci->dst_entry2;
+               cur_branch = ci->branch2;
+               other_branch = ci->branch1;
        }
-       if (lstat(ren2_dst, &st) == 0 && S_ISDIR(st.st_mode)) {
-               dst_name2 = del[delp++] = unique_path(o, ren2_dst, branch2);
-               output(o, 1, "%s is a directory in %s adding as %s instead",
-                      ren2_dst, branch1, dst_name2);
+
+       add = filespec_from_entry(&other, dst_entry, stage ^ 1);
+       if (add) {
+               char *add_name = unique_path(o, rename->path, other_branch);
+               update_file(o, 0, add->sha1, add->mode, add_name);
+
+               remove_file(o, 0, rename->path, 0);
+               dst_name = unique_path(o, rename->path, cur_branch);
+       } else {
+               if (dir_in_way(rename->path, !o->call_depth)) {
+                       dst_name = unique_path(o, rename->path, cur_branch);
+                       output(o, 1, "%s is a directory in %s adding as %s instead",
+                              rename->path, other_branch, dst_name);
+               }
        }
+       update_file(o, 0, rename->sha1, rename->mode, dst_name);
+       if (stage == 2)
+               update_stages(rename->path, NULL, rename, add);
+       else
+               update_stages(rename->path, NULL, add, rename);
+
+       if (dst_name != rename->path)
+               free(dst_name);
+}
+
+static void conflict_rename_rename_1to2(struct merge_options *o,
+                                       struct rename_conflict_info *ci)
+{
+       /* One file was renamed in both branches, but to different names. */
+       struct diff_filespec *one = ci->pair1->one;
+       struct diff_filespec *a = ci->pair1->two;
+       struct diff_filespec *b = ci->pair2->two;
+
+       output(o, 1, "CONFLICT (rename/rename): "
+              "Rename \"%s\"->\"%s\" in branch \"%s\" "
+              "rename \"%s\"->\"%s\" in \"%s\"%s",
+              one->path, a->path, ci->branch1,
+              one->path, b->path, ci->branch2,
+              o->call_depth ? " (left unresolved)" : "");
        if (o->call_depth) {
-               remove_file_from_cache(dst_name1);
-               remove_file_from_cache(dst_name2);
+               struct merge_file_info mfi;
+               struct diff_filespec other;
+               struct diff_filespec *add;
+               mfi = merge_file(o, one->path,
+                                one->sha1, one->mode,
+                                a->sha1, a->mode,
+                                b->sha1, b->mode,
+                                ci->branch1, ci->branch2);
                /*
-                * Uncomment to leave the conflicting names in the resulting tree
-                *
-                * update_file(o, 0, pair1->two->sha1, pair1->two->mode, dst_name1);
-                * update_file(o, 0, pair2->two->sha1, pair2->two->mode, dst_name2);
+                * FIXME: For rename/add-source conflicts (if we could detect
+                * such), this is wrong.  We should instead find a unique
+                * pathname and then either rename the add-source file to that
+                * unique path, or use that unique path instead of src here.
                 */
-       } else {
-               update_stages(ren1_dst, NULL, pair1->two, NULL, 1);
-               update_stages(ren2_dst, NULL, NULL, pair2->two, 1);
+               update_file(o, 0, mfi.sha, mfi.mode, one->path);
 
-               update_file(o, 0, pair1->two->sha1, pair1->two->mode, dst_name1);
-               update_file(o, 0, pair2->two->sha1, pair2->two->mode, dst_name2);
+               /*
+                * Above, we put the merged content at the merge-base's
+                * path.  Now we usually need to delete both a->path and
+                * b->path.  However, the rename on each side of the merge
+                * could also be involved in a rename/add conflict.  In
+                * such cases, we should keep the added file around,
+                * resolving the conflict at that path in its favor.
+                */
+               add = filespec_from_entry(&other, ci->dst_entry1, 2 ^ 1);
+               if (add)
+                       update_file(o, 0, add->sha1, add->mode, a->path);
+               else
+                       remove_file_from_cache(a->path);
+               add = filespec_from_entry(&other, ci->dst_entry2, 3 ^ 1);
+               if (add)
+                       update_file(o, 0, add->sha1, add->mode, b->path);
+               else
+                       remove_file_from_cache(b->path);
+       } else {
+               handle_file(o, a, 2, ci);
+               handle_file(o, b, 3, ci);
        }
-       while (delp--)
-               free(del[delp]);
 }
 
 static void conflict_rename_rename_2to1(struct merge_options *o,
-                                       struct rename *ren1,
-                                       const char *branch1,
-                                       struct rename *ren2,
-                                       const char *branch2)
+                                       struct rename_conflict_info *ci)
 {
-       /* Two files were renamed to the same thing. */
-       char *new_path1 = unique_path(o, ren1->pair->two->path, branch1);
-       char *new_path2 = unique_path(o, ren2->pair->two->path, branch2);
-       output(o, 1, "Renaming %s to %s and %s to %s instead",
-              ren1->pair->one->path, new_path1,
-              ren2->pair->one->path, new_path2);
-       remove_file(o, 0, ren1->pair->two->path, 0);
-       update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1);
-       update_file(o, 0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2);
-       free(new_path2);
-       free(new_path1);
+       /* Two files, a & b, were renamed to the same thing, c. */
+       struct diff_filespec *a = ci->pair1->one;
+       struct diff_filespec *b = ci->pair2->one;
+       struct diff_filespec *c1 = ci->pair1->two;
+       struct diff_filespec *c2 = ci->pair2->two;
+       char *path = c1->path; /* == c2->path */
+       struct merge_file_info mfi_c1;
+       struct merge_file_info mfi_c2;
+
+       output(o, 1, "CONFLICT (rename/rename): "
+              "Rename %s->%s in %s. "
+              "Rename %s->%s in %s",
+              a->path, c1->path, ci->branch1,
+              b->path, c2->path, ci->branch2);
+
+       remove_file(o, 1, a->path, would_lose_untracked(a->path));
+       remove_file(o, 1, b->path, would_lose_untracked(b->path));
+
+       mfi_c1 = merge_file_special_markers(o, a, c1, &ci->ren1_other,
+                                           o->branch1, c1->path,
+                                           o->branch2, ci->ren1_other.path);
+       mfi_c2 = merge_file_special_markers(o, b, &ci->ren2_other, c2,
+                                           o->branch1, ci->ren2_other.path,
+                                           o->branch2, c2->path);
+
+       if (o->call_depth) {
+               /*
+                * If mfi_c1.clean && mfi_c2.clean, then it might make
+                * sense to do a two-way merge of those results.  But, I
+                * think in all cases, it makes sense to have the virtual
+                * merge base just undo the renames; they can be detected
+                * again later for the non-recursive merge.
+                */
+               remove_file(o, 0, path, 0);
+               update_file(o, 0, mfi_c1.sha, mfi_c1.mode, a->path);
+               update_file(o, 0, mfi_c2.sha, mfi_c2.mode, b->path);
+       } else {
+               char *new_path1 = unique_path(o, path, ci->branch1);
+               char *new_path2 = unique_path(o, path, ci->branch2);
+               output(o, 1, "Renaming %s to %s and %s to %s instead",
+                      a->path, new_path1, b->path, new_path2);
+               remove_file(o, 0, path, 0);
+               update_file(o, 0, mfi_c1.sha, mfi_c1.mode, new_path1);
+               update_file(o, 0, mfi_c2.sha, mfi_c2.mode, new_path2);
+               free(new_path2);
+               free(new_path1);
+       }
 }
 
 static int process_renames(struct merge_options *o,
@@ -947,12 +1272,12 @@ static int process_renames(struct merge_options *o,
        for (i = 0; i < a_renames->nr; i++) {
                sre = a_renames->items[i].util;
                string_list_insert(&a_by_dst, sre->pair->two->path)->util
-                       = sre->dst_entry;
+                       = (void *)sre;
        }
        for (i = 0; i < b_renames->nr; i++) {
                sre = b_renames->items[i].util;
                string_list_insert(&b_by_dst, sre->pair->two->path)->util
-                       = sre->dst_entry;
+                       = (void *)sre;
        }
 
        for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) {
@@ -960,6 +1285,7 @@ static int process_renames(struct merge_options *o,
                struct rename *ren1 = NULL, *ren2 = NULL;
                const char *branch1, *branch2;
                const char *ren1_src, *ren1_dst;
+               struct string_list_item *lookup;
 
                if (i >= a_renames->nr) {
                        ren2 = b_renames->items[j++].util;
@@ -991,44 +1317,82 @@ static int process_renames(struct merge_options *o,
                        ren1 = tmp;
                }
 
-               ren1->dst_entry->processed = 1;
-               ren1->src_entry->processed = 1;
-
                if (ren1->processed)
                        continue;
                ren1->processed = 1;
+               ren1->dst_entry->processed = 1;
+               /* BUG: We should only mark src_entry as processed if we
+                * are not dealing with a rename + add-source case.
+                */
+               ren1->src_entry->processed = 1;
 
                ren1_src = ren1->pair->one->path;
                ren1_dst = ren1->pair->two->path;
 
                if (ren2) {
+                       /* One file renamed on both sides */
                        const char *ren2_src = ren2->pair->one->path;
                        const char *ren2_dst = ren2->pair->two->path;
-                       /* Renamed in 1 and renamed in 2 */
+                       enum rename_type rename_type;
                        if (strcmp(ren1_src, ren2_src) != 0)
-                               die("ren1.src != ren2.src");
+                               die("ren1_src != ren2_src");
                        ren2->dst_entry->processed = 1;
                        ren2->processed = 1;
                        if (strcmp(ren1_dst, ren2_dst) != 0) {
-                               setup_rename_df_conflict_info(RENAME_ONE_FILE_TO_TWO,
-                                                             ren1->pair,
-                                                             ren2->pair,
-                                                             branch1,
-                                                             branch2,
-                                                             ren1->dst_entry,
-                                                             ren2->dst_entry);
+                               rename_type = RENAME_ONE_FILE_TO_TWO;
+                               clean_merge = 0;
                        } else {
+                               rename_type = RENAME_ONE_FILE_TO_ONE;
+                               /* BUG: We should only remove ren1_src in
+                                * the base stage (think of rename +
+                                * add-source cases).
+                                */
                                remove_file(o, 1, ren1_src, 1);
-                               update_stages_and_entry(ren1_dst,
-                                                       ren1->dst_entry,
-                                                       ren1->pair->one,
-                                                       ren1->pair->two,
-                                                       ren2->pair->two,
-                                                       1 /* clear */);
+                               update_entry(ren1->dst_entry,
+                                            ren1->pair->one,
+                                            ren1->pair->two,
+                                            ren2->pair->two);
                        }
+                       setup_rename_conflict_info(rename_type,
+                                                  ren1->pair,
+                                                  ren2->pair,
+                                                  branch1,
+                                                  branch2,
+                                                  ren1->dst_entry,
+                                                  ren2->dst_entry,
+                                                  o,
+                                                  NULL,
+                                                  NULL);
+               } else if ((lookup = string_list_lookup(renames2Dst, ren1_dst))) {
+                       /* Two different files renamed to the same thing */
+                       char *ren2_dst;
+                       ren2 = lookup->util;
+                       ren2_dst = ren2->pair->two->path;
+                       if (strcmp(ren1_dst, ren2_dst) != 0)
+                               die("ren1_dst != ren2_dst");
+
+                       clean_merge = 0;
+                       ren2->processed = 1;
+                       /*
+                        * BUG: We should only mark src_entry as processed
+                        * if we are not dealing with a rename + add-source
+                        * case.
+                        */
+                       ren2->src_entry->processed = 1;
+
+                       setup_rename_conflict_info(RENAME_TWO_FILES_TO_ONE,
+                                                  ren1->pair,
+                                                  ren2->pair,
+                                                  branch1,
+                                                  branch2,
+                                                  ren1->dst_entry,
+                                                  ren2->dst_entry,
+                                                  o,
+                                                  ren1->src_entry,
+                                                  ren2->src_entry);
+
                } else {
                        /* Renamed in 1, maybe changed in 2 */
-                       struct string_list_item *item;
                        /* we only use sha1 and mode of these */
                        struct diff_filespec src_other, dst_other;
                        int try_merge;
@@ -1042,7 +1406,12 @@ static int process_renames(struct merge_options *o,
                        int renamed_stage = a_renames == renames1 ? 2 : 3;
                        int other_stage =   a_renames == renames1 ? 3 : 2;
 
-                       remove_file(o, 1, ren1_src, o->call_depth || renamed_stage == 2);
+                       /* BUG: We should only remove ren1_src in the base
+                        * stage and in other_stage (think of rename +
+                        * add-source case).
+                        */
+                       remove_file(o, 1, ren1_src,
+                                   renamed_stage == 2 || !was_tracked(ren1_src));
 
                        hashcpy(src_other.sha1, ren1->src_entry->stages[other_stage].sha);
                        src_other.mode = ren1->src_entry->stages[other_stage].mode;
@@ -1051,27 +1420,33 @@ static int process_renames(struct merge_options *o,
                        try_merge = 0;
 
                        if (sha_eq(src_other.sha1, null_sha1)) {
-                               if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
-                                       ren1->dst_entry->processed = 0;
-                                       setup_rename_df_conflict_info(RENAME_DELETE,
-                                                                     ren1->pair,
-                                                                     NULL,
-                                                                     branch1,
-                                                                     branch2,
-                                                                     ren1->dst_entry,
-                                                                     NULL);
-                               } else {
-                                       clean_merge = 0;
-                                       conflict_rename_delete(o, ren1->pair, branch1, branch2);
-                               }
+                               setup_rename_conflict_info(RENAME_DELETE,
+                                                          ren1->pair,
+                                                          NULL,
+                                                          branch1,
+                                                          branch2,
+                                                          ren1->dst_entry,
+                                                          NULL,
+                                                          o,
+                                                          NULL,
+                                                          NULL);
                        } else if ((dst_other.mode == ren1->pair->two->mode) &&
                                   sha_eq(dst_other.sha1, ren1->pair->two->sha1)) {
-                               /* Added file on the other side
-                                  identical to the file being
-                                  renamed: clean merge */
-                               update_file(o, 1, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst);
+                               /*
+                                * Added file on the other side identical to
+                                * the file being renamed: clean merge.
+                                * Also, there is no need to overwrite the
+                                * file already in the working copy, so call
+                                * update_file_flags() instead of
+                                * update_file().
+                                */
+                               update_file_flags(o,
+                                                 ren1->pair->two->sha1,
+                                                 ren1->pair->two->mode,
+                                                 ren1_dst,
+                                                 1, /* update_cache */
+                                                 0  /* update_wd    */);
                        } else if (!sha_eq(dst_other.sha1, null_sha1)) {
-                               const char *new_path;
                                clean_merge = 0;
                                try_merge = 1;
                                output(o, 1, "CONFLICT (rename/add): Rename %s->%s in %s. "
@@ -1080,40 +1455,19 @@ static int process_renames(struct merge_options *o,
                                       ren1_dst, branch2);
                                if (o->call_depth) {
                                        struct merge_file_info mfi;
-                                       struct diff_filespec one, a, b;
-
-                                       one.path = a.path = b.path =
-                                               (char *)ren1_dst;
-                                       hashcpy(one.sha1, null_sha1);
-                                       one.mode = 0;
-                                       hashcpy(a.sha1, ren1->pair->two->sha1);
-                                       a.mode = ren1->pair->two->mode;
-                                       hashcpy(b.sha1, dst_other.sha1);
-                                       b.mode = dst_other.mode;
-                                       mfi = merge_file(o, &one, &a, &b,
-                                                        branch1,
-                                                        branch2);
+                                       mfi = merge_file(o, ren1_dst, null_sha1, 0,
+                                                        ren1->pair->two->sha1, ren1->pair->two->mode,
+                                                        dst_other.sha1, dst_other.mode,
+                                                        branch1, branch2);
                                        output(o, 1, "Adding merged %s", ren1_dst);
-                                       update_file(o, 0,
-                                                   mfi.sha,
-                                                   mfi.mode,
-                                                   ren1_dst);
+                                       update_file(o, 0, mfi.sha, mfi.mode, ren1_dst);
                                        try_merge = 0;
                                } else {
-                                       new_path = unique_path(o, ren1_dst, branch2);
+                                       char *new_path = unique_path(o, ren1_dst, branch2);
                                        output(o, 1, "Adding as %s instead", new_path);
                                        update_file(o, 0, dst_other.sha1, dst_other.mode, new_path);
+                                       free(new_path);
                                }
-                       } else if ((item = string_list_lookup(renames2Dst, ren1_dst))) {
-                               ren2 = item->util;
-                               clean_merge = 0;
-                               ren2->processed = 1;
-                               output(o, 1, "CONFLICT (rename/rename): "
-                                      "Rename %s->%s in %s. "
-                                      "Rename %s->%s in %s",
-                                      ren1_src, ren1_dst, branch1,
-                                      ren2->pair->one->path, ren2->pair->two->path, branch2);
-                               conflict_rename_rename_2to1(o, ren1, branch1, ren2, branch2);
                        } else
                                try_merge = 1;
 
@@ -1129,16 +1483,17 @@ static int process_renames(struct merge_options *o,
                                        b = ren1->pair->two;
                                        a = &src_other;
                                }
-                               update_stages_and_entry(ren1_dst, ren1->dst_entry, one, a, b, 1);
-                               if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
-                                       setup_rename_df_conflict_info(RENAME_NORMAL,
-                                                                     ren1->pair,
-                                                                     NULL,
-                                                                     branch1,
-                                                                     NULL,
-                                                                     ren1->dst_entry,
-                                                                     NULL);
-                               }
+                               update_entry(ren1->dst_entry, one, a, b);
+                               setup_rename_conflict_info(RENAME_NORMAL,
+                                                          ren1->pair,
+                                                          NULL,
+                                                          branch1,
+                                                          NULL,
+                                                          ren1->dst_entry,
+                                                          NULL,
+                                                          o,
+                                                          NULL,
+                                                          NULL);
                        }
                }
        }
@@ -1200,29 +1555,18 @@ error_return:
        return ret;
 }
 
-static void handle_delete_modify(struct merge_options *o,
+static void handle_modify_delete(struct merge_options *o,
                                 const char *path,
-                                const char *new_path,
+                                unsigned char *o_sha, int o_mode,
                                 unsigned char *a_sha, int a_mode,
                                 unsigned char *b_sha, int b_mode)
 {
-       if (!a_sha) {
-               output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
-                      "and modified in %s. Version %s of %s left in tree%s%s.",
-                      path, o->branch1,
-                      o->branch2, o->branch2, path,
-                      path == new_path ? "" : " at ",
-                      path == new_path ? "" : new_path);
-               update_file(o, 0, b_sha, b_mode, new_path);
-       } else {
-               output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
-                      "and modified in %s. Version %s of %s left in tree%s%s.",
-                      path, o->branch2,
-                      o->branch1, o->branch1, path,
-                      path == new_path ? "" : " at ",
-                      path == new_path ? "" : new_path);
-               update_file(o, 0, a_sha, a_mode, new_path);
-       }
+       handle_change_delete(o,
+                            path,
+                            o_sha, o_mode,
+                            a_sha, a_mode,
+                            b_sha, b_mode,
+                            "modify", "modified");
 }
 
 static int merge_content(struct merge_options *o,
@@ -1230,12 +1574,12 @@ static int merge_content(struct merge_options *o,
                         unsigned char *o_sha, int o_mode,
                         unsigned char *a_sha, int a_mode,
                         unsigned char *b_sha, int b_mode,
-                        const char *df_rename_conflict_branch)
+                        struct rename_conflict_info *rename_conflict_info)
 {
        const char *reason = "content";
+       const char *path1 = NULL, *path2 = NULL;
        struct merge_file_info mfi;
        struct diff_filespec one, a, b;
-       struct stat st;
        unsigned df_conflict_remains = 0;
 
        if (!o_sha) {
@@ -1250,16 +1594,43 @@ static int merge_content(struct merge_options *o,
        hashcpy(b.sha1, b_sha);
        b.mode = b_mode;
 
-       mfi = merge_file(o, &one, &a, &b, o->branch1, o->branch2);
-       if (df_rename_conflict_branch &&
-           lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
-               df_conflict_remains = 1;
+       if (rename_conflict_info) {
+               struct diff_filepair *pair1 = rename_conflict_info->pair1;
+
+               path1 = (o->branch1 == rename_conflict_info->branch1) ?
+                       pair1->two->path : pair1->one->path;
+               /* If rename_conflict_info->pair2 != NULL, we are in
+                * RENAME_ONE_FILE_TO_ONE case.  Otherwise, we have a
+                * normal rename.
+                */
+               path2 = (rename_conflict_info->pair2 ||
+                        o->branch2 == rename_conflict_info->branch1) ?
+                       pair1->two->path : pair1->one->path;
+
+               if (dir_in_way(path, !o->call_depth))
+                       df_conflict_remains = 1;
        }
+       mfi = merge_file_special_markers(o, &one, &a, &b,
+                                        o->branch1, path1,
+                                        o->branch2, path2);
 
        if (mfi.clean && !df_conflict_remains &&
-           sha_eq(mfi.sha, a_sha) && mfi.mode == a.mode)
+           sha_eq(mfi.sha, a_sha) && mfi.mode == a_mode) {
+               int path_renamed_outside_HEAD;
                output(o, 3, "Skipped %s (merged same as existing)", path);
-       else
+               /*
+                * The content merge resulted in the same file contents we
+                * already had.  We can return early if those file contents
+                * are recorded at the correct path (which may not be true
+                * if the merge involves a rename).
+                */
+               path_renamed_outside_HEAD = !path2 || !strcmp(path, path2);
+               if (!path_renamed_outside_HEAD) {
+                       add_cacheinfo(mfi.mode, mfi.sha, path,
+                                     0, (!o->call_depth), 0);
+                       return mfi.clean;
+               }
+       } else
                output(o, 2, "Auto-merging %s", path);
 
        if (!mfi.clean) {
@@ -1267,16 +1638,34 @@ static int merge_content(struct merge_options *o,
                        reason = "submodule";
                output(o, 1, "CONFLICT (%s): Merge conflict in %s",
                                reason, path);
+               if (rename_conflict_info && !df_conflict_remains)
+                       update_stages(path, &one, &a, &b);
        }
 
        if (df_conflict_remains) {
-               const char *new_path;
-               update_file_flags(o, mfi.sha, mfi.mode, path,
-                                 o->call_depth || mfi.clean, 0);
-               new_path = unique_path(o, path, df_rename_conflict_branch);
-               mfi.clean = 0;
+               char *new_path;
+               if (o->call_depth) {
+                       remove_file_from_cache(path);
+               } else {
+                       if (!mfi.clean)
+                               update_stages(path, &one, &a, &b);
+                       else {
+                               int file_from_stage2 = was_tracked(path);
+                               struct diff_filespec merged;
+                               hashcpy(merged.sha1, mfi.sha);
+                               merged.mode = mfi.mode;
+
+                               update_stages(path, NULL,
+                                             file_from_stage2 ? &merged : NULL,
+                                             file_from_stage2 ? NULL : &merged);
+                       }
+
+               }
+               new_path = unique_path(o, path, rename_conflict_info->branch1);
                output(o, 1, "Adding as %s instead", new_path);
-               update_file_flags(o, mfi.sha, mfi.mode, new_path, 0, 1);
+               update_file(o, 0, mfi.sha, mfi.mode, new_path);
+               free(new_path);
+               mfi.clean = 0;
        } else {
                update_file(o, mfi.clean, mfi.sha, mfi.mode, path);
        }
@@ -1301,104 +1690,15 @@ static int process_entry(struct merge_options *o,
        unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
        unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
 
-       if (entry->rename_df_conflict_info)
-               return 1; /* Such cases are handled elsewhere. */
-
-       entry->processed = 1;
-       if (o_sha && (!a_sha || !b_sha)) {
-               /* Case A: Deleted in one */
-               if ((!a_sha && !b_sha) ||
-                   (!b_sha && blob_unchanged(o_sha, a_sha, normalize, path)) ||
-                   (!a_sha && blob_unchanged(o_sha, b_sha, normalize, path))) {
-                       /* Deleted in both or deleted in one and
-                        * unchanged in the other */
-                       if (a_sha)
-                               output(o, 2, "Removing %s", path);
-                       /* do not touch working file if it did not exist */
-                       remove_file(o, 1, path, !a_sha);
-               } else if (string_list_has_string(&o->current_directory_set,
-                                                 path)) {
-                       entry->processed = 0;
-                       return 1; /* Assume clean until processed */
-               } else {
-                       /* Deleted in one and changed in the other */
-                       clean_merge = 0;
-                       handle_delete_modify(o, path, path,
-                                            a_sha, a_mode, b_sha, b_mode);
-               }
-
-       } else if ((!o_sha && a_sha && !b_sha) ||
-                  (!o_sha && !a_sha && b_sha)) {
-               /* Case B: Added in one. */
-               unsigned mode;
-               const unsigned char *sha;
-
-               if (a_sha) {
-                       mode = a_mode;
-                       sha = a_sha;
-               } else {
-                       mode = b_mode;
-                       sha = b_sha;
-               }
-               if (string_list_has_string(&o->current_directory_set, path)) {
-                       /* Handle D->F conflicts after all subfiles */
-                       entry->processed = 0;
-                       return 1; /* Assume clean until processed */
-               } else {
-                       output(o, 2, "Adding %s", path);
-                       update_file(o, 1, sha, mode, path);
-               }
-       } else if (a_sha && b_sha) {
-               /* Case C: Added in both (check for same permissions) and */
-               /* case D: Modified in both, but differently. */
-               clean_merge = merge_content(o, path,
-                                           o_sha, o_mode, a_sha, a_mode, b_sha, b_mode,
-                                           NULL);
-       } else if (!o_sha && !a_sha && !b_sha) {
-               /*
-                * this entry was deleted altogether. a_mode == 0 means
-                * we had that path and want to actively remove it.
-                */
-               remove_file(o, 1, path, !a_mode);
-       } else
-               die("Fatal merge failure, shouldn't happen.");
-
-       return clean_merge;
-}
-
-/*
- * Per entry merge function for D/F (and/or rename) conflicts.  In the
- * cases we can cleanly resolve D/F conflicts, process_entry() can
- * clean out all the files below the directory for us.  All D/F
- * conflict cases must be handled here at the end to make sure any
- * directories that can be cleaned out, are.
- *
- * Some rename conflicts may also be handled here that don't necessarily
- * involve D/F conflicts, since the code to handle them is generic enough
- * to handle those rename conflicts with or without D/F conflicts also
- * being involved.
- */
-static int process_df_entry(struct merge_options *o,
-                           const char *path, struct stage_data *entry)
-{
-       int clean_merge = 1;
-       unsigned o_mode = entry->stages[1].mode;
-       unsigned a_mode = entry->stages[2].mode;
-       unsigned b_mode = entry->stages[3].mode;
-       unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode);
-       unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
-       unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
-       struct stat st;
-
        entry->processed = 1;
-       if (entry->rename_df_conflict_info) {
-               struct rename_df_conflict_info *conflict_info = entry->rename_df_conflict_info;
-               char *src;
+       if (entry->rename_conflict_info) {
+               struct rename_conflict_info *conflict_info = entry->rename_conflict_info;
                switch (conflict_info->rename_type) {
                case RENAME_NORMAL:
+               case RENAME_ONE_FILE_TO_ONE:
                        clean_merge = merge_content(o, path,
                                                    o_sha, o_mode, a_sha, a_mode, b_sha, b_mode,
-                                                   conflict_info->branch1);
+                                                   conflict_info);
                        break;
                case RENAME_DELETE:
                        clean_merge = 0;
@@ -1407,39 +1707,39 @@ static int process_df_entry(struct merge_options *o,
                                               conflict_info->branch2);
                        break;
                case RENAME_ONE_FILE_TO_TWO:
-                       src = conflict_info->pair1->one->path;
                        clean_merge = 0;
-                       output(o, 1, "CONFLICT (rename/rename): "
-                              "Rename \"%s\"->\"%s\" in branch \"%s\" "
-                              "rename \"%s\"->\"%s\" in \"%s\"%s",
-                              src, conflict_info->pair1->two->path, conflict_info->branch1,
-                              src, conflict_info->pair2->two->path, conflict_info->branch2,
-                              o->call_depth ? " (left unresolved)" : "");
-                       if (o->call_depth) {
-                               remove_file_from_cache(src);
-                               update_file(o, 0, conflict_info->pair1->one->sha1,
-                                           conflict_info->pair1->one->mode, src);
-                       }
-                       conflict_rename_rename_1to2(o, conflict_info->pair1,
-                                                   conflict_info->branch1,
-                                                   conflict_info->pair2,
-                                                   conflict_info->branch2);
-                       conflict_info->dst_entry2->processed = 1;
+                       conflict_rename_rename_1to2(o, conflict_info);
+                       break;
+               case RENAME_TWO_FILES_TO_ONE:
+                       clean_merge = 0;
+                       conflict_rename_rename_2to1(o, conflict_info);
                        break;
                default:
                        entry->processed = 0;
                        break;
                }
        } else if (o_sha && (!a_sha || !b_sha)) {
-               /* Modify/delete; deleted side may have put a directory in the way */
-               const char *new_path = path;
-               if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode))
-                       new_path = unique_path(o, path, a_sha ? o->branch1 : o->branch2);
-               clean_merge = 0;
-               handle_delete_modify(o, path, new_path,
-                                    a_sha, a_mode, b_sha, b_mode);
-       } else if (!o_sha && !!a_sha != !!b_sha) {
-               /* directory -> (directory, file) */
+               /* Case A: Deleted in one */
+               if ((!a_sha && !b_sha) ||
+                   (!b_sha && blob_unchanged(o_sha, a_sha, normalize, path)) ||
+                   (!a_sha && blob_unchanged(o_sha, b_sha, normalize, path))) {
+                       /* Deleted in both or deleted in one and
+                        * unchanged in the other */
+                       if (a_sha)
+                               output(o, 2, "Removing %s", path);
+                       /* do not touch working file if it did not exist */
+                       remove_file(o, 1, path, !a_sha);
+               } else {
+                       /* Modify/delete; deleted side may have put a directory in the way */
+                       clean_merge = 0;
+                       handle_modify_delete(o, path, o_sha, o_mode,
+                                            a_sha, a_mode, b_sha, b_mode);
+               }
+       } else if ((!o_sha && a_sha && !b_sha) ||
+                  (!o_sha && !a_sha && b_sha)) {
+               /* Case B: Added in one. */
+               /* [nothing|directory] -> ([nothing|directory], file) */
+
                const char *add_branch;
                const char *other_branch;
                unsigned mode;
@@ -1459,21 +1759,37 @@ static int process_df_entry(struct merge_options *o,
                        sha = b_sha;
                        conf = "directory/file";
                }
-               if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
-                       const char *new_path = unique_path(o, path, add_branch);
+               if (dir_in_way(path, !o->call_depth)) {
+                       char *new_path = unique_path(o, path, add_branch);
                        clean_merge = 0;
                        output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. "
                               "Adding %s as %s",
                               conf, path, other_branch, path, new_path);
+                       if (o->call_depth)
+                               remove_file_from_cache(path);
                        update_file(o, 0, sha, mode, new_path);
+                       if (o->call_depth)
+                               remove_file_from_cache(path);
+                       free(new_path);
                } else {
                        output(o, 2, "Adding %s", path);
-                       update_file(o, 1, sha, mode, path);
+                       /* do not overwrite file if already present */
+                       update_file_flags(o, sha, mode, path, 1, !a_sha);
                }
-       } else {
-               entry->processed = 0;
-               return 1; /* not handled; assume clean until processed */
-       }
+       } else if (a_sha && b_sha) {
+               /* Case C: Added in both (check for same permissions) and */
+               /* case D: Modified in both, but differently. */
+               clean_merge = merge_content(o, path,
+                                           o_sha, o_mode, a_sha, a_mode, b_sha, b_mode,
+                                           NULL);
+       } else if (!o_sha && !a_sha && !b_sha) {
+               /*
+                * this entry was deleted altogether. a_mode == 0 means
+                * we had that path and want to actively remove it.
+                */
+               remove_file(o, 1, path, !a_mode);
+       } else
+               die("Fatal merge failure, shouldn't happen.");
 
        return clean_merge;
 }
@@ -1517,24 +1833,17 @@ int merge_trees(struct merge_options *o,
                get_files_dirs(o, merge);
 
                entries = get_unmerged();
-               make_room_for_directories_of_df_conflicts(o, entries);
+               record_df_conflict_files(o, entries);
                re_head  = get_renames(o, head, common, head, merge, entries);
                re_merge = get_renames(o, merge, common, head, merge, entries);
                clean = process_renames(o, re_head, re_merge);
-               for (i = 0; i < entries->nr; i++) {
+               for (i = entries->nr-1; 0 <= i; i--) {
                        const char *path = entries->items[i].string;
                        struct stage_data *e = entries->items[i].util;
                        if (!e->processed
                                && !process_entry(o, path, e))
                                clean = 0;
                }
-               for (i = 0; i < entries->nr; i++) {
-                       const char *path = entries->items[i].string;
-                       struct stage_data *e = entries->items[i].util;
-                       if (!e->processed
-                               && !process_df_entry(o, path, e))
-                               clean = 0;
-               }
                for (i = 0; i < entries->nr; i++) {
                        struct stage_data *e = entries->items[i].util;
                        if (!e->processed)
@@ -1601,12 +1910,10 @@ int merge_recursive(struct merge_options *o,
 
        merged_common_ancestors = pop_commit(&ca);
        if (merged_common_ancestors == NULL) {
-               /* if there is no common ancestor, make an empty tree */
-               struct tree *tree = xcalloc(1, sizeof(struct tree));
+               /* if there is no common ancestor, use an empty tree */
+               struct tree *tree;
 
-               tree->object.parsed = 1;
-               tree->object.type = OBJ_TREE;
-               pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1);
+               tree = lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN);
                merged_common_ancestors = make_virtual_commit(tree, "ancestor");
        }
 
@@ -1743,6 +2050,8 @@ void init_merge_options(struct merge_options *o)
        o->current_file_set.strdup_strings = 1;
        memset(&o->current_directory_set, 0, sizeof(struct string_list));
        o->current_directory_set.strdup_strings = 1;
+       memset(&o->df_conflict_file_set, 0, sizeof(struct string_list));
+       o->df_conflict_file_set.strdup_strings = 1;
 }
 
 int parse_merge_opt(struct merge_options *o, const char *s)
@@ -1759,6 +2068,8 @@ int parse_merge_opt(struct merge_options *o, const char *s)
                o->subtree_shift = s + strlen("subtree=");
        else if (!strcmp(s, "patience"))
                o->xdl_opts |= XDF_PATIENCE_DIFF;
+       else if (!strcmp(s, "histogram"))
+               o->xdl_opts |= XDF_HISTOGRAM_DIFF;
        else if (!strcmp(s, "ignore-space-change"))
                o->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE;
        else if (!strcmp(s, "ignore-all-space"))
index 7e1e972b13cb8a9f2fae084f769fffede24c8dbb..58f3435e9e854ab82c2fd0c10c55520bc69e5ed0 100644 (file)
@@ -26,6 +26,7 @@ struct merge_options {
        struct strbuf obuf;
        struct string_list current_file_set;
        struct string_list current_directory_set;
+       struct string_list df_conflict_file_set;
 };
 
 /* merge_trees() but with recursive ancestor consolidation */
diff --git a/mergetools/araxis b/mergetools/araxis
new file mode 100644 (file)
index 0000000..64f97c5
--- /dev/null
@@ -0,0 +1,20 @@
+diff_cmd () {
+       "$merge_tool_path" -wait -2 "$LOCAL" "$REMOTE" >/dev/null 2>&1
+}
+
+merge_cmd () {
+       touch "$BACKUP"
+       if $base_present
+       then
+               "$merge_tool_path" -wait -merge -3 -a1 \
+                       "$BASE" "$LOCAL" "$REMOTE" "$MERGED" >/dev/null 2>&1
+       else
+               "$merge_tool_path" -wait -2 \
+                       "$LOCAL" "$REMOTE" "$MERGED" >/dev/null 2>&1
+       fi
+       check_unchanged
+}
+
+translate_merge_tool_path() {
+       echo compare
+}
diff --git a/mergetools/bc3 b/mergetools/bc3
new file mode 100644 (file)
index 0000000..27b3dd4
--- /dev/null
@@ -0,0 +1,20 @@
+diff_cmd () {
+       "$merge_tool_path" "$LOCAL" "$REMOTE"
+}
+
+merge_cmd () {
+       touch "$BACKUP"
+       if $base_present
+       then
+               "$merge_tool_path" "$LOCAL" "$REMOTE" "$BASE" \
+                       -mergeoutput="$MERGED"
+       else
+               "$merge_tool_path" "$LOCAL" "$REMOTE" \
+                       -mergeoutput="$MERGED"
+       fi
+       check_unchanged
+}
+
+translate_merge_tool_path() {
+       echo bcompare
+}
diff --git a/mergetools/defaults b/mergetools/defaults
new file mode 100644 (file)
index 0000000..1d8f2a3
--- /dev/null
@@ -0,0 +1,46 @@
+# Redefined by builtin tools
+can_merge () {
+       return 0
+}
+
+can_diff () {
+       return 0
+}
+
+diff_cmd () {
+       merge_tool_cmd="$(get_merge_tool_cmd "$1")"
+       if test -z "$merge_tool_cmd"
+       then
+               status=1
+               break
+       fi
+       ( eval $merge_tool_cmd )
+       status=$?
+       return $status
+}
+
+merge_cmd () {
+       merge_tool_cmd="$(get_merge_tool_cmd "$1")"
+       if test -z "$merge_tool_cmd"
+       then
+               status=1
+               break
+       fi
+       trust_exit_code="$(git config --bool \
+               mergetool."$1".trustExitCode || echo false)"
+       if test "$trust_exit_code" = "false"
+       then
+               touch "$BACKUP"
+               ( eval $merge_tool_cmd )
+               status=$?
+               check_unchanged
+       else
+               ( eval $merge_tool_cmd )
+               status=$?
+       fi
+       return $status
+}
+
+translate_merge_tool_path () {
+       echo "$1"
+}
diff --git a/mergetools/diffuse b/mergetools/diffuse
new file mode 100644 (file)
index 0000000..02e0843
--- /dev/null
@@ -0,0 +1,17 @@
+diff_cmd () {
+       "$merge_tool_path" "$LOCAL" "$REMOTE" | cat
+}
+
+merge_cmd () {
+       touch "$BACKUP"
+       if $base_present
+       then
+               "$merge_tool_path" \
+                       "$LOCAL" "$MERGED" "$REMOTE" \
+                       "$BASE" | cat
+       else
+               "$merge_tool_path" \
+                       "$LOCAL" "$MERGED" "$REMOTE" | cat
+       fi
+       check_unchanged
+}
diff --git a/mergetools/ecmerge b/mergetools/ecmerge
new file mode 100644 (file)
index 0000000..13c2e43
--- /dev/null
@@ -0,0 +1,16 @@
+diff_cmd () {
+       "$merge_tool_path" --default --mode=diff2 "$LOCAL" "$REMOTE"
+}
+
+merge_cmd () {
+       touch "$BACKUP"
+       if $base_present
+       then
+               "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" \
+                       --default --mode=merge3 --to="$MERGED"
+       else
+               "$merge_tool_path" "$LOCAL" "$REMOTE" \
+                       --default --mode=merge2 --to="$MERGED"
+       fi
+       check_unchanged
+}
diff --git a/mergetools/emerge b/mergetools/emerge
new file mode 100644 (file)
index 0000000..f96d9e5
--- /dev/null
@@ -0,0 +1,23 @@
+diff_cmd () {
+       "$merge_tool_path" -f emerge-files-command "$LOCAL" "$REMOTE"
+}
+
+merge_cmd () {
+       if $base_present
+       then
+               "$merge_tool_path" \
+                       -f emerge-files-with-ancestor-command \
+                       "$LOCAL" "$REMOTE" "$BASE" \
+                       "$(basename "$MERGED")"
+       else
+               "$merge_tool_path" \
+                       -f emerge-files-command \
+                       "$LOCAL" "$REMOTE" \
+                       "$(basename "$MERGED")"
+       fi
+       status=$?
+}
+
+translate_merge_tool_path() {
+       echo emacs
+}
diff --git a/mergetools/kdiff3 b/mergetools/kdiff3
new file mode 100644 (file)
index 0000000..28fead4
--- /dev/null
@@ -0,0 +1,24 @@
+diff_cmd () {
+       "$merge_tool_path" --auto \
+               --L1 "$MERGED (A)" --L2 "$MERGED (B)" \
+               "$LOCAL" "$REMOTE" >/dev/null 2>&1
+}
+
+merge_cmd () {
+       if $base_present
+       then
+               "$merge_tool_path" --auto \
+                       --L1 "$MERGED (Base)" \
+                       --L2 "$MERGED (Local)" \
+                       --L3 "$MERGED (Remote)" \
+                       -o "$MERGED" "$BASE" "$LOCAL" "$REMOTE" \
+               >/dev/null 2>&1
+       else
+               "$merge_tool_path" --auto \
+                       --L1 "$MERGED (Local)" \
+                       --L2 "$MERGED (Remote)" \
+                       -o "$MERGED" "$LOCAL" "$REMOTE" \
+               >/dev/null 2>&1
+       fi
+       status=$?
+}
diff --git a/mergetools/kompare b/mergetools/kompare
new file mode 100644 (file)
index 0000000..433686c
--- /dev/null
@@ -0,0 +1,7 @@
+can_merge () {
+       return 1
+}
+
+diff_cmd () {
+       "$merge_tool_path" "$LOCAL" "$REMOTE"
+}
diff --git a/mergetools/meld b/mergetools/meld
new file mode 100644 (file)
index 0000000..eaa115c
--- /dev/null
@@ -0,0 +1,32 @@
+diff_cmd () {
+       "$merge_tool_path" "$LOCAL" "$REMOTE"
+}
+
+merge_cmd () {
+       if test -z "${meld_has_output_option:+set}"
+       then
+               check_meld_for_output_version
+       fi
+       touch "$BACKUP"
+       if test "$meld_has_output_option" = true
+       then
+               "$merge_tool_path" --output "$MERGED" \
+                       "$LOCAL" "$BASE" "$REMOTE"
+       else
+               "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"
+       fi
+       check_unchanged
+}
+
+# Check whether 'meld --output <file>' is supported
+check_meld_for_output_version () {
+       meld_path="$(git config mergetool.meld.path)"
+       meld_path="${meld_path:-meld}"
+
+       if "$meld_path" --output /dev/null --help >/dev/null 2>&1
+       then
+               meld_has_output_option=true
+       else
+               meld_has_output_option=false
+       fi
+}
diff --git a/mergetools/opendiff b/mergetools/opendiff
new file mode 100644 (file)
index 0000000..0942b2a
--- /dev/null
@@ -0,0 +1,16 @@
+diff_cmd () {
+       "$merge_tool_path" "$LOCAL" "$REMOTE" | cat
+}
+
+merge_cmd () {
+       touch "$BACKUP"
+       if $base_present
+       then
+               "$merge_tool_path" "$LOCAL" "$REMOTE" \
+                       -ancestor "$BASE" -merge "$MERGED" | cat
+       else
+               "$merge_tool_path" "$LOCAL" "$REMOTE" \
+                       -merge "$MERGED" | cat
+       fi
+       check_unchanged
+}
diff --git a/mergetools/p4merge b/mergetools/p4merge
new file mode 100644 (file)
index 0000000..1a45c1b
--- /dev/null
@@ -0,0 +1,10 @@
+diff_cmd () {
+       "$merge_tool_path" "$LOCAL" "$REMOTE"
+}
+
+merge_cmd () {
+       touch "$BACKUP"
+       $base_present || >"$BASE"
+       "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED"
+       check_unchanged
+}
diff --git a/mergetools/tkdiff b/mergetools/tkdiff
new file mode 100644 (file)
index 0000000..618c438
--- /dev/null
@@ -0,0 +1,12 @@
+diff_cmd () {
+       "$merge_tool_path" "$LOCAL" "$REMOTE"
+}
+
+merge_cmd () {
+       if $base_present
+       then
+               "$merge_tool_path" -a "$BASE" -o "$MERGED" "$LOCAL" "$REMOTE"
+       else
+               "$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE"
+       fi
+}
diff --git a/mergetools/tortoisemerge b/mergetools/tortoisemerge
new file mode 100644 (file)
index 0000000..ed7db49
--- /dev/null
@@ -0,0 +1,17 @@
+can_diff () {
+       return 1
+}
+
+merge_cmd () {
+       if $base_present
+       then
+               touch "$BACKUP"
+               "$merge_tool_path" \
+                       -base:"$BASE" -mine:"$LOCAL" \
+                       -theirs:"$REMOTE" -merged:"$MERGED"
+               check_unchanged
+       else
+               echo "TortoiseMerge cannot be used without a base" 1>&2
+               return 1
+       fi
+}
diff --git a/mergetools/vim b/mergetools/vim
new file mode 100644 (file)
index 0000000..619594a
--- /dev/null
@@ -0,0 +1,44 @@
+diff_cmd () {
+       case "$1" in
+       gvimdiff|vimdiff)
+               "$merge_tool_path" -R -f -d \
+                       -c 'wincmd l' -c 'cd $GIT_PREFIX' "$LOCAL" "$REMOTE"
+               ;;
+       gvimdiff2|vimdiff2)
+               "$merge_tool_path" -R -f -d \
+                       -c 'wincmd l' -c 'cd $GIT_PREFIX' "$LOCAL" "$REMOTE"
+               ;;
+       esac
+}
+
+merge_cmd () {
+       touch "$BACKUP"
+       case "$1" in
+       gvimdiff|vimdiff)
+               if $base_present
+               then
+                       "$merge_tool_path" -f -d -c 'wincmd J' \
+                               "$MERGED" "$LOCAL" "$BASE" "$REMOTE"
+               else
+                       "$merge_tool_path" -f -d -c 'wincmd l' \
+                               "$LOCAL" "$MERGED" "$REMOTE"
+               fi
+               ;;
+       gvimdiff2|vimdiff2)
+               "$merge_tool_path" -f -d -c 'wincmd l' \
+                       "$LOCAL" "$MERGED" "$REMOTE"
+               ;;
+       esac
+       check_unchanged
+}
+
+translate_merge_tool_path() {
+       case "$1" in
+       gvimdiff|gvimdiff2)
+               echo gvim
+               ;;
+       vimdiff|vimdiff2)
+               echo vim
+               ;;
+       esac
+}
diff --git a/mergetools/xxdiff b/mergetools/xxdiff
new file mode 100644 (file)
index 0000000..05b4433
--- /dev/null
@@ -0,0 +1,25 @@
+diff_cmd () {
+       "$merge_tool_path" \
+               -R 'Accel.Search: "Ctrl+F"' \
+               -R 'Accel.SearchForward: "Ctrl-G"' \
+               "$LOCAL" "$REMOTE"
+}
+
+merge_cmd () {
+       touch "$BACKUP"
+       if $base_present
+       then
+               "$merge_tool_path" -X --show-merged-pane \
+                       -R 'Accel.SaveAsMerged: "Ctrl-S"' \
+                       -R 'Accel.Search: "Ctrl+F"' \
+                       -R 'Accel.SearchForward: "Ctrl-G"' \
+                       --merged-file "$MERGED" "$LOCAL" "$BASE" "$REMOTE"
+       else
+               "$merge_tool_path" -X $extra \
+                       -R 'Accel.SaveAsMerged: "Ctrl-S"' \
+                       -R 'Accel.Search: "Ctrl+F"' \
+                       -R 'Accel.SearchForward: "Ctrl-G"' \
+                       --merged-file "$MERGED" "$LOCAL" "$REMOTE"
+       fi
+       check_unchanged
+}
index e1aaf43b438d0cfb6b7b0c723060a662a915bcea..baaf31f4aed4e4cfd967c278b7cfd4bee6a0ab30 100644 (file)
@@ -680,7 +680,7 @@ int notes_merge_commit(struct notes_merge_options *o,
         * Finally store the new commit object SHA1 into 'result_sha1'.
         */
        struct dir_struct dir;
-       const char *path = git_path(NOTES_MERGE_WORKTREE "/");
+       char *path = xstrdup(git_path(NOTES_MERGE_WORKTREE "/"));
        int path_len = strlen(path), i;
        const char *msg = strstr(partial_commit->buffer, "\n\n");
 
@@ -720,6 +720,7 @@ int notes_merge_commit(struct notes_merge_options *o,
                            result_sha1);
        OUTPUT(o, 4, "Finalized notes merge commit: %s",
               sha1_to_hex(result_sha1));
+       free(path);
        return 0;
 }
 
diff --git a/notes.c b/notes.c
index f6ce8489d31e48d30dfbc6aef4edf6dbb8314234..93e9868d5d1aa536b70e981d3a4cd3c7969764d3 100644 (file)
--- a/notes.c
+++ b/notes.c
@@ -1105,7 +1105,7 @@ int remove_note(struct notes_tree *t, const unsigned char *object_sha1)
        hashcpy(l.key_sha1, object_sha1);
        hashclr(l.val_sha1);
        note_tree_remove(t, t->root, 0, &l);
-       if (is_null_sha1(l.val_sha1)) // no note was removed
+       if (is_null_sha1(l.val_sha1)) /* no note was removed */
                return 1;
        t->dirty = 1;
        return 0;
index a1a521648deee8e7e82c4e2cb7fe8fe03605dc06..0c19b6e5a5677bd14989175abddc119381fac4ef 100644 (file)
@@ -26,7 +26,7 @@ int check_pack_crc(struct packed_git *p, struct pack_window **w_curs,
        uint32_t data_crc = crc32(0, NULL, 0);
 
        do {
-               unsigned int avail;
+               unsigned long avail;
                void *data = use_pack(p, w_curs, offset, &avail);
                if (avail > len)
                        avail = len;
@@ -61,7 +61,7 @@ static int verify_packfile(struct packed_git *p,
 
        git_SHA1_Init(&ctx);
        do {
-               unsigned int remaining;
+               unsigned long remaining;
                unsigned char *in = use_pack(p, w_curs, offset, &remaining);
                offset += remaining;
                if (!pack_sig_ofs)
index a905ca4486754f099a30f90a2fcd22d0c771a070..9cd3bfbb4b3859cbbdc1b9375ea95f511fffc94e 100644 (file)
@@ -2,8 +2,12 @@
 #include "pack.h"
 #include "csum-file.h"
 
-uint32_t pack_idx_default_version = 2;
-uint32_t pack_idx_off32_limit = 0x7fffffff;
+void reset_pack_idx_option(struct pack_idx_option *opts)
+{
+       memset(opts, 0, sizeof(*opts));
+       opts->version = 2;
+       opts->off32_limit = 0x7fffffff;
+}
 
 static int sha1_compare(const void *_a, const void *_b)
 {
@@ -12,13 +16,35 @@ static int sha1_compare(const void *_a, const void *_b)
        return hashcmp(a->sha1, b->sha1);
 }
 
+static int cmp_uint32(const void *a_, const void *b_)
+{
+       uint32_t a = *((uint32_t *)a_);
+       uint32_t b = *((uint32_t *)b_);
+
+       return (a < b) ? -1 : (a != b);
+}
+
+static int need_large_offset(off_t offset, const struct pack_idx_option *opts)
+{
+       uint32_t ofsval;
+
+       if ((offset >> 31) || (opts->off32_limit < offset))
+               return 1;
+       if (!opts->anomaly_nr)
+               return 0;
+       ofsval = offset;
+       return !!bsearch(&ofsval, opts->anomaly, opts->anomaly_nr,
+                        sizeof(ofsval), cmp_uint32);
+}
+
 /*
  * On entry *sha1 contains the pack content SHA1 hash, on exit it is
  * the SHA1 hash of sorted object names. The objects array passed in
  * will be sorted by SHA1 on exit.
  */
 const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects,
-                          int nr_objects, unsigned char *sha1)
+                          int nr_objects, const struct pack_idx_option *opts,
+                          unsigned char *sha1)
 {
        struct sha1file *f;
        struct pack_idx_entry **sorted_by_sha, **list, **last;
@@ -42,20 +68,25 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec
        else
                sorted_by_sha = list = last = NULL;
 
-       if (!index_name) {
-               static char tmpfile[PATH_MAX];
-               fd = odb_mkstemp(tmpfile, sizeof(tmpfile), "pack/tmp_idx_XXXXXX");
-               index_name = xstrdup(tmpfile);
+       if (opts->flags & WRITE_IDX_VERIFY) {
+               assert(index_name);
+               f = sha1fd_check(index_name);
        } else {
-               unlink(index_name);
-               fd = open(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
+               if (!index_name) {
+                       static char tmpfile[PATH_MAX];
+                       fd = odb_mkstemp(tmpfile, sizeof(tmpfile), "pack/tmp_idx_XXXXXX");
+                       index_name = xstrdup(tmpfile);
+               } else {
+                       unlink(index_name);
+                       fd = open(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
+               }
+               if (fd < 0)
+                       die_errno("unable to create '%s'", index_name);
+               f = sha1fd(fd, index_name);
        }
-       if (fd < 0)
-               die_errno("unable to create '%s'", index_name);
-       f = sha1fd(fd, index_name);
 
        /* if last object's offset is >= 2^31 we should use index V2 */
-       index_version = (last_obj_offset >> 31) ? 2 : pack_idx_default_version;
+       index_version = need_large_offset(last_obj_offset, opts) ? 2 : opts->version;
 
        /* index versions 2 and above need a header */
        if (index_version >= 2) {
@@ -115,8 +146,11 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec
                list = sorted_by_sha;
                for (i = 0; i < nr_objects; i++) {
                        struct pack_idx_entry *obj = *list++;
-                       uint32_t offset = (obj->offset <= pack_idx_off32_limit) ?
-                               obj->offset : (0x80000000 | nr_large_offset++);
+                       uint32_t offset;
+
+                       offset = (need_large_offset(obj->offset, opts)
+                                 ? (0x80000000 | nr_large_offset++)
+                                 : obj->offset);
                        offset = htonl(offset);
                        sha1write(f, &offset, 4);
                }
@@ -126,18 +160,20 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec
                while (nr_large_offset) {
                        struct pack_idx_entry *obj = *list++;
                        uint64_t offset = obj->offset;
-                       if (offset > pack_idx_off32_limit) {
-                               uint32_t split[2];
-                               split[0] = htonl(offset >> 32);
-                               split[1] = htonl(offset & 0xffffffff);
-                               sha1write(f, split, 8);
-                               nr_large_offset--;
-                       }
+                       uint32_t split[2];
+
+                       if (!need_large_offset(offset, opts))
+                               continue;
+                       split[0] = htonl(offset >> 32);
+                       split[1] = htonl(offset & 0xffffffff);
+                       sha1write(f, split, 8);
+                       nr_large_offset--;
                }
        }
 
        sha1write(f, sha1, 20);
-       sha1close(f, NULL, CSUM_FSYNC);
+       sha1close(f, NULL, ((opts->flags & WRITE_IDX_VERIFY)
+                           ? CSUM_CLOSE : CSUM_FSYNC));
        git_SHA1_Final(sha1, &ctx);
        return index_name;
 }
diff --git a/pack.h b/pack.h
index bb275762b7eb6f473f333ae40780821e383db20b..722a54e00a2cb7d9514c12f799fb1ec15930cf5d 100644 (file)
--- a/pack.h
+++ b/pack.h
@@ -34,9 +34,24 @@ struct pack_header {
  */
 #define PACK_IDX_SIGNATURE 0xff744f63  /* "\377tOc" */
 
-/* These may be overridden by command-line parameters */
-extern uint32_t pack_idx_default_version;
-extern uint32_t pack_idx_off32_limit;
+struct pack_idx_option {
+       unsigned flags;
+       /* flag bits */
+#define WRITE_IDX_VERIFY 01
+
+       uint32_t version;
+       uint32_t off32_limit;
+
+       /*
+        * List of offsets that would fit within off32_limit but
+        * need to be written out as 64-bit entity for byte-for-byte
+        * verification.
+        */
+       int anomaly_alloc, anomaly_nr;
+       uint32_t *anomaly;
+};
+
+extern void reset_pack_idx_option(struct pack_idx_option *);
 
 /*
  * Packed object index header
@@ -55,7 +70,7 @@ struct pack_idx_entry {
        off_t offset;
 };
 
-extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1);
+extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, const struct pack_idx_option *, unsigned char *sha1);
 extern int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, off_t offset, off_t len, unsigned int nr);
 extern int verify_pack_index(struct packed_git *);
 extern int verify_pack(struct packed_git *);
diff --git a/pager.c b/pager.c
index dac358f047550a07dd8d90b2e410feab3eebd534..975955ba82a0dbb128d6733090cd74c2b509ea81 100644 (file)
--- a/pager.c
+++ b/pager.c
@@ -11,8 +11,6 @@
  * something different on Windows.
  */
 
-static int spawned_pager;
-
 #ifndef WIN32
 static void pager_preexec(void)
 {
@@ -78,7 +76,7 @@ void setup_pager(void)
        if (!pager)
                return;
 
-       spawned_pager = 1; /* means we are emitting to terminal */
+       setenv("GIT_PAGER_IN_USE", "true", 1);
 
        /* spawn the pager */
        pager_argv[0] = pager;
@@ -109,10 +107,6 @@ void setup_pager(void)
 int pager_in_use(void)
 {
        const char *env;
-
-       if (spawned_pager)
-               return 1;
-
        env = getenv("GIT_PAGER_IN_USE");
        return env ? git_config_bool("GIT_PAGER_IN_USE", env) : 0;
 }
diff --git a/parse-options-cb.c b/parse-options-cb.c
new file mode 100644 (file)
index 0000000..6db0921
--- /dev/null
@@ -0,0 +1,125 @@
+#include "git-compat-util.h"
+#include "parse-options.h"
+#include "cache.h"
+#include "commit.h"
+#include "color.h"
+#include "string-list.h"
+
+/*----- some often used options -----*/
+
+int parse_opt_abbrev_cb(const struct option *opt, const char *arg, int unset)
+{
+       int v;
+
+       if (!arg) {
+               v = unset ? 0 : DEFAULT_ABBREV;
+       } else {
+               v = strtol(arg, (char **)&arg, 10);
+               if (*arg)
+                       return opterror(opt, "expects a numerical value", 0);
+               if (v && v < MINIMUM_ABBREV)
+                       v = MINIMUM_ABBREV;
+               else if (v > 40)
+                       v = 40;
+       }
+       *(int *)(opt->value) = v;
+       return 0;
+}
+
+int parse_opt_approxidate_cb(const struct option *opt, const char *arg,
+                            int unset)
+{
+       *(unsigned long *)(opt->value) = approxidate(arg);
+       return 0;
+}
+
+int parse_opt_color_flag_cb(const struct option *opt, const char *arg,
+                           int unset)
+{
+       int value;
+
+       if (!arg)
+               arg = unset ? "never" : (const char *)opt->defval;
+       value = git_config_colorbool(NULL, arg);
+       if (value < 0)
+               return opterror(opt,
+                       "expects \"always\", \"auto\", or \"never\"", 0);
+       *(int *)opt->value = value;
+       return 0;
+}
+
+int parse_opt_verbosity_cb(const struct option *opt, const char *arg,
+                          int unset)
+{
+       int *target = opt->value;
+
+       if (unset)
+               /* --no-quiet, --no-verbose */
+               *target = 0;
+       else if (opt->short_name == 'v') {
+               if (*target >= 0)
+                       (*target)++;
+               else
+                       *target = 1;
+       } else {
+               if (*target <= 0)
+                       (*target)--;
+               else
+                       *target = -1;
+       }
+       return 0;
+}
+
+int parse_opt_with_commit(const struct option *opt, const char *arg, int unset)
+{
+       unsigned char sha1[20];
+       struct commit *commit;
+
+       if (!arg)
+               return -1;
+       if (get_sha1(arg, sha1))
+               return error("malformed object name %s", arg);
+       commit = lookup_commit_reference(sha1);
+       if (!commit)
+               return error("no such commit %s", arg);
+       commit_list_insert(commit, opt->value);
+       return 0;
+}
+
+int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
+{
+       int *target = opt->value;
+       *target = unset ? 2 : 1;
+       return 0;
+}
+
+int parse_options_concat(struct option *dst, size_t dst_size, struct option *src)
+{
+       int i, j;
+
+       for (i = 0; i < dst_size; i++)
+               if (dst[i].type == OPTION_END)
+                       break;
+       for (j = 0; i < dst_size; i++, j++) {
+               dst[i] = src[j];
+               if (src[j].type == OPTION_END)
+                       return 0;
+       }
+       return -1;
+}
+
+int parse_opt_string_list(const struct option *opt, const char *arg, int unset)
+{
+       struct string_list *v = opt->value;
+
+       if (unset) {
+               string_list_clear(v, 0);
+               return 0;
+       }
+
+       if (!arg)
+               return -1;
+
+       string_list_append(v, xstrdup(arg));
+       return 0;
+}
index 73bd28ad90986af1618d82176076133186d045de..503ab5d500c8ca2fe30f50601717979e752b9254 100644 (file)
@@ -11,14 +11,14 @@ static int parse_options_usage(struct parse_opt_ctx_t *ctx,
 #define OPT_SHORT 1
 #define OPT_UNSET 2
 
-static int optbug(const struct option *opt, const char *reason)
+int optbug(const struct option *opt, const char *reason)
 {
        if (opt->long_name)
                return error("BUG: option '%s' %s", opt->long_name, reason);
        return error("BUG: switch '%c' %s", opt->short_name, reason);
 }
 
-static int opterror(const struct option *opt, const char *reason, int flags)
+int opterror(const struct option *opt, const char *reason, int flags)
 {
        if (flags & OPT_SHORT)
                return error("switch `%c' %s", opt->short_name, reason);
@@ -583,107 +583,3 @@ static int parse_options_usage(struct parse_opt_ctx_t *ctx,
        return usage_with_options_internal(ctx, usagestr, opts, 0, err);
 }
 
-
-/*----- some often used options -----*/
-#include "cache.h"
-
-int parse_opt_abbrev_cb(const struct option *opt, const char *arg, int unset)
-{
-       int v;
-
-       if (!arg) {
-               v = unset ? 0 : DEFAULT_ABBREV;
-       } else {
-               v = strtol(arg, (char **)&arg, 10);
-               if (*arg)
-                       return opterror(opt, "expects a numerical value", 0);
-               if (v && v < MINIMUM_ABBREV)
-                       v = MINIMUM_ABBREV;
-               else if (v > 40)
-                       v = 40;
-       }
-       *(int *)(opt->value) = v;
-       return 0;
-}
-
-int parse_opt_approxidate_cb(const struct option *opt, const char *arg,
-                            int unset)
-{
-       *(unsigned long *)(opt->value) = approxidate(arg);
-       return 0;
-}
-
-int parse_opt_color_flag_cb(const struct option *opt, const char *arg,
-                           int unset)
-{
-       int value;
-
-       if (!arg)
-               arg = unset ? "never" : (const char *)opt->defval;
-       value = git_config_colorbool(NULL, arg, -1);
-       if (value < 0)
-               return opterror(opt,
-                       "expects \"always\", \"auto\", or \"never\"", 0);
-       *(int *)opt->value = value;
-       return 0;
-}
-
-int parse_opt_verbosity_cb(const struct option *opt, const char *arg,
-                          int unset)
-{
-       int *target = opt->value;
-
-       if (unset)
-               /* --no-quiet, --no-verbose */
-               *target = 0;
-       else if (opt->short_name == 'v') {
-               if (*target >= 0)
-                       (*target)++;
-               else
-                       *target = 1;
-       } else {
-               if (*target <= 0)
-                       (*target)--;
-               else
-                       *target = -1;
-       }
-       return 0;
-}
-
-int parse_opt_with_commit(const struct option *opt, const char *arg, int unset)
-{
-       unsigned char sha1[20];
-       struct commit *commit;
-
-       if (!arg)
-               return -1;
-       if (get_sha1(arg, sha1))
-               return error("malformed object name %s", arg);
-       commit = lookup_commit_reference(sha1);
-       if (!commit)
-               return error("no such commit %s", arg);
-       commit_list_insert(commit, opt->value);
-       return 0;
-}
-
-int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
-{
-       int *target = opt->value;
-       *target = unset ? 2 : 1;
-       return 0;
-}
-
-int parse_options_concat(struct option *dst, size_t dst_size, struct option *src)
-{
-       int i, j;
-
-       for (i = 0; i < dst_size; i++)
-               if (dst[i].type == OPTION_END)
-                       break;
-       for (j = 0; i < dst_size; i++, j++) {
-               dst[i] = src[j];
-               if (src[j].type == OPTION_END)
-                       return 0;
-       }
-       return -1;
-}
index d1b12fe979b34ac9bbf2ff637934b96f3878a53d..59e0b524bdcbe1c061f49b8e5f3c6365a9f2eba4 100644 (file)
@@ -130,6 +130,9 @@ struct option {
                                      (h), PARSE_OPT_NOARG, NULL, (p) }
 #define OPT_INTEGER(s, l, v, h)     { OPTION_INTEGER, (s), (l), (v), "n", (h) }
 #define OPT_STRING(s, l, v, a, h)   { OPTION_STRING,  (s), (l), (v), (a), (h) }
+#define OPT_STRING_LIST(s, l, v, a, h) \
+                                   { OPTION_CALLBACK, (s), (l), (v), (a), \
+                                     (h), 0, &parse_opt_string_list }
 #define OPT_UYN(s, l, v, h)         { OPTION_CALLBACK, (s), (l), (v), NULL, \
                                      (h), PARSE_OPT_NOARG, &parse_opt_tertiary }
 #define OPT_DATE(s, l, v, h) \
@@ -162,6 +165,8 @@ extern NORETURN void usage_msg_opt(const char *msg,
                                   const char * const *usagestr,
                                   const struct option *options);
 
+extern int optbug(const struct option *opt, const char *reason);
+extern int opterror(const struct option *opt, const char *reason, int flags);
 /*----- incremental advanced APIs -----*/
 
 enum {
@@ -204,6 +209,7 @@ extern int parse_opt_color_flag_cb(const struct option *, const char *, int);
 extern int parse_opt_verbosity_cb(const struct option *, const char *, int);
 extern int parse_opt_with_commit(const struct option *, const char *, int);
 extern int parse_opt_tertiary(const struct option *, const char *, int);
+extern int parse_opt_string_list(const struct option *, const char *, int);
 
 #define OPT__VERBOSE(var, h)  OPT_BOOLEAN('v', "verbose", (var), (h))
 #define OPT__QUIET(var, h)    OPT_BOOLEAN('q', "quiet",   (var), (h))
diff --git a/path.c b/path.c
index 4d73cc9cd26708b4cb41e86fb319b93f526cb2f2..6f3f5d56c0ed76f50d1aa37646d18ae280f1edbb 100644 (file)
--- a/path.c
+++ b/path.c
@@ -139,7 +139,7 @@ char *git_path_submodule(const char *path, const char *fmt, ...)
                strbuf_addch(&buf, '/');
        strbuf_addstr(&buf, ".git");
 
-       git_dir = read_gitfile_gently(buf.buf);
+       git_dir = read_gitfile(buf.buf);
        if (git_dir) {
                strbuf_reset(&buf);
                strbuf_addstr(&buf, git_dir);
diff --git a/quote.c b/quote.c
index 87bc65eed8bb2cc177fb39d73a193f0a037524c7..911229fdf3caffe29a87aea76f38dc50863797d4 100644 (file)
--- a/quote.c
+++ b/quote.c
@@ -342,8 +342,12 @@ static const char *path_relative(const char *in, int len,
 
        if (len < 0)
                len = strlen(in);
-       if (prefix && prefix_len < 0)
-               prefix_len = strlen(prefix);
+       if (prefix_len < 0) {
+               if (prefix)
+                       prefix_len = strlen(prefix);
+               else
+                       prefix_len = 0;
+       }
 
        off = 0;
        i = 0;
index 4ac9a037f478e769a69072c324a47876e298cae4..01a0e2505121f10544ee03948e545d07c24f366e 100644 (file)
@@ -726,11 +726,12 @@ static int verify_dotfile(const char *rest)
         * has already been discarded, we now test
         * the rest.
         */
-       switch (*rest) {
+
        /* "." is not allowed */
-       case '\0': case '/':
+       if (*rest == '\0' || is_dir_sep(*rest))
                return 0;
 
+       switch (*rest) {
        /*
         * ".git" followed by  NUL or slash is bad. This
         * shares the path end test with the ".." case.
@@ -743,7 +744,7 @@ static int verify_dotfile(const char *rest)
                rest += 2;
        /* fallthrough */
        case '.':
-               if (rest[1] == '\0' || rest[1] == '/')
+               if (rest[1] == '\0' || is_dir_sep(rest[1]))
                        return 0;
        }
        return 1;
@@ -753,23 +754,19 @@ int verify_path(const char *path)
 {
        char c;
 
+       if (has_dos_drive_prefix(path))
+               return 0;
+
        goto inside;
        for (;;) {
                if (!c)
                        return 1;
-               if (c == '/') {
+               if (is_dir_sep(c)) {
 inside:
                        c = *path++;
-                       switch (c) {
-                       default:
-                               continue;
-                       case '/': case '\0':
-                               break;
-                       case '.':
-                               if (verify_dotfile(path))
-                                       continue;
-                       }
-                       return 0;
+                       if ((c == '.' && !verify_dotfile(path)) ||
+                           is_dir_sep(c) || c == '\0')
+                               return 0;
                }
                c = *path++;
        }
@@ -1087,7 +1084,7 @@ static void show_file(const char * fmt, const char * name, int in_porcelain,
 {
        if (in_porcelain && *first && header_msg) {
                printf("%s\n", header_msg);
-               *first=0;
+               *first = 0;
        }
        printf(fmt, name);
 }
diff --git a/refs.c b/refs.c
index b10419a69815ef1c005915f3606c992659e60c77..a615043b34cd6d0507d8a30f7bd69445ec9f2456 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -451,7 +451,7 @@ int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *re
        memcpy(gitdir + len, "/.git", 6);
        len += 5;
 
-       tmp = read_gitfile_gently(gitdir);
+       tmp = read_gitfile(gitdir);
        if (tmp) {
                free(gitdir);
                len = strlen(tmp);
@@ -584,7 +584,7 @@ int read_ref(const char *ref, unsigned char *sha1)
 static int do_one_ref(const char *base, each_ref_fn fn, int trim,
                      int flags, void *cb_data, struct ref_list *entry)
 {
-       if (strncmp(base, entry->name, trim))
+       if (prefixcmp(entry->name, base))
                return 0;
 
        if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
@@ -728,12 +728,12 @@ int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
 
 int for_each_ref(each_ref_fn fn, void *cb_data)
 {
-       return do_for_each_ref(NULL, "refs/", fn, 0, 0, cb_data);
+       return do_for_each_ref(NULL, "", fn, 0, 0, cb_data);
 }
 
 int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
 {
-       return do_for_each_ref(submodule, "refs/", fn, 0, 0, cb_data);
+       return do_for_each_ref(submodule, "", fn, 0, 0, cb_data);
 }
 
 int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
@@ -782,6 +782,31 @@ int for_each_replace_ref(each_ref_fn fn, void *cb_data)
        return do_for_each_ref(NULL, "refs/replace/", fn, 13, 0, cb_data);
 }
 
+int head_ref_namespaced(each_ref_fn fn, void *cb_data)
+{
+       struct strbuf buf = STRBUF_INIT;
+       int ret = 0;
+       unsigned char sha1[20];
+       int flag;
+
+       strbuf_addf(&buf, "%sHEAD", get_git_namespace());
+       if (resolve_ref(buf.buf, sha1, 1, &flag))
+               ret = fn(buf.buf, sha1, flag, cb_data);
+       strbuf_release(&buf);
+
+       return ret;
+}
+
+int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
+{
+       struct strbuf buf = STRBUF_INIT;
+       int ret;
+       strbuf_addf(&buf, "%srefs/", get_git_namespace());
+       ret = do_for_each_ref(NULL, buf.buf, fn, 0, 0, cb_data);
+       strbuf_release(&buf);
+       return ret;
+}
+
 int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
        const char *prefix, void *cb_data)
 {
@@ -819,7 +844,7 @@ int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data)
 
 int for_each_rawref(each_ref_fn fn, void *cb_data)
 {
-       return do_for_each_ref(NULL, "refs/", fn, 0,
+       return do_for_each_ref(NULL, "", fn, 0,
                               DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
 }
 
@@ -837,7 +862,7 @@ int for_each_rawref(each_ref_fn fn, void *cb_data)
 
 static inline int bad_ref_char(int ch)
 {
-       if (((unsigned) ch) <= ' ' ||
+       if (((unsigned) ch) <= ' ' || ch == 0x7f ||
            ch == '~' || ch == '^' || ch == ':' || ch == '\\')
                return 1;
        /* 2.13 Pattern Matching Notation */
@@ -1826,6 +1851,12 @@ int update_ref(const char *action, const char *refname,
        return 0;
 }
 
+int ref_exists(char *refname)
+{
+       unsigned char sha1[20];
+       return !!resolve_ref(refname, sha1, 1, NULL);
+}
+
 struct ref *find_ref_by_name(const struct ref *list, const char *name)
 {
        for ( ; list; list = list->next)
diff --git a/refs.h b/refs.h
index 5e7a9a59f5f6b687d15eb37a11696433bdc8f15c..dfb086e93346aa3fb6f600b2ed07b5736716bf9c 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -36,6 +36,9 @@ extern int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, voi
 extern int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
 extern int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
 
+extern int head_ref_namespaced(each_ref_fn fn, void *cb_data);
+extern int for_each_namespaced_ref(each_ref_fn fn, void *cb_data);
+
 static inline const char *has_glob_specials(const char *pattern)
 {
        return strpbrk(pattern, "?*[");
@@ -54,6 +57,7 @@ extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refn
  */
 extern void add_extra_ref(const char *refname, const unsigned char *sha1, int flags);
 extern void clear_extra_refs(void);
+extern int ref_exists(char *);
 
 extern int peel_ref(const char *, unsigned char *);
 
index b5be25ce9623936e42566ca52b9e4d1d33153111..b8cf45a7dd439b83c80bcf7a397e1b8e34c70f67 100644 (file)
@@ -227,6 +227,8 @@ static struct ref *parse_info_refs(struct discovery *heads)
                if (data[i] == '\t')
                        mid = &data[i];
                if (data[i] == '\n') {
+                       if (mid - start != 40)
+                               die("%sinfo/refs not valid: is this a git repository?", url);
                        data[i] = 0;
                        ref_name = mid + 1;
                        ref = xmalloc(sizeof(struct ref) +
@@ -471,16 +473,12 @@ static int post_rpc(struct rpc_state *rpc)
                 * the transfer time.
                 */
                size_t size;
-               z_stream stream;
+               git_zstream stream;
                int ret;
 
                memset(&stream, 0, sizeof(stream));
-               ret = deflateInit2(&stream, Z_BEST_COMPRESSION,
-                               Z_DEFLATED, (15 + 16),
-                               8, Z_DEFAULT_STRATEGY);
-               if (ret != Z_OK)
-                       die("cannot deflate request; zlib init error %d", ret);
-               size = deflateBound(&stream, rpc->len);
+               git_deflate_init_gzip(&stream, Z_BEST_COMPRESSION);
+               size = git_deflate_bound(&stream, rpc->len);
                gzip_body = xmalloc(size);
 
                stream.next_in = (unsigned char *)rpc->buf;
@@ -488,11 +486,11 @@ static int post_rpc(struct rpc_state *rpc)
                stream.next_out = (unsigned char *)gzip_body;
                stream.avail_out = size;
 
-               ret = deflate(&stream, Z_FINISH);
+               ret = git_deflate(&stream, Z_FINISH);
                if (ret != Z_STREAM_END)
                        die("cannot deflate request; zlib deflate error %d", ret);
 
-               ret = deflateEnd(&stream);
+               ret = git_deflate_end_gently(&stream);
                if (ret != Z_OK)
                        die("cannot deflate request; zlib end error %d", ret);
 
@@ -857,7 +855,14 @@ int main(int argc, const char **argv)
        http_init(remote);
 
        do {
-               if (strbuf_getline(&buf, stdin, '\n') == EOF)
+               if (strbuf_getline(&buf, stdin, '\n') == EOF) {
+                       if (ferror(stdin))
+                               fprintf(stderr, "Error reading command stream\n");
+                       else
+                               fprintf(stderr, "Unexpected end of command stream\n");
+                       return 1;
+               }
+               if (buf.len == 0)
                        break;
                if (!prefixcmp(buf.buf, "fetch ")) {
                        if (nongit)
@@ -897,6 +902,7 @@ int main(int argc, const char **argv)
                        printf("\n");
                        fflush(stdout);
                } else {
+                       fprintf(stderr, "Unknown command '%s'\n", buf.buf);
                        return 1;
                }
                strbuf_reset(&buf);
index ca42a126ad04514f0ed8378768ebce98cfc5a659..b8ecfa5d9558e3824872ad6778fc344681cc8d5c 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -896,7 +896,7 @@ struct ref *alloc_ref(const char *name)
        return alloc_ref_with_prefix("", 0, name);
 }
 
-static struct ref *copy_ref(const struct ref *ref)
+struct ref *copy_ref(const struct ref *ref)
 {
        struct ref *cpy;
        size_t len;
@@ -1667,7 +1667,9 @@ struct ref *guess_remote_head(const struct ref *head,
 
        /* Look for another ref that points there */
        for (r = refs; r; r = r->next) {
-               if (r != head && !hashcmp(r->old_sha1, head->old_sha1)) {
+               if (r != head &&
+                   !prefixcmp(r->name, "refs/heads/") &&
+                   !hashcmp(r->old_sha1, head->old_sha1)) {
                        *tail = copy_ref(r);
                        tail = &((*tail)->next);
                        if (!all)
index 888d7c15de2eacc56d869a96d4463aefca7a7a06..9a30a9dba64825950f7a0448ad8b64581c0b8fae 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -70,7 +70,7 @@ struct refspec {
 extern const struct refspec *tag_refspec;
 
 struct ref *alloc_ref(const char *name);
-
+struct ref *copy_ref(const struct ref *ref);
 struct ref *copy_ref_list(const struct ref *ref);
 
 int check_ref_type(const struct ref *ref, int flags);
index c46cfaa3e4d2f06fd67ccd71c7ba47891796fd60..3d2deedaf8e50c6a71b9750dacf602bb5b9238b9 100644 (file)
@@ -729,12 +729,16 @@ static void limit_to_ancestry(struct commit_list *bottom, struct commit_list *li
  * to filter the result of "A..B" further to the ones that can actually
  * reach A.
  */
-static struct commit_list *collect_bottom_commits(struct commit_list *list)
+static struct commit_list *collect_bottom_commits(struct rev_info *revs)
 {
-       struct commit_list *elem, *bottom = NULL;
-       for (elem = list; elem; elem = elem->next)
-               if (elem->item->object.flags & UNINTERESTING)
-                       commit_list_insert(elem->item, &bottom);
+       struct commit_list *bottom = NULL;
+       int i;
+       for (i = 0; i < revs->cmdline.nr; i++) {
+               struct rev_cmdline_entry *elem = &revs->cmdline.rev[i];
+               if ((elem->flags & UNINTERESTING) &&
+                   elem->item->type == OBJ_COMMIT)
+                       commit_list_insert((struct commit *)elem->item, &bottom);
+       }
        return bottom;
 }
 
@@ -765,7 +769,7 @@ static int limit_list(struct rev_info *revs)
        struct commit_list *bottom = NULL;
 
        if (revs->ancestry_path) {
-               bottom = collect_bottom_commits(list);
+               bottom = collect_bottom_commits(revs);
                if (!bottom)
                        die("--ancestry-path given but there are no bottom commits");
        }
@@ -822,6 +826,23 @@ static int limit_list(struct rev_info *revs)
        return 0;
 }
 
+static void add_rev_cmdline(struct rev_info *revs,
+                           struct object *item,
+                           const char *name,
+                           int whence,
+                           unsigned flags)
+{
+       struct rev_cmdline_info *info = &revs->cmdline;
+       int nr = info->nr;
+
+       ALLOC_GROW(info->rev, nr + 1, info->alloc);
+       info->rev[nr].item = item;
+       info->rev[nr].name = name;
+       info->rev[nr].whence = whence;
+       info->rev[nr].flags = flags;
+       info->nr++;
+}
+
 struct all_refs_cb {
        int all_flags;
        int warned_bad_reflog;
@@ -834,6 +855,7 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, int flag,
        struct all_refs_cb *cb = cb_data;
        struct object *object = get_reference(cb->all_revs, path, sha1,
                                              cb->all_flags);
+       add_rev_cmdline(cb->all_revs, object, path, REV_CMD_REF, cb->all_flags);
        add_pending_object(cb->all_revs, object, path);
        return 0;
 }
@@ -860,6 +882,7 @@ static void handle_one_reflog_commit(unsigned char *sha1, void *cb_data)
                struct object *o = parse_object(sha1);
                if (o) {
                        o->flags |= cb->all_flags;
+                       /* ??? CMDLINEFLAGS ??? */
                        add_pending_object(cb->all_revs, o, "");
                }
                else if (!cb->warned_bad_reflog) {
@@ -896,12 +919,13 @@ static void handle_reflog(struct rev_info *revs, unsigned flags)
        for_each_reflog(handle_one_reflog, &cb);
 }
 
-static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
+static int add_parents_only(struct rev_info *revs, const char *arg_, int flags)
 {
        unsigned char sha1[20];
        struct object *it;
        struct commit *commit;
        struct commit_list *parents;
+       const char *arg = arg_;
 
        if (*arg == '^') {
                flags ^= UNINTERESTING;
@@ -925,6 +949,7 @@ static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
        for (parents = commit->parents; parents; parents = parents->next) {
                it = &parents->item->object;
                it->flags |= flags;
+               add_rev_cmdline(revs, it, arg_, REV_CMD_PARENTS_ONLY, flags);
                add_pending_object(revs, it, arg);
        }
        return 1;
@@ -986,10 +1011,12 @@ static void prepare_show_merge(struct rev_info *revs)
        const char **prune = NULL;
        int i, prune_num = 1; /* counting terminating NULL */
 
-       if (get_sha1("HEAD", sha1) || !(head = lookup_commit(sha1)))
+       if (get_sha1("HEAD", sha1))
                die("--merge without HEAD?");
-       if (get_sha1("MERGE_HEAD", sha1) || !(other = lookup_commit(sha1)))
+       head = lookup_commit_or_die(sha1, "HEAD");
+       if (get_sha1("MERGE_HEAD", sha1))
                die("--merge without MERGE_HEAD?");
+       other = lookup_commit_or_die(sha1, "MERGE_HEAD");
        add_pending_object(revs, &head->object, "HEAD");
        add_pending_object(revs, &other->object, "MERGE_HEAD");
        bases = get_merge_bases(head, other, 1);
@@ -1018,7 +1045,7 @@ static void prepare_show_merge(struct rev_info *revs)
        revs->limited = 1;
 }
 
-int handle_revision_arg(const char *arg, struct rev_info *revs,
+int handle_revision_arg(const char *arg_, struct rev_info *revs,
                        int flags,
                        int cant_be_filename)
 {
@@ -1027,6 +1054,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
        struct object *object;
        unsigned char sha1[20];
        int local_flags;
+       const char *arg = arg_;
 
        dotdot = strstr(arg, "..");
        if (dotdot) {
@@ -1035,6 +1063,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
                const char *this = arg;
                int symmetric = *next == '.';
                unsigned int flags_exclude = flags ^ UNINTERESTING;
+               unsigned int a_flags;
 
                *dotdot = 0;
                next += symmetric;
@@ -1069,10 +1098,15 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
                                add_pending_commit_list(revs, exclude,
                                                        flags_exclude);
                                free_commit_list(exclude);
-                               a->object.flags |= flags | SYMMETRIC_LEFT;
+                               a_flags = flags | SYMMETRIC_LEFT;
                        } else
-                               a->object.flags |= flags_exclude;
+                               a_flags = flags_exclude;
+                       a->object.flags |= a_flags;
                        b->object.flags |= flags;
+                       add_rev_cmdline(revs, &a->object, this,
+                                       REV_CMD_LEFT, a_flags);
+                       add_rev_cmdline(revs, &b->object, next,
+                                       REV_CMD_RIGHT, flags);
                        add_pending_object(revs, &a->object, this);
                        add_pending_object(revs, &b->object, next);
                        return 0;
@@ -1103,6 +1137,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
        if (!cant_be_filename)
                verify_non_filename(revs->prefix, arg);
        object = get_reference(revs, arg, sha1, flags ^ local_flags);
+       add_rev_cmdline(revs, object, arg_, REV_CMD_REV, flags ^ local_flags);
        add_pending_object_with_mode(revs, object, arg, mode);
        return 0;
 }
index 3d64adad18e2c889b7a7b7367f99c239f958dc70..93d31556cb44d3e4c5162d21c8e92d572a36508e 100644 (file)
@@ -24,6 +24,23 @@ struct rev_info;
 struct log_info;
 struct string_list;
 
+struct rev_cmdline_info {
+       unsigned int nr;
+       unsigned int alloc;
+       struct rev_cmdline_entry {
+               struct object *item;
+               const char *name;
+               enum {
+                       REV_CMD_REF,
+                       REV_CMD_PARENTS_ONLY,
+                       REV_CMD_LEFT,
+                       REV_CMD_RIGHT,
+                       REV_CMD_REV
+               } whence;
+               unsigned flags;
+       } *rev;
+};
+
 struct rev_info {
        /* Starting list */
        struct commit_list *commits;
@@ -32,6 +49,9 @@ struct rev_info {
        /* Parents of shown commits */
        struct object_array boundary_commits;
 
+       /* The end-points specified by the end user */
+       struct rev_cmdline_info cmdline;
+
        /* Basic information */
        const char *prefix;
        const char *def;
index 73e013ea3a003afa22ef769f0a5459c390c1d106..1c5104388429e50722769358d5d89948c55bb3aa 100644 (file)
@@ -78,16 +78,14 @@ static void notify_parent(void)
 
 static NORETURN void die_child(const char *err, va_list params)
 {
-       char msg[4096];
-       int len = vsnprintf(msg, sizeof(msg), err, params);
-       if (len > sizeof(msg))
-               len = sizeof(msg);
-
-       write_in_full(child_err, "fatal: ", 7);
-       write_in_full(child_err, msg, len);
-       write_in_full(child_err, "\n", 1);
+       vwritef(child_err, "fatal: ", err, params);
        exit(128);
 }
+
+static void error_child(const char *err, va_list params)
+{
+       vwritef(child_err, "error: ", err, params);
+}
 #endif
 
 static inline void set_cloexec(int fd)
@@ -128,9 +126,6 @@ static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure)
                if (code == 127) {
                        code = -1;
                        failed_errno = ENOENT;
-                       if (!silent_exec_failure)
-                               error("cannot run %s: %s", argv0,
-                                       strerror(ENOENT));
                }
        } else {
                error("waitpid is confused (%s)", argv0);
@@ -218,6 +213,7 @@ fail_pipe:
                        set_cloexec(child_err);
                }
                set_die_routine(die_child);
+               set_error_routine(error_child);
 
                close(notify_pipe[0]);
                set_cloexec(notify_pipe[1]);
@@ -284,14 +280,14 @@ fail_pipe:
                } else {
                        execvp(cmd->argv[0], (char *const*) cmd->argv);
                }
-               /*
-                * Do not check for cmd->silent_exec_failure; the parent
-                * process will check it when it sees this exit code.
-                */
-               if (errno == ENOENT)
+               if (errno == ENOENT) {
+                       if (!cmd->silent_exec_failure)
+                               error("cannot run %s: %s", cmd->argv[0],
+                                       strerror(ENOENT));
                        exit(127);
-               else
+               } else {
                        die_errno("cannot exec '%s'", cmd->argv[0]);
+               }
        }
        if (cmd->pid < 0)
                error("cannot fork() for %s: %s", cmd->argv[0],
diff --git a/setup.c b/setup.c
index ce87900ce3c68ace0f231827bdda0e9a65ef15b3..27c1d4787a2c2efd8420a225b43e36b76464ce42 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -40,34 +40,6 @@ char *prefix_path(const char *prefix, int len, const char *path)
        return sanitized;
 }
 
-/*
- * Unlike prefix_path, this should be used if the named file does
- * not have to interact with index entry; i.e. name of a random file
- * on the filesystem.
- */
-const char *prefix_filename(const char *pfx, int pfx_len, const char *arg)
-{
-       static char path[PATH_MAX];
-#ifndef WIN32
-       if (!pfx_len || is_absolute_path(arg))
-               return arg;
-       memcpy(path, pfx, pfx_len);
-       strcpy(path + pfx_len, arg);
-#else
-       char *p;
-       /* don't add prefix to absolute paths, but still replace '\' by '/' */
-       if (is_absolute_path(arg))
-               pfx_len = 0;
-       else if (pfx_len)
-               memcpy(path, pfx, pfx_len);
-       strcpy(path + pfx_len, arg);
-       for (p = path + pfx_len; *p; p++)
-               if (*p == '\\')
-                       *p = '/';
-#endif
-       return path;
-}
-
 int check_filename(const char *prefix, const char *arg)
 {
        const char *name;
@@ -264,6 +236,38 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
        return pathspec;
 }
 
+const char *pathspec_prefix(const char *prefix, const char **pathspec)
+{
+       const char **p, *n, *prev;
+       unsigned long max;
+
+       if (!pathspec)
+               return prefix ? xmemdupz(prefix, strlen(prefix)) : NULL;
+
+       prev = NULL;
+       max = PATH_MAX;
+       for (p = pathspec; (n = *p) != NULL; p++) {
+               int i, len = 0;
+               for (i = 0; i < max; i++) {
+                       char c = n[i];
+                       if (prev && prev[i] != c)
+                               break;
+                       if (!c || c == '*' || c == '?')
+                               break;
+                       if (c == '/')
+                               len = i+1;
+               }
+               prev = n;
+               if (len < max) {
+                       max = len;
+                       if (!max)
+                               break;
+               }
+       }
+
+       return max ? xmemdupz(prev, max) : NULL;
+}
+
 /*
  * Test if it looks like we're at a git directory.
  * We want to see:
@@ -375,7 +379,7 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
  * Try to read the location of the git directory from the .git file,
  * return path to git directory if found.
  */
-const char *read_gitfile_gently(const char *path)
+const char *read_gitfile(const char *path)
 {
        char *buf;
        char *dir;
@@ -437,7 +441,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,
        if (PATH_MAX - 40 < strlen(gitdirenv))
                die("'$%s' too big", GIT_DIR_ENVIRONMENT);
 
-       gitfile = (char*)read_gitfile_gently(gitdirenv);
+       gitfile = (char*)read_gitfile(gitdirenv);
        if (gitfile) {
                gitfile = xstrdup(gitfile);
                gitdirenv = gitfile;
@@ -661,7 +665,7 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
        if (one_filesystem)
                current_device = get_device_or_die(".", NULL);
        for (;;) {
-               gitfile = (char*)read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT);
+               gitfile = (char*)read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT);
                if (gitfile)
                        gitdirenv = gitfile = xstrdup(gitfile);
                else {
@@ -710,6 +714,11 @@ const char *setup_git_directory_gently(int *nongit_ok)
        const char *prefix;
 
        prefix = setup_git_directory_gently_1(nongit_ok);
+       if (prefix)
+               setenv("GIT_PREFIX", prefix, 1);
+       else
+               setenv("GIT_PREFIX", "", 1);
+
        if (startup_info) {
                startup_info->have_repository = !nongit_ok || !*nongit_ok;
                startup_info->prefix = prefix;
index 9d2e971a275ede8a1365b2a2737452a873586707..5ddd6886c85356164e81c477d3a170343bc28049 100644 (file)
@@ -73,6 +73,7 @@ main (int argc, char *argv[])
        {
        case 1:
          error ("we won't substitute all variables on stdin for you");
+         break;
          /*
          all_variables = 1;
       subst_from_stdin ();
index 064a33040812ba8782bf602c693abf08613d6ec7..e002056b85ce89c089dd09c1300461d60d81dffa 100644 (file)
@@ -380,7 +380,7 @@ void add_to_alternates_file(const char *reference)
 {
        struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
        int fd = hold_lock_file_for_append(lock, git_path("objects/info/alternates"), LOCK_DIE_ON_ERROR);
-       char *alt = mkpath("%s/objects\n", reference);
+       char *alt = mkpath("%s\n", reference);
        write_or_die(fd, alt, strlen(alt));
        if (commit_lock_file(lock))
                die("could not close alternates file");
@@ -834,7 +834,7 @@ static int in_window(struct pack_window *win, off_t offset)
 unsigned char *use_pack(struct packed_git *p,
                struct pack_window **w_cursor,
                off_t offset,
-               unsigned int *left)
+               unsigned long *left)
 {
        struct pack_window *win = *w_cursor;
 
@@ -1186,7 +1186,7 @@ static int open_sha1_file(const unsigned char *sha1)
        return -1;
 }
 
-static void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
+void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
 {
        void *map;
        int fd;
@@ -1205,20 +1205,49 @@ static void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
        return map;
 }
 
-static int legacy_loose_object(unsigned char *map)
+/*
+ * There used to be a second loose object header format which
+ * was meant to mimic the in-pack format, allowing for direct
+ * copy of the object data.  This format turned up not to be
+ * really worth it and we no longer write loose objects in that
+ * format.
+ */
+static int experimental_loose_object(unsigned char *map)
 {
        unsigned int word;
 
        /*
-        * Is it a zlib-compressed buffer? If so, the first byte
-        * must be 0x78 (15-bit window size, deflated), and the
-        * first 16-bit word is evenly divisible by 31
+        * We must determine if the buffer contains the standard
+        * zlib-deflated stream or the experimental format based
+        * on the in-pack object format. Compare the header byte
+        * for each format:
+        *
+        * RFC1950 zlib w/ deflate : 0www1000 : 0 <= www <= 7
+        * Experimental pack-based : Stttssss : ttt = 1,2,3,4
+        *
+        * If bit 7 is clear and bits 0-3 equal 8, the buffer MUST be
+        * in standard loose-object format, UNLESS it is a Git-pack
+        * format object *exactly* 8 bytes in size when inflated.
+        *
+        * However, RFC1950 also specifies that the 1st 16-bit word
+        * must be divisible by 31 - this checksum tells us our buffer
+        * is in the standard format, giving a false positive only if
+        * the 1st word of the Git-pack format object happens to be
+        * divisible by 31, ie:
+        *      ((byte0 * 256) + byte1) % 31 = 0
+        *   =>        0ttt10000www1000 % 31 = 0
+        *
+        * As it happens, this case can only arise for www=3 & ttt=1
+        * - ie, a Commit object, which would have to be 8 bytes in
+        * size. As no Commit can be that small, we find that the
+        * combination of these two criteria (bitmask & checksum)
+        * can always correctly determine the buffer format.
         */
        word = (map[0] << 8) + map[1];
-       if (map[0] == 0x78 && !(word % 31))
-               return 1;
-       else
+       if ((map[0] & 0x8F) == 0x08 && !(word % 31))
                return 0;
+       else
+               return 1;
 }
 
 unsigned long unpack_object_header_buffer(const unsigned char *buf,
@@ -1245,7 +1274,7 @@ unsigned long unpack_object_header_buffer(const unsigned char *buf,
        return used;
 }
 
-static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz)
+int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz)
 {
        unsigned long size, used;
        static const char valid_loose_object_type[8] = {
@@ -1262,37 +1291,32 @@ static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned lon
        stream->next_out = buffer;
        stream->avail_out = bufsiz;
 
-       if (legacy_loose_object(map)) {
-               git_inflate_init(stream);
-               return git_inflate(stream, 0);
-       }
-
+       if (experimental_loose_object(map)) {
+               /*
+                * The old experimental format we no longer produce;
+                * we can still read it.
+                */
+               used = unpack_object_header_buffer(map, mapsize, &type, &size);
+               if (!used || !valid_loose_object_type[type])
+                       return -1;
+               map += used;
+               mapsize -= used;
 
-       /*
-        * There used to be a second loose object header format which
-        * was meant to mimic the in-pack format, allowing for direct
-        * copy of the object data.  This format turned up not to be
-        * really worth it and we don't write it any longer.  But we
-        * can still read it.
-        */
-       used = unpack_object_header_buffer(map, mapsize, &type, &size);
-       if (!used || !valid_loose_object_type[type])
-               return -1;
-       map += used;
-       mapsize -= used;
+               /* Set up the stream for the rest.. */
+               stream->next_in = map;
+               stream->avail_in = mapsize;
+               git_inflate_init(stream);
 
-       /* Set up the stream for the rest.. */
-       stream->next_in = map;
-       stream->avail_in = mapsize;
+               /* And generate the fake traditional header */
+               stream->total_out = 1 + snprintf(buffer, bufsiz, "%s %lu",
+                                                typename(type), size);
+               return 0;
+       }
        git_inflate_init(stream);
-
-       /* And generate the fake traditional header */
-       stream->total_out = 1 + snprintf(buffer, bufsiz, "%s %lu",
-                                        typename(type), size);
-       return 0;
+       return git_inflate(stream, 0);
 }
 
-static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size, const unsigned char *sha1)
+static void *unpack_sha1_rest(git_zstream *stream, void *buffer, unsigned long size, const unsigned char *sha1)
 {
        int bytes = strlen(buffer) + 1;
        unsigned char *buf = xmallocz(size);
@@ -1342,7 +1366,7 @@ static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size
  * too permissive for what we want to check. So do an anal
  * object header parse by hand.
  */
-static int parse_sha1_header(const char *hdr, unsigned long *sizep)
+int parse_sha1_header(const char *hdr, unsigned long *sizep)
 {
        char type[10];
        int i;
@@ -1391,7 +1415,7 @@ static int parse_sha1_header(const char *hdr, unsigned long *sizep)
 static void *unpack_sha1_file(void *map, unsigned long mapsize, enum object_type *type, unsigned long *size, const unsigned char *sha1)
 {
        int ret;
-       z_stream stream;
+       git_zstream stream;
        char hdr[8192];
 
        ret = unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr));
@@ -1407,7 +1431,7 @@ unsigned long get_size_from_delta(struct packed_git *p,
 {
        const unsigned char *data;
        unsigned char delta_head[20], *in;
-       z_stream stream;
+       git_zstream stream;
        int st;
 
        memset(&stream, 0, sizeof(stream));
@@ -1481,7 +1505,7 @@ static off_t get_delta_base(struct packed_git *p,
 
 /* forward declaration for a mutually recursive function */
 static int packed_object_info(struct packed_git *p, off_t offset,
-                             unsigned long *sizep);
+                             unsigned long *sizep, int *rtype);
 
 static int packed_delta_info(struct packed_git *p,
                             struct pack_window **w_curs,
@@ -1495,7 +1519,7 @@ static int packed_delta_info(struct packed_git *p,
        base_offset = get_delta_base(p, w_curs, &curpos, type, obj_offset);
        if (!base_offset)
                return OBJ_BAD;
-       type = packed_object_info(p, base_offset, NULL);
+       type = packed_object_info(p, base_offset, NULL, NULL);
        if (type <= OBJ_NONE) {
                struct revindex_entry *revidx;
                const unsigned char *base_sha1;
@@ -1523,13 +1547,13 @@ static int packed_delta_info(struct packed_git *p,
        return type;
 }
 
-static int unpack_object_header(struct packed_git *p,
-                               struct pack_window **w_curs,
-                               off_t *curpos,
-                               unsigned long *sizep)
+int unpack_object_header(struct packed_git *p,
+                        struct pack_window **w_curs,
+                        off_t *curpos,
+                        unsigned long *sizep)
 {
        unsigned char *base;
-       unsigned int left;
+       unsigned long left;
        unsigned long used;
        enum object_type type;
 
@@ -1549,63 +1573,8 @@ static int unpack_object_header(struct packed_git *p,
        return type;
 }
 
-const char *packed_object_info_detail(struct packed_git *p,
-                                     off_t obj_offset,
-                                     unsigned long *size,
-                                     unsigned long *store_size,
-                                     unsigned int *delta_chain_length,
-                                     unsigned char *base_sha1)
-{
-       struct pack_window *w_curs = NULL;
-       off_t curpos;
-       unsigned long dummy;
-       unsigned char *next_sha1;
-       enum object_type type;
-       struct revindex_entry *revidx;
-
-       *delta_chain_length = 0;
-       curpos = obj_offset;
-       type = unpack_object_header(p, &w_curs, &curpos, size);
-
-       revidx = find_pack_revindex(p, obj_offset);
-       *store_size = revidx[1].offset - obj_offset;
-
-       for (;;) {
-               switch (type) {
-               default:
-                       die("pack %s contains unknown object type %d",
-                           p->pack_name, type);
-               case OBJ_COMMIT:
-               case OBJ_TREE:
-               case OBJ_BLOB:
-               case OBJ_TAG:
-                       unuse_pack(&w_curs);
-                       return typename(type);
-               case OBJ_OFS_DELTA:
-                       obj_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset);
-                       if (!obj_offset)
-                               die("pack %s contains bad delta base reference of type %s",
-                                   p->pack_name, typename(type));
-                       if (*delta_chain_length == 0) {
-                               revidx = find_pack_revindex(p, obj_offset);
-                               hashcpy(base_sha1, nth_packed_object_sha1(p, revidx->nr));
-                       }
-                       break;
-               case OBJ_REF_DELTA:
-                       next_sha1 = use_pack(p, &w_curs, curpos, NULL);
-                       if (*delta_chain_length == 0)
-                               hashcpy(base_sha1, next_sha1);
-                       obj_offset = find_pack_entry_one(next_sha1, p);
-                       break;
-               }
-               (*delta_chain_length)++;
-               curpos = obj_offset;
-               type = unpack_object_header(p, &w_curs, &curpos, &dummy);
-       }
-}
-
 static int packed_object_info(struct packed_git *p, off_t obj_offset,
-                             unsigned long *sizep)
+                             unsigned long *sizep, int *rtype)
 {
        struct pack_window *w_curs = NULL;
        unsigned long size;
@@ -1613,6 +1582,8 @@ static int packed_object_info(struct packed_git *p, off_t obj_offset,
        enum object_type type;
 
        type = unpack_object_header(p, &w_curs, &curpos, &size);
+       if (rtype)
+               *rtype = type; /* representation type */
 
        switch (type) {
        case OBJ_OFS_DELTA:
@@ -1642,7 +1613,7 @@ static void *unpack_compressed_entry(struct packed_git *p,
                                    unsigned long size)
 {
        int st;
-       z_stream stream;
+       git_zstream stream;
        unsigned char *buffer, *in;
 
        buffer = xmallocz(size);
@@ -1695,6 +1666,13 @@ static unsigned long pack_entry_hash(struct packed_git *p, off_t base_offset)
        return hash % MAX_DELTA_CACHE;
 }
 
+static int in_delta_base_cache(struct packed_git *p, off_t base_offset)
+{
+       unsigned long hash = pack_entry_hash(p, base_offset);
+       struct delta_base_cache_entry *ent = delta_base_cache + hash;
+       return (ent->data && ent->p == p && ent->base_offset == base_offset);
+}
+
 static void *cache_or_unpack_entry(struct packed_git *p, off_t base_offset,
        unsigned long *base_size, enum object_type *type, int keep_cache)
 {
@@ -1839,6 +1817,24 @@ static void *unpack_delta_entry(struct packed_git *p,
        return result;
 }
 
+static void write_pack_access_log(struct packed_git *p, off_t obj_offset)
+{
+       static FILE *log_file;
+
+       if (!log_file) {
+               log_file = fopen(log_pack_access, "w");
+               if (!log_file) {
+                       error("cannot open pack access log '%s' for writing: %s",
+                             log_pack_access, strerror(errno));
+                       log_pack_access = NULL;
+                       return;
+               }
+       }
+       fprintf(log_file, "%s %"PRIuMAX"\n",
+               p->pack_name, (uintmax_t)obj_offset);
+       fflush(log_file);
+}
+
 int do_check_packed_object_crc;
 
 void *unpack_entry(struct packed_git *p, off_t obj_offset,
@@ -1848,6 +1844,9 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset,
        off_t curpos = obj_offset;
        void *data;
 
+       if (log_pack_access)
+               write_pack_access_log(p, obj_offset);
+
        if (do_check_packed_object_crc && p->index_version > 1) {
                struct revindex_entry *revidx = find_pack_revindex(p, obj_offset);
                unsigned long len = revidx[1].offset - obj_offset;
@@ -2075,7 +2074,7 @@ static int sha1_loose_object_info(const unsigned char *sha1, unsigned long *size
        int status;
        unsigned long mapsize, size;
        void *map;
-       z_stream stream;
+       git_zstream stream;
        char hdr[32];
 
        map = map_sha1_file(sha1, &mapsize);
@@ -2093,24 +2092,28 @@ static int sha1_loose_object_info(const unsigned char *sha1, unsigned long *size
        return status;
 }
 
-int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)
+/* returns enum object_type or negative */
+int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi)
 {
        struct cached_object *co;
        struct pack_entry e;
-       int status;
+       int status, rtype;
 
        co = find_cached_object(sha1);
        if (co) {
-               if (sizep)
-                       *sizep = co->size;
+               if (oi->sizep)
+                       *(oi->sizep) = co->size;
+               oi->whence = OI_CACHED;
                return co->type;
        }
 
        if (!find_pack_entry(sha1, &e)) {
                /* Most likely it's a loose object. */
-               status = sha1_loose_object_info(sha1, sizep);
-               if (status >= 0)
+               status = sha1_loose_object_info(sha1, oi->sizep);
+               if (status >= 0) {
+                       oi->whence = OI_LOOSE;
                        return status;
+               }
 
                /* Not a loose object; someone else may have just packed it. */
                reprepare_packed_git();
@@ -2118,15 +2121,31 @@ int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)
                        return status;
        }
 
-       status = packed_object_info(e.p, e.offset, sizep);
+       status = packed_object_info(e.p, e.offset, oi->sizep, &rtype);
        if (status < 0) {
                mark_bad_packed_object(e.p, sha1);
-               status = sha1_object_info(sha1, sizep);
+               status = sha1_object_info_extended(sha1, oi);
+       } else if (in_delta_base_cache(e.p, e.offset)) {
+               oi->whence = OI_DBCACHED;
+       } else {
+               oi->whence = OI_PACKED;
+               oi->u.packed.offset = e.offset;
+               oi->u.packed.pack = e.p;
+               oi->u.packed.is_delta = (rtype == OBJ_REF_DELTA ||
+                                        rtype == OBJ_OFS_DELTA);
        }
 
        return status;
 }
 
+int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)
+{
+       struct object_info oi;
+
+       oi.sizep = sizep;
+       return sha1_object_info_extended(sha1, &oi);
+}
+
 static void *read_packed_sha1(const unsigned char *sha1,
                              enum object_type *type, unsigned long *size)
 {
@@ -2424,7 +2443,7 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
 {
        int fd, ret;
        unsigned char compressed[4096];
-       z_stream stream;
+       git_zstream stream;
        git_SHA_CTX c;
        unsigned char parano_sha1[20];
        char *filename;
@@ -2441,7 +2460,7 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
 
        /* Set it up */
        memset(&stream, 0, sizeof(stream));
-       deflateInit(&stream, zlib_compression_level);
+       git_deflate_init(&stream, zlib_compression_level);
        stream.next_out = compressed;
        stream.avail_out = sizeof(compressed);
        git_SHA1_Init(&c);
@@ -2449,8 +2468,8 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
        /* First header.. */
        stream.next_in = (unsigned char *)hdr;
        stream.avail_in = hdrlen;
-       while (deflate(&stream, 0) == Z_OK)
-               /* nothing */;
+       while (git_deflate(&stream, 0) == Z_OK)
+               ; /* nothing */
        git_SHA1_Update(&c, hdr, hdrlen);
 
        /* Then the data itself.. */
@@ -2458,7 +2477,7 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
        stream.avail_in = len;
        do {
                unsigned char *in0 = stream.next_in;
-               ret = deflate(&stream, Z_FINISH);
+               ret = git_deflate(&stream, Z_FINISH);
                git_SHA1_Update(&c, in0, stream.next_in - in0);
                if (write_buffer(fd, compressed, stream.next_out - compressed) < 0)
                        die("unable to write sha1 file");
@@ -2468,7 +2487,7 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
 
        if (ret != Z_STREAM_END)
                die("unable to deflate new object %s (%d)", sha1_to_hex(sha1), ret);
-       ret = deflateEnd(&stream);
+       ret = git_deflate_end_gently(&stream);
        if (ret != Z_OK)
                die("deflateEnd on object %s failed (%d)", sha1_to_hex(sha1), ret);
        git_SHA1_Final(parano_sha1, &c);
@@ -2704,7 +2723,7 @@ static int index_stream(unsigned char *sha1, int fd, size_t size,
        while (size) {
                char buf[10240];
                size_t sz = size < sizeof(buf) ? size : sizeof(buf);
-               size_t actual;
+               ssize_t actual;
 
                actual = read_in_full(fd, buf, sz);
                if (actual < 0)
index 09c43ae59a7d4715c26f13d72ca37bc759d1c76d..3ad2cc00160fbf24e1e4904bb37ce44e8c414ce5 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -30,10 +30,8 @@ void strbuf_init(struct strbuf *sb, size_t hint)
 {
        sb->alloc = sb->len = 0;
        sb->buf = strbuf_slopbuf;
-       if (hint) {
+       if (hint)
                strbuf_grow(sb, hint);
-               sb->buf[0] = '\0';
-       }
 }
 
 void strbuf_release(struct strbuf *sb)
@@ -65,12 +63,15 @@ void strbuf_attach(struct strbuf *sb, void *buf, size_t len, size_t alloc)
 
 void strbuf_grow(struct strbuf *sb, size_t extra)
 {
+       int new_buf = !sb->alloc;
        if (unsigned_add_overflows(extra, 1) ||
            unsigned_add_overflows(sb->len, extra + 1))
                die("you want to use way too much memory");
-       if (!sb->alloc)
+       if (new_buf)
                sb->buf = NULL;
        ALLOC_GROW(sb->buf, sb->len + extra + 1, sb->alloc);
+       if (new_buf)
+               sb->buf[0] = '\0';
 }
 
 void strbuf_trim(struct strbuf *sb)
@@ -103,24 +104,27 @@ void strbuf_ltrim(struct strbuf *sb)
        sb->buf[sb->len] = '\0';
 }
 
-struct strbuf **strbuf_split(const struct strbuf *sb, int delim)
+struct strbuf **strbuf_split_buf(const char *str, size_t slen, int delim, int max)
 {
        int alloc = 2, pos = 0;
-       char *n, *p;
+       const char *n, *p;
        struct strbuf **ret;
        struct strbuf *t;
 
        ret = xcalloc(alloc, sizeof(struct strbuf *));
-       p = n = sb->buf;
-       while (n < sb->buf + sb->len) {
+       p = n = str;
+       while (n < str + slen) {
                int len;
-               n = memchr(n, delim, sb->len - (n - sb->buf));
+               if (max <= 0 || pos + 1 < max)
+                       n = memchr(n, delim, slen - (n - str));
+               else
+                       n = NULL;
                if (pos + 1 >= alloc) {
                        alloc = alloc * 2;
                        ret = xrealloc(ret, sizeof(struct strbuf *) * alloc);
                }
                if (!n)
-                       n = sb->buf + sb->len - 1;
+                       n = str + slen - 1;
                len = n - p + 1;
                t = xmalloc(sizeof(struct strbuf));
                strbuf_init(t, len);
@@ -353,7 +357,6 @@ int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)
 {
        int ch;
 
-       strbuf_grow(sb, 0);
        if (feof(fp))
                return EOF;
 
index 9e6d9fa53fc04156452bf850af1a9b2d3ba830f2..46a33f8c46985c4377071011d6ea48d6d3fe5331 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -44,7 +44,22 @@ extern void strbuf_rtrim(struct strbuf *);
 extern void strbuf_ltrim(struct strbuf *);
 extern int strbuf_cmp(const struct strbuf *, const struct strbuf *);
 
-extern struct strbuf **strbuf_split(const struct strbuf *, int delim);
+extern struct strbuf **strbuf_split_buf(const char *, size_t,
+                                       int delim, int max);
+static inline struct strbuf **strbuf_split_str(const char *str,
+                                              int delim, int max)
+{
+       return strbuf_split_buf(str, strlen(str), delim, max);
+}
+static inline struct strbuf **strbuf_split_max(const struct strbuf *sb,
+                                               int delim, int max)
+{
+       return strbuf_split_buf(sb->buf, sb->len, delim, max);
+}
+static inline struct strbuf **strbuf_split(const struct strbuf *sb, int delim)
+{
+       return strbuf_split_max(sb, delim, 0);
+}
 extern void strbuf_list_free(struct strbuf **);
 
 /*----- add data in your buffer -----*/
diff --git a/streaming.c b/streaming.c
new file mode 100644 (file)
index 0000000..71072e1
--- /dev/null
@@ -0,0 +1,491 @@
+/*
+ * Copyright (c) 2011, Google Inc.
+ */
+#include "cache.h"
+#include "streaming.h"
+
+enum input_source {
+       stream_error = -1,
+       incore = 0,
+       loose = 1,
+       pack_non_delta = 2
+};
+
+typedef int (*open_istream_fn)(struct git_istream *,
+                              struct object_info *,
+                              const unsigned char *,
+                              enum object_type *);
+typedef int (*close_istream_fn)(struct git_istream *);
+typedef ssize_t (*read_istream_fn)(struct git_istream *, char *, size_t);
+
+struct stream_vtbl {
+       close_istream_fn close;
+       read_istream_fn read;
+};
+
+#define open_method_decl(name) \
+       int open_istream_ ##name \
+       (struct git_istream *st, struct object_info *oi, \
+        const unsigned char *sha1, \
+        enum object_type *type)
+
+#define close_method_decl(name) \
+       int close_istream_ ##name \
+       (struct git_istream *st)
+
+#define read_method_decl(name) \
+       ssize_t read_istream_ ##name \
+       (struct git_istream *st, char *buf, size_t sz)
+
+/* forward declaration */
+static open_method_decl(incore);
+static open_method_decl(loose);
+static open_method_decl(pack_non_delta);
+static struct git_istream *attach_stream_filter(struct git_istream *st,
+                                               struct stream_filter *filter);
+
+
+static open_istream_fn open_istream_tbl[] = {
+       open_istream_incore,
+       open_istream_loose,
+       open_istream_pack_non_delta,
+};
+
+#define FILTER_BUFFER (1024*16)
+
+struct filtered_istream {
+       struct git_istream *upstream;
+       struct stream_filter *filter;
+       char ibuf[FILTER_BUFFER];
+       char obuf[FILTER_BUFFER];
+       int i_end, i_ptr;
+       int o_end, o_ptr;
+       int input_finished;
+};
+
+struct git_istream {
+       const struct stream_vtbl *vtbl;
+       unsigned long size; /* inflated size of full object */
+       git_zstream z;
+       enum { z_unused, z_used, z_done, z_error } z_state;
+
+       union {
+               struct {
+                       char *buf; /* from read_object() */
+                       unsigned long read_ptr;
+               } incore;
+
+               struct {
+                       void *mapped;
+                       unsigned long mapsize;
+                       char hdr[32];
+                       int hdr_avail;
+                       int hdr_used;
+               } loose;
+
+               struct {
+                       struct packed_git *pack;
+                       off_t pos;
+               } in_pack;
+
+               struct filtered_istream filtered;
+       } u;
+};
+
+int close_istream(struct git_istream *st)
+{
+       int r = st->vtbl->close(st);
+       free(st);
+       return r;
+}
+
+ssize_t read_istream(struct git_istream *st, char *buf, size_t sz)
+{
+       return st->vtbl->read(st, buf, sz);
+}
+
+static enum input_source istream_source(const unsigned char *sha1,
+                                       enum object_type *type,
+                                       struct object_info *oi)
+{
+       unsigned long size;
+       int status;
+
+       oi->sizep = &size;
+       status = sha1_object_info_extended(sha1, oi);
+       if (status < 0)
+               return stream_error;
+       *type = status;
+
+       switch (oi->whence) {
+       case OI_LOOSE:
+               return loose;
+       case OI_PACKED:
+               if (!oi->u.packed.is_delta && big_file_threshold <= size)
+                       return pack_non_delta;
+               /* fallthru */
+       default:
+               return incore;
+       }
+}
+
+struct git_istream *open_istream(const unsigned char *sha1,
+                                enum object_type *type,
+                                unsigned long *size,
+                                struct stream_filter *filter)
+{
+       struct git_istream *st;
+       struct object_info oi;
+       const unsigned char *real = lookup_replace_object(sha1);
+       enum input_source src = istream_source(real, type, &oi);
+
+       if (src < 0)
+               return NULL;
+
+       st = xmalloc(sizeof(*st));
+       if (open_istream_tbl[src](st, &oi, real, type)) {
+               if (open_istream_incore(st, &oi, real, type)) {
+                       free(st);
+                       return NULL;
+               }
+       }
+       if (st && filter) {
+               /* Add "&& !is_null_stream_filter(filter)" for performance */
+               struct git_istream *nst = attach_stream_filter(st, filter);
+               if (!nst)
+                       close_istream(st);
+               st = nst;
+       }
+
+       *size = st->size;
+       return st;
+}
+
+
+/*****************************************************************
+ *
+ * Common helpers
+ *
+ *****************************************************************/
+
+static void close_deflated_stream(struct git_istream *st)
+{
+       if (st->z_state == z_used)
+               git_inflate_end(&st->z);
+}
+
+
+/*****************************************************************
+ *
+ * Filtered stream
+ *
+ *****************************************************************/
+
+static close_method_decl(filtered)
+{
+       free_stream_filter(st->u.filtered.filter);
+       return close_istream(st->u.filtered.upstream);
+}
+
+static read_method_decl(filtered)
+{
+       struct filtered_istream *fs = &(st->u.filtered);
+       size_t filled = 0;
+
+       while (sz) {
+               /* do we already have filtered output? */
+               if (fs->o_ptr < fs->o_end) {
+                       size_t to_move = fs->o_end - fs->o_ptr;
+                       if (sz < to_move)
+                               to_move = sz;
+                       memcpy(buf + filled, fs->obuf + fs->o_ptr, to_move);
+                       fs->o_ptr += to_move;
+                       sz -= to_move;
+                       filled += to_move;
+                       continue;
+               }
+               fs->o_end = fs->o_ptr = 0;
+
+               /* do we have anything to feed the filter with? */
+               if (fs->i_ptr < fs->i_end) {
+                       size_t to_feed = fs->i_end - fs->i_ptr;
+                       size_t to_receive = FILTER_BUFFER;
+                       if (stream_filter(fs->filter,
+                                         fs->ibuf + fs->i_ptr, &to_feed,
+                                         fs->obuf, &to_receive))
+                               return -1;
+                       fs->i_ptr = fs->i_end - to_feed;
+                       fs->o_end = FILTER_BUFFER - to_receive;
+                       continue;
+               }
+
+               /* tell the filter to drain upon no more input */
+               if (fs->input_finished) {
+                       size_t to_receive = FILTER_BUFFER;
+                       if (stream_filter(fs->filter,
+                                         NULL, NULL,
+                                         fs->obuf, &to_receive))
+                               return -1;
+                       fs->o_end = FILTER_BUFFER - to_receive;
+                       if (!fs->o_end)
+                               break;
+                       continue;
+               }
+               fs->i_end = fs->i_ptr = 0;
+
+               /* refill the input from the upstream */
+               if (!fs->input_finished) {
+                       fs->i_end = read_istream(fs->upstream, fs->ibuf, FILTER_BUFFER);
+                       if (fs->i_end < 0)
+                               break;
+                       if (fs->i_end)
+                               continue;
+               }
+               fs->input_finished = 1;
+       }
+       return filled;
+}
+
+static struct stream_vtbl filtered_vtbl = {
+       close_istream_filtered,
+       read_istream_filtered,
+};
+
+static struct git_istream *attach_stream_filter(struct git_istream *st,
+                                               struct stream_filter *filter)
+{
+       struct git_istream *ifs = xmalloc(sizeof(*ifs));
+       struct filtered_istream *fs = &(ifs->u.filtered);
+
+       ifs->vtbl = &filtered_vtbl;
+       fs->upstream = st;
+       fs->filter = filter;
+       fs->i_end = fs->i_ptr = 0;
+       fs->o_end = fs->o_ptr = 0;
+       fs->input_finished = 0;
+       ifs->size = -1; /* unknown */
+       return ifs;
+}
+
+/*****************************************************************
+ *
+ * Loose object stream
+ *
+ *****************************************************************/
+
+static read_method_decl(loose)
+{
+       size_t total_read = 0;
+
+       switch (st->z_state) {
+       case z_done:
+               return 0;
+       case z_error:
+               return -1;
+       default:
+               break;
+       }
+
+       if (st->u.loose.hdr_used < st->u.loose.hdr_avail) {
+               size_t to_copy = st->u.loose.hdr_avail - st->u.loose.hdr_used;
+               if (sz < to_copy)
+                       to_copy = sz;
+               memcpy(buf, st->u.loose.hdr + st->u.loose.hdr_used, to_copy);
+               st->u.loose.hdr_used += to_copy;
+               total_read += to_copy;
+       }
+
+       while (total_read < sz) {
+               int status;
+
+               st->z.next_out = (unsigned char *)buf + total_read;
+               st->z.avail_out = sz - total_read;
+               status = git_inflate(&st->z, Z_FINISH);
+
+               total_read = st->z.next_out - (unsigned char *)buf;
+
+               if (status == Z_STREAM_END) {
+                       git_inflate_end(&st->z);
+                       st->z_state = z_done;
+                       break;
+               }
+               if (status != Z_OK && status != Z_BUF_ERROR) {
+                       git_inflate_end(&st->z);
+                       st->z_state = z_error;
+                       return -1;
+               }
+       }
+       return total_read;
+}
+
+static close_method_decl(loose)
+{
+       close_deflated_stream(st);
+       munmap(st->u.loose.mapped, st->u.loose.mapsize);
+       return 0;
+}
+
+static struct stream_vtbl loose_vtbl = {
+       close_istream_loose,
+       read_istream_loose,
+};
+
+static open_method_decl(loose)
+{
+       st->u.loose.mapped = map_sha1_file(sha1, &st->u.loose.mapsize);
+       if (!st->u.loose.mapped)
+               return -1;
+       if (unpack_sha1_header(&st->z,
+                              st->u.loose.mapped,
+                              st->u.loose.mapsize,
+                              st->u.loose.hdr,
+                              sizeof(st->u.loose.hdr)) < 0) {
+               git_inflate_end(&st->z);
+               munmap(st->u.loose.mapped, st->u.loose.mapsize);
+               return -1;
+       }
+
+       parse_sha1_header(st->u.loose.hdr, &st->size);
+       st->u.loose.hdr_used = strlen(st->u.loose.hdr) + 1;
+       st->u.loose.hdr_avail = st->z.total_out;
+       st->z_state = z_used;
+
+       st->vtbl = &loose_vtbl;
+       return 0;
+}
+
+
+/*****************************************************************
+ *
+ * Non-delta packed object stream
+ *
+ *****************************************************************/
+
+static read_method_decl(pack_non_delta)
+{
+       size_t total_read = 0;
+
+       switch (st->z_state) {
+       case z_unused:
+               memset(&st->z, 0, sizeof(st->z));
+               git_inflate_init(&st->z);
+               st->z_state = z_used;
+               break;
+       case z_done:
+               return 0;
+       case z_error:
+               return -1;
+       case z_used:
+               break;
+       }
+
+       while (total_read < sz) {
+               int status;
+               struct pack_window *window = NULL;
+               unsigned char *mapped;
+
+               mapped = use_pack(st->u.in_pack.pack, &window,
+                                 st->u.in_pack.pos, &st->z.avail_in);
+
+               st->z.next_out = (unsigned char *)buf + total_read;
+               st->z.avail_out = sz - total_read;
+               st->z.next_in = mapped;
+               status = git_inflate(&st->z, Z_FINISH);
+
+               st->u.in_pack.pos += st->z.next_in - mapped;
+               total_read = st->z.next_out - (unsigned char *)buf;
+               unuse_pack(&window);
+
+               if (status == Z_STREAM_END) {
+                       git_inflate_end(&st->z);
+                       st->z_state = z_done;
+                       break;
+               }
+               if (status != Z_OK && status != Z_BUF_ERROR) {
+                       git_inflate_end(&st->z);
+                       st->z_state = z_error;
+                       return -1;
+               }
+       }
+       return total_read;
+}
+
+static close_method_decl(pack_non_delta)
+{
+       close_deflated_stream(st);
+       return 0;
+}
+
+static struct stream_vtbl pack_non_delta_vtbl = {
+       close_istream_pack_non_delta,
+       read_istream_pack_non_delta,
+};
+
+static open_method_decl(pack_non_delta)
+{
+       struct pack_window *window;
+       enum object_type in_pack_type;
+
+       st->u.in_pack.pack = oi->u.packed.pack;
+       st->u.in_pack.pos = oi->u.packed.offset;
+       window = NULL;
+
+       in_pack_type = unpack_object_header(st->u.in_pack.pack,
+                                           &window,
+                                           &st->u.in_pack.pos,
+                                           &st->size);
+       unuse_pack(&window);
+       switch (in_pack_type) {
+       default:
+               return -1; /* we do not do deltas for now */
+       case OBJ_COMMIT:
+       case OBJ_TREE:
+       case OBJ_BLOB:
+       case OBJ_TAG:
+               break;
+       }
+       st->z_state = z_unused;
+       st->vtbl = &pack_non_delta_vtbl;
+       return 0;
+}
+
+
+/*****************************************************************
+ *
+ * In-core stream
+ *
+ *****************************************************************/
+
+static close_method_decl(incore)
+{
+       free(st->u.incore.buf);
+       return 0;
+}
+
+static read_method_decl(incore)
+{
+       size_t read_size = sz;
+       size_t remainder = st->size - st->u.incore.read_ptr;
+
+       if (remainder <= read_size)
+               read_size = remainder;
+       if (read_size) {
+               memcpy(buf, st->u.incore.buf + st->u.incore.read_ptr, read_size);
+               st->u.incore.read_ptr += read_size;
+       }
+       return read_size;
+}
+
+static struct stream_vtbl incore_vtbl = {
+       close_istream_incore,
+       read_istream_incore,
+};
+
+static open_method_decl(incore)
+{
+       st->u.incore.buf = read_sha1_file_extended(sha1, type, &st->size, 0);
+       st->u.incore.read_ptr = 0;
+       st->vtbl = &incore_vtbl;
+
+       return st->u.incore.buf ? 0 : -1;
+}
diff --git a/streaming.h b/streaming.h
new file mode 100644 (file)
index 0000000..589e857
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2011, Google Inc.
+ */
+#ifndef STREAMING_H
+#define STREAMING_H 1
+#include "cache.h"
+
+/* opaque */
+struct git_istream;
+
+extern struct git_istream *open_istream(const unsigned char *, enum object_type *, unsigned long *, struct stream_filter *);
+extern int close_istream(struct git_istream *);
+extern ssize_t read_istream(struct git_istream *, char *, size_t);
+
+#endif /* STREAMING_H */
index 51681189e8380cc43ed26d35526a6dff78f1a24c..d9810aba421cbbc844b45ea5ca713a480a699eb2 100644 (file)
@@ -185,3 +185,12 @@ int unsorted_string_list_has_string(struct string_list *list,
        return unsorted_string_list_lookup(list, string) != NULL;
 }
 
+void unsorted_string_list_delete_item(struct string_list *list, int i, int free_util)
+{
+       if (list->strdup_strings)
+               free(list->items[i].string);
+       if (free_util)
+               free(list->items[i].util);
+       list->items[i] = list->items[list->nr-1];
+       list->nr--;
+}
index bda69839832d2030c15bbd86c4d8d79aa3cc4785..0684cb73bfd27846182479a4e192d682a44f201a 100644 (file)
@@ -44,4 +44,5 @@ void sort_string_list(struct string_list *list);
 int unsorted_string_list_has_string(struct string_list *list, const char *string);
 struct string_list_item *unsorted_string_list_lookup(struct string_list *list,
                                                     const char *string);
+void unsorted_string_list_delete_item(struct string_list *list, int i, int free_util);
 #endif /* STRING_LIST_H */
index 6306737eddd78e51848f0dccbf18efa0843d71b7..0b709bc2914335853e7525076f5e1d026d5dd779 100644 (file)
@@ -38,7 +38,7 @@ static int add_submodule_odb(const char *path)
        const char *git_dir;
 
        strbuf_addf(&objects_directory, "%s/.git", path);
-       git_dir = read_gitfile_gently(objects_directory.buf);
+       git_dir = read_gitfile(objects_directory.buf);
        if (git_dir) {
                strbuf_reset(&objects_directory);
                strbuf_addstr(&objects_directory, git_dir);
@@ -314,6 +314,114 @@ void set_config_fetch_recurse_submodules(int value)
        config_fetch_recurse_submodules = value;
 }
 
+static int has_remote(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
+{
+       return 1;
+}
+
+static int submodule_needs_pushing(const char *path, const unsigned char sha1[20])
+{
+       if (add_submodule_odb(path) || !lookup_commit_reference(sha1))
+               return 0;
+
+       if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) {
+               struct child_process cp;
+               const char *argv[] = {"rev-list", NULL, "--not", "--remotes", "-n", "1" , NULL};
+               struct strbuf buf = STRBUF_INIT;
+               int needs_pushing = 0;
+
+               argv[1] = sha1_to_hex(sha1);
+               memset(&cp, 0, sizeof(cp));
+               cp.argv = argv;
+               cp.env = local_repo_env;
+               cp.git_cmd = 1;
+               cp.no_stdin = 1;
+               cp.out = -1;
+               cp.dir = path;
+               if (start_command(&cp))
+                       die("Could not run 'git rev-list %s --not --remotes -n 1' command in submodule %s",
+                               sha1_to_hex(sha1), path);
+               if (strbuf_read(&buf, cp.out, 41))
+                       needs_pushing = 1;
+               finish_command(&cp);
+               close(cp.out);
+               strbuf_release(&buf);
+               return needs_pushing;
+       }
+
+       return 0;
+}
+
+static void collect_submodules_from_diff(struct diff_queue_struct *q,
+                                        struct diff_options *options,
+                                        void *data)
+{
+       int i;
+       int *needs_pushing = data;
+
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               if (!S_ISGITLINK(p->two->mode))
+                       continue;
+               if (submodule_needs_pushing(p->two->path, p->two->sha1)) {
+                       *needs_pushing = 1;
+                       break;
+               }
+       }
+}
+
+
+static void commit_need_pushing(struct commit *commit, struct commit_list *parent, int *needs_pushing)
+{
+       const unsigned char (*parents)[20];
+       unsigned int i, n;
+       struct rev_info rev;
+
+       n = commit_list_count(parent);
+       parents = xmalloc(n * sizeof(*parents));
+
+       for (i = 0; i < n; i++) {
+               hashcpy((unsigned char *)(parents + i), parent->item->object.sha1);
+               parent = parent->next;
+       }
+
+       init_revisions(&rev, NULL);
+       rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
+       rev.diffopt.format_callback = collect_submodules_from_diff;
+       rev.diffopt.format_callback_data = needs_pushing;
+       diff_tree_combined(commit->object.sha1, parents, n, 1, &rev);
+
+       free(parents);
+}
+
+int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remotes_name)
+{
+       struct rev_info rev;
+       struct commit *commit;
+       const char *argv[] = {NULL, NULL, "--not", "NULL", NULL};
+       int argc = ARRAY_SIZE(argv) - 1;
+       char *sha1_copy;
+       int needs_pushing = 0;
+       struct strbuf remotes_arg = STRBUF_INIT;
+
+       strbuf_addf(&remotes_arg, "--remotes=%s", remotes_name);
+       init_revisions(&rev, NULL);
+       sha1_copy = xstrdup(sha1_to_hex(new_sha1));
+       argv[1] = sha1_copy;
+       argv[3] = remotes_arg.buf;
+       setup_revisions(argc, argv, &rev, NULL);
+       if (prepare_revision_walk(&rev))
+               die("revision walk setup failed");
+
+       while ((commit = get_revision(&rev)) && !needs_pushing)
+               commit_need_pushing(commit, commit->parents, &needs_pushing);
+
+       free(sha1_copy);
+       strbuf_release(&remotes_arg);
+
+       return needs_pushing;
+}
+
 static int is_submodule_commit_present(const char *path, unsigned char sha1[20])
 {
        int is_present = 0;
@@ -400,6 +508,10 @@ static void calculate_changed_submodule_paths(void)
        struct commit *commit;
        struct argv_array argv = ARGV_ARRAY_INIT;
 
+       /* No need to check if there are no submodules configured */
+       if (!config_name_for_path.nr)
+               return;
+
        init_revisions(&rev, NULL);
        argv_array_push(&argv, "--"); /* argv[0] program name */
        sha1_array_for_each_unique(&ref_tips_after_fetch,
@@ -420,6 +532,7 @@ static void calculate_changed_submodule_paths(void)
                while (parent) {
                        struct diff_options diff_opts;
                        diff_setup(&diff_opts);
+                       DIFF_OPT_SET(&diff_opts, RECURSIVE);
                        diff_opts.output_format |= DIFF_FORMAT_CALLBACK;
                        diff_opts.format_callback = submodule_collect_changed_cb;
                        if (diff_setup_done(&diff_opts) < 0)
@@ -516,7 +629,7 @@ int fetch_populated_submodules(int num_options, const char **options,
                strbuf_addf(&submodule_path, "%s/%s", work_tree, ce->name);
                strbuf_addf(&submodule_git_dir, "%s/.git", submodule_path.buf);
                strbuf_addf(&submodule_prefix, "%s%s/", prefix, ce->name);
-               git_dir = read_gitfile_gently(submodule_git_dir.buf);
+               git_dir = read_gitfile(submodule_git_dir.buf);
                if (!git_dir)
                        git_dir = submodule_git_dir.buf;
                if (is_directory(git_dir)) {
@@ -554,7 +667,7 @@ unsigned is_submodule_modified(const char *path, int ignore_untracked)
        const char *git_dir;
 
        strbuf_addf(&buf, "%s/.git", path);
-       git_dir = read_gitfile_gently(buf.buf);
+       git_dir = read_gitfile(buf.buf);
        if (!git_dir)
                git_dir = buf.buf;
        if (!is_directory(git_dir)) {
index 5350b0d5a0d16e12e96b983e6510cb1d4098f962..799c22d6c6a459756983420960bc099da20336b3 100644 (file)
@@ -29,5 +29,6 @@ int fetch_populated_submodules(int num_options, const char **options,
 unsigned is_submodule_modified(const char *path, int ignore_untracked);
 int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],
                    const unsigned char a[20], const unsigned char b[20]);
+int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remotes_name);
 
 #endif
index d206b7c4cfa4f7d61d151cebc35a0f296fbe4c6b..7913e206ed6b73d16779e91d6a9197e602626c57 100755 (executable)
@@ -1,5 +1,6 @@
 #!/bin/sh
 
+failed_tests=
 fixed=0
 success=0
 failed=0
@@ -18,7 +19,13 @@ do
                success)
                        success=$(($success + $value)) ;;
                failed)
-                       failed=$(($failed + $value)) ;;
+                       failed=$(($failed + $value))
+                       if test $value != 0
+                       then
+                               testnum=$(expr "$file" : 'test-results/\(t[0-9]*\)-')
+                               failed_tests="$failed_tests $testnum"
+                       fi
+                       ;;
                broken)
                        broken=$(($broken + $value)) ;;
                total)
@@ -27,6 +34,11 @@ do
        done <"$file"
 done
 
+if test -n "$failed_tests"
+then
+       printf "\nfailed test(s):$failed_tests\n\n"
+fi
+
 printf "%-8s%d\n" fixed $fixed
 printf "%-8s%d\n" success $success
 printf "%-8s%d\n" failed $failed
index 143eb1f24092df070c5ddf75340bebb319a0c746..292753f77c4daf5f3cb55de1c28269b13455544f 100644 (file)
@@ -107,4 +107,9 @@ perl -MEncode -e '$e="";decode_utf8($e, Encode::FB_CROAK)' >/dev/null 2>&1 || {
        test_done
 }
 
+perl -MCGI -MCGI::Util -MCGI::Carp -e 0 >/dev/null 2>&1 || {
+       skip_all='skipping gitweb tests, CGI module unusable'
+       test_done
+}
+
 gitweb_init
diff --git a/t/lib-diff-alternative.sh b/t/lib-diff-alternative.sh
new file mode 100644 (file)
index 0000000..75ffd91
--- /dev/null
@@ -0,0 +1,165 @@
+#!/bin/sh
+
+test_diff_frobnitz() {
+       cat >file1 <<\EOF
+#include <stdio.h>
+
+// Frobs foo heartily
+int frobnitz(int foo)
+{
+    int i;
+    for(i = 0; i < 10; i++)
+    {
+        printf("Your answer is: ");
+        printf("%d\n", foo);
+    }
+}
+
+int fact(int n)
+{
+    if(n > 1)
+    {
+        return fact(n-1) * n;
+    }
+    return 1;
+}
+
+int main(int argc, char **argv)
+{
+    frobnitz(fact(10));
+}
+EOF
+
+       cat >file2 <<\EOF
+#include <stdio.h>
+
+int fib(int n)
+{
+    if(n > 2)
+    {
+        return fib(n-1) + fib(n-2);
+    }
+    return 1;
+}
+
+// Frobs foo heartily
+int frobnitz(int foo)
+{
+    int i;
+    for(i = 0; i < 10; i++)
+    {
+        printf("%d\n", foo);
+    }
+}
+
+int main(int argc, char **argv)
+{
+    frobnitz(fib(10));
+}
+EOF
+
+       cat >expect <<\EOF
+diff --git a/file1 b/file2
+index 6faa5a3..e3af329 100644
+--- a/file1
++++ b/file2
+@@ -1,26 +1,25 @@
+ #include <stdio.h>
++int fib(int n)
++{
++    if(n > 2)
++    {
++        return fib(n-1) + fib(n-2);
++    }
++    return 1;
++}
++
+ // Frobs foo heartily
+ int frobnitz(int foo)
+ {
+     int i;
+     for(i = 0; i < 10; i++)
+     {
+-        printf("Your answer is: ");
+         printf("%d\n", foo);
+     }
+ }
+-int fact(int n)
+-{
+-    if(n > 1)
+-    {
+-        return fact(n-1) * n;
+-    }
+-    return 1;
+-}
+-
+ int main(int argc, char **argv)
+ {
+-    frobnitz(fact(10));
++    frobnitz(fib(10));
+ }
+EOF
+
+       STRATEGY=$1
+
+       test_expect_success "$STRATEGY diff" '
+               test_must_fail git diff --no-index "--$STRATEGY" file1 file2 > output &&
+               test_cmp expect output
+       '
+
+       test_expect_success "$STRATEGY diff output is valid" '
+               mv file2 expect &&
+               git apply < output &&
+               test_cmp expect file2
+       '
+}
+
+test_diff_unique() {
+       cat >uniq1 <<\EOF
+1
+2
+3
+4
+5
+6
+EOF
+
+       cat >uniq2 <<\EOF
+a
+b
+c
+d
+e
+f
+EOF
+
+       cat >expect <<\EOF
+diff --git a/uniq1 b/uniq2
+index b414108..0fdf397 100644
+--- a/uniq1
++++ b/uniq2
+@@ -1,6 +1,6 @@
+-1
+-2
+-3
+-4
+-5
+-6
++a
++b
++c
++d
++e
++f
+EOF
+
+       STRATEGY=$1
+
+       test_expect_success 'completely different files' '
+               test_must_fail git diff --no-index "--$STRATEGY" uniq1 uniq2 > output &&
+               test_cmp expect output
+       '
+}
+
index ebbc7554a7d4dce4c2ed33a79f5d4feb3a520f05..ae2f1da28fa55b92338d7dbcb6ef851b6ec40118 100755 (executable)
@@ -9,16 +9,17 @@ attr_check () {
        path="$1"
        expect="$2"
 
-       git check-attr test -- "$path" >actual &&
+       git check-attr test -- "$path" >actual 2>err &&
        echo "$path: test: $2" >expect &&
-       test_cmp expect actual
+       test_cmp expect actual &&
+       test_line_count = 0 err
 
 }
 
 
 test_expect_success 'setup' '
 
-       mkdir -p a/b/d a/c &&
+       mkdir -p a/b/d a/c &&
        (
                echo "[attr]notest !test"
                echo "f test=f"
@@ -35,10 +36,42 @@ test_expect_success 'setup' '
                echo "h test=a/b/h" &&
                echo "d/* test=a/b/d/*"
                echo "d/yes notest"
-       ) >a/b/.gitattributes
+       ) >a/b/.gitattributes &&
        (
                echo "global test=global"
-       ) >"$HOME"/global-gitattributes
+       ) >"$HOME"/global-gitattributes &&
+       cat <<EOF >expect-all
+f: test: f
+a/f: test: f
+a/c/f: test: f
+a/g: test: a/g
+a/b/g: test: a/b/g
+b/g: test: unspecified
+a/b/h: test: a/b/h
+a/b/d/g: test: a/b/d/*
+onoff: test: unset
+offon: test: set
+no: notest: set
+no: test: unspecified
+a/b/d/no: notest: set
+a/b/d/no: test: a/b/d/*
+a/b/d/yes: notest: set
+a/b/d/yes: test: unspecified
+EOF
+
+'
+
+test_expect_success 'command line checks' '
+
+       test_must_fail git check-attr &&
+       test_must_fail git check-attr -- &&
+       test_must_fail git check-attr test &&
+       test_must_fail git check-attr test -- &&
+       test_must_fail git check-attr -- f &&
+       echo "f" | test_must_fail git check-attr --stdin &&
+       echo "f" | test_must_fail git check-attr --stdin -- f &&
+       echo "f" | test_must_fail git check-attr --stdin test -- f &&
+       test_must_fail git check-attr "" -- f
 
 '
 
@@ -60,6 +93,28 @@ test_expect_success 'attribute test' '
 
 '
 
+test_expect_success 'unnormalized paths' '
+
+       attr_check ./f f &&
+       attr_check ./a/g a/g &&
+       attr_check a/./g a/g &&
+       attr_check a/c/../b/g a/b/g
+
+'
+
+test_expect_success 'relative paths' '
+
+       (cd a && attr_check ../f f) &&
+       (cd a && attr_check f f) &&
+       (cd a && attr_check i a/i) &&
+       (cd a && attr_check g a/g) &&
+       (cd a && attr_check b/g a/b/g) &&
+       (cd b && attr_check ../a/f f) &&
+       (cd b && attr_check ../a/g a/g) &&
+       (cd b && attr_check ../a/b/g a/b/g)
+
+'
+
 test_expect_success 'core.attributesfile' '
        attr_check global unspecified &&
        git config core.attributesfile "$HOME/global-gitattributes" &&
@@ -72,26 +127,19 @@ test_expect_success 'core.attributesfile' '
 
 test_expect_success 'attribute test: read paths from stdin' '
 
-       cat <<EOF > expect &&
-f: test: f
-a/f: test: f
-a/c/f: test: f
-a/g: test: a/g
-a/b/g: test: a/b/g
-b/g: test: unspecified
-a/b/h: test: a/b/h
-a/b/d/g: test: a/b/d/*
-onoff: test: unset
-offon: test: set
-no: test: unspecified
-a/b/d/no: test: a/b/d/*
-a/b/d/yes: test: unspecified
-EOF
-
+       grep -v notest < expect-all > expect &&
        sed -e "s/:.*//" < expect | git check-attr --stdin test > actual &&
        test_cmp expect actual
 '
 
+test_expect_success 'attribute test: --all option' '
+
+       grep -v unspecified < expect-all | sort > expect &&
+       sed -e "s/:.*//" < expect-all | uniq |
+               git check-attr --stdin --all | sort > actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'root subdir attribute test' '
 
        attr_check a/i a/i &&
index f87abb5a0682d7a2fc7c3947789301c4157d331d..1d29810a7a94dad15645869c2f4f015a470fa622 100755 (executable)
@@ -40,6 +40,12 @@ check_parse 2008-02 bad
 check_parse 2008-02-14 bad
 check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 +0000'
 check_parse '2008-02-14 20:30:45 -0500' '2008-02-14 20:30:45 -0500'
+check_parse '2008-02-14 20:30:45 -0015' '2008-02-14 20:30:45 -0015'
+check_parse '2008-02-14 20:30:45 -5' '2008-02-14 20:30:45 +0000'
+check_parse '2008-02-14 20:30:45 -5:' '2008-02-14 20:30:45 +0000'
+check_parse '2008-02-14 20:30:45 -05' '2008-02-14 20:30:45 -0500'
+check_parse '2008-02-14 20:30:45 -:30' '2008-02-14 20:30:45 +0000'
+check_parse '2008-02-14 20:30:45 -05:00' '2008-02-14 20:30:45 -0500'
 check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 -0500' EST5
 
 check_approxidate() {
index 9078b84ae68b430285312749f62daee103dcc428..f19e6510d04583866e39cbdea545a0d1323b7f76 100755 (executable)
@@ -66,31 +66,48 @@ test_expect_success expanded_in_repo '
                echo "\$Id:NoSpaceAtEitherEnd\$"
                echo "\$Id: NoTerminatingSymbol"
                echo "\$Id: Foreign Commit With Spaces \$"
-               echo "\$Id: NoTerminatingSymbolAtEOF"
-       } > expanded-keywords &&
+       } >expanded-keywords.0 &&
+
+       {
+               cat expanded-keywords.0 &&
+               printf "\$Id: NoTerminatingSymbolAtEOF"
+       } >expanded-keywords &&
+       cat expanded-keywords >expanded-keywords-crlf &&
+       git add expanded-keywords expanded-keywords-crlf &&
+       git commit -m "File with keywords expanded" &&
+       id=$(git rev-parse --verify :expanded-keywords) &&
 
        {
                echo "File with expanded keywords"
-               echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
-               echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
-               echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
-               echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
-               echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
-               echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
+               echo "\$Id: $id \$"
+               echo "\$Id: $id \$"
+               echo "\$Id: $id \$"
+               echo "\$Id: $id \$"
+               echo "\$Id: $id \$"
+               echo "\$Id: $id \$"
                echo "\$Id: NoTerminatingSymbol"
                echo "\$Id: Foreign Commit With Spaces \$"
-               echo "\$Id: NoTerminatingSymbolAtEOF"
-       } > expected-output &&
-
-       git add expanded-keywords &&
-       git commit -m "File with keywords expanded" &&
+       } >expected-output.0 &&
+       {
+               cat expected-output.0 &&
+               printf "\$Id: NoTerminatingSymbolAtEOF"
+       } >expected-output &&
+       {
+               append_cr <expected-output.0 &&
+               printf "\$Id: NoTerminatingSymbolAtEOF"
+       } >expected-output-crlf &&
+       {
+               echo "expanded-keywords ident"
+               echo "expanded-keywords-crlf ident text eol=crlf"
+       } >>.gitattributes &&
 
-       echo "expanded-keywords ident" >> .gitattributes &&
+       rm -f expanded-keywords expanded-keywords-crlf &&
 
-       rm -f expanded-keywords &&
        git checkout -- expanded-keywords &&
-       cat expanded-keywords &&
-       cmp expanded-keywords expected-output
+       test_cmp expanded-keywords expected-output &&
+
+       git checkout -- expanded-keywords-crlf &&
+       test_cmp expanded-keywords-crlf expected-output-crlf
 '
 
 # The use of %f in a filter definition is expanded to the path to
index ae266147b6db9e06abeb4dc30e4271b6c611deac..007f39d5e1dca6eff7e01969282c4cf2dc1bb57a 100755 (executable)
@@ -28,6 +28,7 @@ String options
     --st <st>             get another string (pervert ordering)
     -o <str>              get another string
     --default-string      set string to default
+    --list <str>          add str to list
 
 Magic arguments
     --quux                means --quux
@@ -337,4 +338,20 @@ test_expect_success 'negation of OPT_NONEG flags is not ambiguous' '
        test_cmp expect output
 '
 
+cat >>expect <<'EOF'
+list: foo
+list: bar
+list: baz
+EOF
+test_expect_success '--list keeps list of strings' '
+       test-parse-options --list foo --list=bar --list=baz >output &&
+       test_cmp expect output
+'
+
+test_expect_success '--no-list resets list' '
+       test-parse-options --list=other --list=irrelevant --list=options \
+               --no-list --list=foo --list=bar --list=baz >output &&
+       test_cmp expect output
+'
+
 test_done
diff --git a/t/t1013-loose-object-format.sh b/t/t1013-loose-object-format.sh
new file mode 100755 (executable)
index 0000000..0a9cedd
--- /dev/null
@@ -0,0 +1,66 @@
+#!/bin/sh
+#
+# Copyright (c) 2011 Roberto Tyley
+#
+
+test_description='Correctly identify and parse loose object headers
+
+There are two file formats for loose objects - the original standard
+format, and the experimental format introduced with Git v1.4.3, later
+deprecated with v1.5.3. Although Git no longer writes the
+experimental format, objects in both formats must be read, with the
+format for a given file being determined by the header.
+
+Detecting file format based on header is not entirely trivial, not
+least because the first byte of a zlib-deflated stream will vary
+depending on how much memory was allocated for the deflation window
+buffer when the object was written out (for example 4KB on Android,
+rather that 32KB on a normal PC).
+
+The loose objects used as test vectors have been generated with the
+following Git versions:
+
+standard format: Git v1.7.4.1
+experimental format: Git v1.4.3 (legacyheaders=false)
+standard format, deflated with 4KB window size: Agit/JGit on Android
+'
+
+. ./test-lib.sh
+
+assert_blob_equals() {
+       printf "%s" "$2" >expected &&
+       git cat-file -p "$1" >actual &&
+       test_cmp expected actual
+}
+
+test_expect_success setup '
+       cp -R "$TEST_DIRECTORY/t1013/objects" .git/
+       git --version
+'
+
+test_expect_success 'read standard-format loose objects' '
+       git cat-file tag 8d4e360d6c70fbd72411991c02a09c442cf7a9fa &&
+       git cat-file commit 6baee0540ea990d9761a3eb9ab183003a71c3696 &&
+       git ls-tree 7a37b887a73791d12d26c0d3e39568a8fb0fa6e8 &&
+       assert_blob_equals "257cc5642cb1a054f08cc83f2d943e56fd3ebe99" "foo$LF"
+'
+
+test_expect_success 'read experimental-format loose objects' '
+       git cat-file tag 76e7fa9941f4d5f97f64fea65a2cba436bc79cbb &&
+       git cat-file commit 7875c6237d3fcdd0ac2f0decc7d3fa6a50b66c09 &&
+       git ls-tree 95b1625de3ba8b2214d1e0d0591138aea733f64f &&
+       assert_blob_equals "2e65efe2a145dda7ee51d1741299f848e5bf752e" "a" &&
+       assert_blob_equals "9ae9e86b7bd6cb1472d9373702d8249973da0832" "ab" &&
+       assert_blob_equals "85df50785d62d3b05ab03d9cbf7e4a0b49449730" "abcd" &&
+       assert_blob_equals "1656f9233d999f61ef23ef390b9c71d75399f435" "abcdefgh" &&
+       assert_blob_equals "1e72a6b2c4a577ab0338860fa9fe87f761fc9bbd" "abcdefghi" &&
+       assert_blob_equals "70e6a83d8dcb26fc8bc0cf702e2ddeb6adca18fd" "abcdefghijklmnop" &&
+       assert_blob_equals "bd15045f6ce8ff75747562173640456a394412c8" "abcdefghijklmnopqrstuvwx"
+'
+
+test_expect_success 'read standard-format objects deflated with smaller window buffer' '
+       git cat-file tag f816d5255855ac160652ee5253b06cd8ee14165a &&
+       git cat-file tag 149cedb5c46929d18e0f118e9fa31927487af3b6
+'
+
+test_done
diff --git a/t/t1013/objects/14/9cedb5c46929d18e0f118e9fa31927487af3b6 b/t/t1013/objects/14/9cedb5c46929d18e0f118e9fa31927487af3b6
new file mode 100644 (file)
index 0000000..472fd14
Binary files /dev/null and b/t/t1013/objects/14/9cedb5c46929d18e0f118e9fa31927487af3b6 differ
diff --git a/t/t1013/objects/16/56f9233d999f61ef23ef390b9c71d75399f435 b/t/t1013/objects/16/56f9233d999f61ef23ef390b9c71d75399f435
new file mode 100644 (file)
index 0000000..c379d74
Binary files /dev/null and b/t/t1013/objects/16/56f9233d999f61ef23ef390b9c71d75399f435 differ
diff --git a/t/t1013/objects/1e/72a6b2c4a577ab0338860fa9fe87f761fc9bbd b/t/t1013/objects/1e/72a6b2c4a577ab0338860fa9fe87f761fc9bbd
new file mode 100644 (file)
index 0000000..9370630
Binary files /dev/null and b/t/t1013/objects/1e/72a6b2c4a577ab0338860fa9fe87f761fc9bbd differ
diff --git a/t/t1013/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 b/t/t1013/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99
new file mode 100644 (file)
index 0000000..bdcf704
Binary files /dev/null and b/t/t1013/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 differ
diff --git a/t/t1013/objects/2e/65efe2a145dda7ee51d1741299f848e5bf752e b/t/t1013/objects/2e/65efe2a145dda7ee51d1741299f848e5bf752e
new file mode 100644 (file)
index 0000000..ad62c43
Binary files /dev/null and b/t/t1013/objects/2e/65efe2a145dda7ee51d1741299f848e5bf752e differ
diff --git a/t/t1013/objects/6b/aee0540ea990d9761a3eb9ab183003a71c3696 b/t/t1013/objects/6b/aee0540ea990d9761a3eb9ab183003a71c3696
new file mode 100644 (file)
index 0000000..3d2f033
Binary files /dev/null and b/t/t1013/objects/6b/aee0540ea990d9761a3eb9ab183003a71c3696 differ
diff --git a/t/t1013/objects/70/e6a83d8dcb26fc8bc0cf702e2ddeb6adca18fd b/t/t1013/objects/70/e6a83d8dcb26fc8bc0cf702e2ddeb6adca18fd
new file mode 100644 (file)
index 0000000..b3f71a6
Binary files /dev/null and b/t/t1013/objects/70/e6a83d8dcb26fc8bc0cf702e2ddeb6adca18fd differ
diff --git a/t/t1013/objects/76/e7fa9941f4d5f97f64fea65a2cba436bc79cbb b/t/t1013/objects/76/e7fa9941f4d5f97f64fea65a2cba436bc79cbb
new file mode 100644 (file)
index 0000000..af4e9a7
--- /dev/null
@@ -0,0 +1,2 @@
\vx\9c%ÌA\ e\820\10@Ñ}O1{cSZ(\98\18ãνá\ 2Ãthª\94\92Z\8cÜÞ Ëÿ\16?\r\ f¦\ 2m×6dµi\9d\19É9\85¤Gå\98h\a´Ø¨ÁZR'Q¶\85\81R\8c¡\88\82\1eø³p\ e\91ç\82ÓqL9âÏ=g¸§\81sIÐo\13opÎÿ\94eÏ«_1»\80³¤$×ç\ 5*Si«ëNwpP\95RBôûÅÁú
\87[(ð®d-\8dø\ 2ÁL9á
\ No newline at end of file
diff --git a/t/t1013/objects/78/75c6237d3fcdd0ac2f0decc7d3fa6a50b66c09 b/t/t1013/objects/78/75c6237d3fcdd0ac2f0decc7d3fa6a50b66c09
new file mode 100644 (file)
index 0000000..3dd28be
Binary files /dev/null and b/t/t1013/objects/78/75c6237d3fcdd0ac2f0decc7d3fa6a50b66c09 differ
diff --git a/t/t1013/objects/7a/37b887a73791d12d26c0d3e39568a8fb0fa6e8 b/t/t1013/objects/7a/37b887a73791d12d26c0d3e39568a8fb0fa6e8
new file mode 100644 (file)
index 0000000..2b97b26
Binary files /dev/null and b/t/t1013/objects/7a/37b887a73791d12d26c0d3e39568a8fb0fa6e8 differ
diff --git a/t/t1013/objects/85/df50785d62d3b05ab03d9cbf7e4a0b49449730 b/t/t1013/objects/85/df50785d62d3b05ab03d9cbf7e4a0b49449730
new file mode 100644 (file)
index 0000000..6dff746
Binary files /dev/null and b/t/t1013/objects/85/df50785d62d3b05ab03d9cbf7e4a0b49449730 differ
diff --git a/t/t1013/objects/8d/4e360d6c70fbd72411991c02a09c442cf7a9fa b/t/t1013/objects/8d/4e360d6c70fbd72411991c02a09c442cf7a9fa
new file mode 100644 (file)
index 0000000..cb41e92
Binary files /dev/null and b/t/t1013/objects/8d/4e360d6c70fbd72411991c02a09c442cf7a9fa differ
diff --git a/t/t1013/objects/95/b1625de3ba8b2214d1e0d0591138aea733f64f b/t/t1013/objects/95/b1625de3ba8b2214d1e0d0591138aea733f64f
new file mode 100644 (file)
index 0000000..7ac46b4
Binary files /dev/null and b/t/t1013/objects/95/b1625de3ba8b2214d1e0d0591138aea733f64f differ
diff --git a/t/t1013/objects/9a/e9e86b7bd6cb1472d9373702d8249973da0832 b/t/t1013/objects/9a/e9e86b7bd6cb1472d9373702d8249973da0832
new file mode 100644 (file)
index 0000000..9d8316d
Binary files /dev/null and b/t/t1013/objects/9a/e9e86b7bd6cb1472d9373702d8249973da0832 differ
diff --git a/t/t1013/objects/bd/15045f6ce8ff75747562173640456a394412c8 b/t/t1013/objects/bd/15045f6ce8ff75747562173640456a394412c8
new file mode 100644 (file)
index 0000000..eebf239
Binary files /dev/null and b/t/t1013/objects/bd/15045f6ce8ff75747562173640456a394412c8 differ
diff --git a/t/t1013/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/t/t1013/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
new file mode 100644 (file)
index 0000000..134cf19
Binary files /dev/null and b/t/t1013/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 differ
diff --git a/t/t1013/objects/f8/16d5255855ac160652ee5253b06cd8ee14165a b/t/t1013/objects/f8/16d5255855ac160652ee5253b06cd8ee14165a
new file mode 100644 (file)
index 0000000..26b75ae
--- /dev/null
@@ -0,0 +1 @@
+H\89\15ÌÁ\ e\820\f\80aÏ{\8aÞ\rI»e\1d&Æø*¥\1d\88\ 1\17ß^¸ýù\ e¿Ë\ 4DåÒ\86wU\87Ò\97¬\1cS±4ª\19\8aÆ\11­ª\9e ,\19\afÅ[ðßVAÛºÎ\1eüxÈÇö6[wtG§Lu\a¸?\97¦²¼Ú×\1f@\89"gì{\86+\12b\b\7fy¾%M
\ No newline at end of file
index f6a44c9ee07329c8909662d2a807acf0dd6a8e43..3b1b985996e9a6b52b032ef45c5be9b1c57b60f6 100755 (executable)
@@ -17,8 +17,6 @@ test_expect_success setup '
        cp one original.one &&
        cp dir/two original.two
 '
-LF='
-'
 
 test_expect_success 'update-index and ls-files' '
        git update-index --add one &&
@@ -140,6 +138,22 @@ test_expect_success 'GIT_PREFIX for !alias' '
        test_cmp expect actual
 '
 
+test_expect_success 'GIT_PREFIX for built-ins' '
+       # Use GIT_EXTERNAL_DIFF to test that the "diff" built-in
+       # receives the GIT_PREFIX variable.
+       printf "dir/" >expect &&
+       printf "#!/bin/sh\n" >diff &&
+       printf "printf \"\$GIT_PREFIX\"" >>diff &&
+       chmod +x diff &&
+       (
+               cd dir &&
+               printf "change" >two &&
+               env GIT_EXTERNAL_DIFF=./diff git diff >../actual
+               git checkout -- two
+       ) &&
+       test_cmp expect actual
+'
+
 test_expect_success 'no file/rev ambiguity check inside .git' '
        git commit -a -m 1 &&
        (
index 3db56267ee89e1b585a548f7bee13386e47395f4..dffccf84f8f67ae8dc13eae4547eb4c7c687de1d 100755 (executable)
@@ -333,6 +333,12 @@ test_expect_success 'get-regexp variable with no value' \
        'git config --get-regexp novalue > output &&
         cmp output expect'
 
+echo 'novalue.variable true' > expect
+
+test_expect_success 'get-regexp --bool variable with no value' \
+       'git config --bool --get-regexp novalue > output &&
+        cmp output expect'
+
 echo 'emptyvalue.variable ' > expect
 
 test_expect_success 'get-regexp variable with empty value' \
@@ -904,4 +910,22 @@ test_expect_success 'git -c works with aliases of builtins' '
        test_cmp expect actual
 '
 
+test_expect_success 'git -c does not split values on equals' '
+       echo "value with = in it" >expect &&
+       git -c core.foo="value with = in it" config core.foo >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git -c dies on bogus config' '
+       test_must_fail git -c core.bare=foo rev-parse
+'
+
+test_expect_success 'git -c complains about empty key' '
+       test_must_fail git -c "=foo" rev-parse
+'
+
+test_expect_success 'git -c complains about empty key and value' '
+       test_must_fail git -c "" rev-parse
+'
+
 test_done
index b5d89a2250e41afe544b0d0f59f638e02d570cdd..2b962cfda70d998e9c2799cf8fc720b330cd118a 100755 (executable)
@@ -25,6 +25,11 @@ else
        test_set_prereq SETFACL
 fi
 
+if test -z "$LOGNAME"
+then
+       LOGNAME=$USER
+fi
+
 check_perms_and_acl () {
        test -r "$1" &&
        getfacl "$1" > actual &&
index 1b0f82fa4c7928fc4605ccf31a3ae45e6ac9f38e..ed4275afe3100491ea57025632665182127f33cf 100755 (executable)
@@ -18,6 +18,9 @@ invalid_ref 'foo'
 valid_ref 'foo/bar/baz'
 valid_ref 'refs///heads/foo'
 invalid_ref 'heads/foo/'
+valid_ref '/heads/foo'
+valid_ref '///heads/foo'
+invalid_ref '/foo'
 invalid_ref './foo'
 invalid_ref '.refs/foo'
 invalid_ref 'heads/foo..bar'
@@ -27,6 +30,9 @@ invalid_ref 'heads/foo.lock'
 valid_ref 'heads/foo@bar'
 invalid_ref 'heads/v@{ation'
 invalid_ref 'heads/foo\bar'
+invalid_ref "$(printf 'heads/foo\t')"
+invalid_ref "$(printf 'heads/foo\177')"
+valid_ref "$(printf 'heads/fu\303\237')"
 
 test_expect_success "check-ref-format --branch @{-1}" '
        T=$(git write-tree) &&
@@ -70,7 +76,10 @@ invalid_ref_normalized() {
 
 valid_ref_normalized 'heads/foo' 'heads/foo'
 valid_ref_normalized 'refs///heads/foo' 'refs/heads/foo'
+valid_ref_normalized '/heads/foo' 'heads/foo'
+valid_ref_normalized '///heads/foo' 'heads/foo'
 invalid_ref_normalized 'foo'
+invalid_ref_normalized '/foo'
 invalid_ref_normalized 'heads/foo/../bar'
 invalid_ref_normalized 'heads/./foo'
 invalid_ref_normalized 'heads\foo'
index 7f519e5ebeac0780aee1c6d9c4458e00b62a273d..647d888507a4b74b82ae4016c2f30f7d171e98ca 100755 (executable)
@@ -21,10 +21,10 @@ test_expect_success 'setup reflog with alternating commits' '
 
 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@{0} reset: moving to two
+               topic@{1} reset: moving to one
+               topic@{2} reset: moving to two
+               topic@{3} reset: moving to one
                topic@{4} branch: Created from HEAD
        EOF
        git log -g --format="%gd %gs" topic >actual &&
index bb01d5ab8f8ebcaf49ef238d48ae89e3845d5f4b..523ce9c45b75d85a129015a56004473a5fccf926 100755 (executable)
@@ -110,6 +110,42 @@ test_expect_success 'email with embedded > is not okay' '
        grep "error in commit $new" out
 '
 
+test_expect_success 'missing < email delimiter is reported nicely' '
+       git cat-file commit HEAD >basis &&
+       sed "s/<//" basis >bad-email-2 &&
+       new=$(git hash-object -t commit -w --stdin <bad-email-2) &&
+       test_when_finished "remove_object $new" &&
+       git update-ref refs/heads/bogus "$new" &&
+       test_when_finished "git update-ref -d refs/heads/bogus" &&
+       git fsck 2>out &&
+       cat out &&
+       grep "error in commit $new.* - bad name" out
+'
+
+test_expect_success 'missing email is reported nicely' '
+       git cat-file commit HEAD >basis &&
+       sed "s/[a-z]* <[^>]*>//" basis >bad-email-3 &&
+       new=$(git hash-object -t commit -w --stdin <bad-email-3) &&
+       test_when_finished "remove_object $new" &&
+       git update-ref refs/heads/bogus "$new" &&
+       test_when_finished "git update-ref -d refs/heads/bogus" &&
+       git fsck 2>out &&
+       cat out &&
+       grep "error in commit $new.* - missing email" out
+'
+
+test_expect_success '> in name is reported' '
+       git cat-file commit HEAD >basis &&
+       sed "s/ </> </" basis >bad-email-4 &&
+       new=$(git hash-object -t commit -w --stdin <bad-email-4) &&
+       test_when_finished "remove_object $new" &&
+       git update-ref refs/heads/bogus "$new" &&
+       test_when_finished "git update-ref -d refs/heads/bogus" &&
+       git fsck 2>out &&
+       cat out &&
+       grep "error in commit $new" out
+'
+
 test_expect_success 'tag pointing to nonexistent' '
        cat >invalid-tag <<-\EOF &&
        object ffffffffffffffffffffffffffffffffffffffff
index fa69016381b0196c49472af51e36948ec7c5a2a9..75874e85dfbcae8ea9634693a93524841b741559 100755 (executable)
@@ -118,6 +118,15 @@ test_expect_success 'checkout -b to an existing branch fails' '
        test_must_fail do_checkout branch2 $HEAD2
 '
 
+test_expect_success 'checkout -b to @{-1} fails with the right branch name' '
+       git reset --hard HEAD &&
+       git checkout branch1 &&
+       git checkout branch2 &&
+       echo  >expect "fatal: A branch named '\''branch1'\'' already exists." &&
+       test_must_fail git checkout -b @{-1} 2>actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'checkout -B to an existing branch resets branch to HEAD' '
        git checkout branch1 &&
 
@@ -169,4 +178,23 @@ test_expect_success 'checkout -f -B to an existing branch with mergeable changes
        test_must_fail test_dirty_mergeable
 '
 
+test_expect_success 'checkout -b <describe>' '
+       git tag -f -m "First commit" initial initial &&
+       git checkout -f change1 &&
+       name=$(git describe) &&
+       git checkout -b $name &&
+       git diff --exit-code change1 &&
+       echo "refs/heads/$name" >expect &&
+       git symbolic-ref HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'checkout -B to the current branch fails before merging' '
+       git checkout branch1 &&
+       setup_dirty_mergeable &&
+       git commit -mfooble &&
+       test_must_fail git checkout -B branch1 initial &&
+       test_must_fail test_dirty_mergeable
+'
+
 test_done
diff --git a/t/t2022-checkout-paths.sh b/t/t2022-checkout-paths.sh
new file mode 100755 (executable)
index 0000000..56090d2
--- /dev/null
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+test_description='checkout $tree -- $paths'
+. ./test-lib.sh
+
+test_expect_success setup '
+       mkdir dir &&
+       >dir/master &&
+       echo common >dir/common &&
+       git add dir/master dir/common &&
+       test_tick && git commit -m "master has dir/master" &&
+       git checkout -b next &&
+       git mv dir/master dir/next0 &&
+       echo next >dir/next1 &&
+       git add dir &&
+       test_tick && git commit -m "next has dir/next but not dir/master"
+'
+
+test_expect_success 'checking out paths out of a tree does not clobber unrelated paths' '
+       git checkout next &&
+       git reset --hard &&
+       rm dir/next0 &&
+       cat dir/common >expect.common &&
+       echo modified >expect.next1 &&
+       cat expect.next1 >dir/next1 &&
+       echo untracked >expect.next2 &&
+       cat expect.next2 >dir/next2 &&
+
+       git checkout master dir &&
+
+       test_cmp expect.common dir/common &&
+       test_path_is_file dir/master &&
+       git diff --exit-code master dir/master &&
+
+       test_path_is_missing dir/next0 &&
+       test_cmp expect.next1 dir/next1 &&
+       test_path_is_file dir/next2 &&
+       test_must_fail git ls-files --error-unmatch dir/next2 &&
+       test_cmp expect.next2 dir/next2
+'
+
+test_done
diff --git a/t/t3005-ls-files-relative.sh b/t/t3005-ls-files-relative.sh
new file mode 100755 (executable)
index 0000000..3778694
--- /dev/null
@@ -0,0 +1,72 @@
+#!/bin/sh
+
+test_description='ls-files tests with relative paths
+
+This test runs git ls-files with various relative path arguments.
+'
+
+. ./test-lib.sh
+
+new_line='
+'
+sq=\'
+
+test_expect_success 'prepare' '
+       : >never-mind-me &&
+       git add never-mind-me &&
+       mkdir top &&
+       (
+               cd top &&
+               mkdir sub &&
+               x="x xa xbc xdef xghij xklmno" &&
+               y=$(echo "$x" | tr x y) &&
+               touch $x &&
+               touch $y &&
+               cd sub &&
+               git add ../x*
+       )
+'
+
+test_expect_success 'ls-files with mixed levels' '
+       (
+               cd top/sub &&
+               cat >expect <<-EOF &&
+               ../../never-mind-me
+               ../x
+               EOF
+               git ls-files $(cat expect) >actual &&
+               test_cmp expect actual
+       )
+'
+
+test_expect_success 'ls-files -c' '
+       (
+               cd top/sub &&
+               for f in ../y*
+               do
+                       echo "error: pathspec $sq$f$sq did not match any file(s) known to git."
+               done >expect.err &&
+               echo "Did you forget to ${sq}git add${sq}?" >>expect.err &&
+               ls ../x* >expect.out &&
+               test_must_fail git ls-files -c --error-unmatch ../[xy]* >actual.out 2>actual.err &&
+               test_cmp expect.out actual.out &&
+               test_cmp expect.err actual.err
+       )
+'
+
+test_expect_success 'ls-files -o' '
+       (
+               cd top/sub &&
+               for f in ../x*
+               do
+                       echo "error: pathspec $sq$f$sq did not match any file(s) known to git."
+               done >expect.err &&
+               echo "Did you forget to ${sq}git add${sq}?" >>expect.err &&
+               ls ../y* >expect.out &&
+               test_must_fail git ls-files -o --error-unmatch ../[xy]* >actual.out 2>actual.err &&
+               test_cmp expect.out actual.out &&
+               test_cmp expect.err actual.err
+       )
+'
+
+test_done
index 0c02d569520ddc81b32db7d248197562d00bd7ea..55ef1895d7fd15348c47a5dc4a7f93541a1d38c1 100755 (executable)
@@ -267,7 +267,8 @@ test_expect_success 'setup 8' '
                ln -s e a &&
                git add a e &&
                test_tick &&
-               git commit -m "rename a->e, symlink a->e"
+               git commit -m "rename a->e, symlink a->e" &&
+               oln=`printf e | git hash-object --stdin`
        fi
 '
 
@@ -630,16 +631,18 @@ test_expect_success 'merge-recursive copy vs. rename' '
 
 if test_have_prereq SYMLINKS
 then
-       test_expect_success 'merge-recursive rename vs. rename/symlink' '
+       test_expect_failure 'merge-recursive rename vs. rename/symlink' '
 
                git checkout -f rename &&
                git merge rename-ln &&
                ( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
                (
+                       echo "120000 blob $oln  a"
                        echo "100644 blob $o0   b"
                        echo "100644 blob $o0   c"
                        echo "100644 blob $o0   d/e"
                        echo "100644 blob $o0   e"
+                       echo "120000 $oln 0     a"
                        echo "100644 $o0 0      b"
                        echo "100644 $o0 0      c"
                        echo "100644 $o0 0      d/e"
diff --git a/t/t3103-ls-tree-misc.sh b/t/t3103-ls-tree-misc.sh
new file mode 100755 (executable)
index 0000000..09dcf04
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+test_description='
+Miscellaneous tests for git ls-tree.
+
+             1. git ls-tree fails in presence of tree damage.
+
+'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       mkdir a &&
+       touch a/one &&
+       git add a/one &&
+       git commit -m test
+'
+
+test_expect_success 'ls-tree fails with non-zero exit code on broken tree' '
+       rm -f .git/objects/5f/cffbd6e4c5c5b8d81f5e9314b20e338e3ffff5 &&
+       test_must_fail git ls-tree -r HEAD
+'
+
+test_done
index 9e69c8c926620f06343e64e7b3aa3e4ada5a6b69..7633930bb472e1c9aebf2799f3eaf3bc53c7263c 100755 (executable)
@@ -98,6 +98,18 @@ test_expect_success 'git branch -m q r/q should fail when r exists' '
        test_must_fail git branch -m q r/q
 '
 
+test_expect_success 'git branch -M foo bar should fail when bar is checked out' '
+       git branch bar &&
+       git checkout -b foo &&
+       test_must_fail git branch -M bar foo
+'
+
+test_expect_success 'git branch -M baz bam should succeed when baz is checked out' '
+       git checkout -b baz &&
+       git branch bam &&
+       git branch -M baz bam
+'
+
 mv .git/config .git/config-saved
 
 test_expect_success 'git branch -m q q2 without config should succeed' '
@@ -542,4 +554,17 @@ test_expect_success 'attempt to delete a branch merged to its base' '
        test_must_fail git branch -d my10
 '
 
+test_expect_success 'use set-upstream on the current branch' '
+       git checkout master &&
+       git --bare init myupstream.git &&
+       git push myupstream.git master:refs/heads/frotz &&
+       git remote add origin myupstream.git &&
+       git fetch &&
+       git branch --set-upstream master origin/frotz &&
+
+       test "z$(git config branch.master.remote)" = "zorigin" &&
+       test "z$(git config branch.master.merge)" = "zrefs/heads/frotz"
+
+'
+
 test_done
index 2ea3be6546f43fd5c224aa8df6add96fa9df6ac2..1aa366a410e9a3e2ad4c2fa84431198fbb553a5f 100755 (executable)
@@ -26,13 +26,13 @@ test_expect_success 'example 1: notes to add an Acked-by line' '
 '
 
 test_expect_success 'example 2: binary notes' '
-       cp "$TEST_DIRECTORY"/test4012.png . &&
+       cp "$TEST_DIRECTORY"/test-binary-1.png . &&
        git checkout B &&
-       blob=$(git hash-object -w test4012.png) &&
+       blob=$(git hash-object -w test-binary-1.png) &&
        git notes --ref=logo add -C "$blob" &&
        git notes --ref=logo copy B C &&
        git notes --ref=logo show C >actual &&
-       test_cmp test4012.png actual
+       test_cmp test-binary-1.png actual
 '
 
 test_done
index 47c8371c7edfe7945ded380bb6f96a00d26c04eb..b981572d736a1adf8da5281f31e580982e2059af 100755 (executable)
@@ -295,7 +295,7 @@ test_expect_success 'preserve merges with -p' '
 '
 
 test_expect_success 'edit ancestor with -p' '
-       FAKE_LINES="1 edit 2 3 4" git rebase -i -p HEAD~3 &&
+       FAKE_LINES="1 2 edit 3 4" git rebase -i -p HEAD~3 &&
        echo 2 > unrelated-file &&
        test_tick &&
        git commit -m L2-modified --amend unrelated-file &&
@@ -527,6 +527,20 @@ test_expect_success 'auto-amend only edited commits after "edit"' '
        git rebase --abort
 '
 
+test_expect_success 'clean error after failed "exec"' '
+       test_tick &&
+       test_when_finished "git rebase --abort || :" &&
+       (
+               FAKE_LINES="1 exec_false" &&
+               export FAKE_LINES &&
+               test_must_fail git rebase -i HEAD^
+       ) &&
+       echo "edited again" > file7 &&
+       git add file7 &&
+       test_must_fail git rebase --continue 2>error &&
+       grep "You have staged changes in your working tree." error
+'
+
 test_expect_success 'rebase a detached HEAD' '
        grandparent=$(git rev-parse HEAD~2) &&
        git checkout $(git rev-parse HEAD) &&
index 08201e2331d36af769d832e3d9551c48331b7033..6de4e2263f9ec65d2de2e28af6a12d5c1065f686 100755 (executable)
@@ -37,7 +37,15 @@ export GIT_AUTHOR_EMAIL
 #      \
 #       B2     <-- origin/topic
 #
-# In all cases, 'topic' is rebased onto 'origin/topic'.
+# Clone 4 (merge using second parent as base):
+#
+# A1--A2--B3   <-- origin/master
+#  \
+#   B1--A3--M  <-- topic
+#    \     /
+#     \--A4    <-- topic2
+#      \
+#       B2     <-- origin/topic
 
 test_expect_success 'setup for merge-preserving rebase' \
        'echo First > A &&
@@ -57,6 +65,13 @@ test_expect_success 'setup for merge-preserving rebase' \
        git merge origin/master
        ) &&
 
+       git clone ./. clone4 &&
+       (
+               cd clone4 &&
+               git checkout -b topic origin/topic &&
+               git merge origin/master
+       ) &&
+
        echo Fifth > B &&
        git add B &&
        git commit -m "Add different B" &&
@@ -123,4 +138,15 @@ test_expect_success 'rebase -p preserves no-ff merges' '
        )
 '
 
+test_expect_success 'rebase -p works when base inside second parent' '
+       (
+       cd clone4 &&
+       git fetch &&
+       git rebase -p HEAD^2 &&
+       test 1 = $(git rev-list --all --pretty=oneline | grep "Modify A" | wc -l) &&
+       test 1 = $(git rev-list --all --pretty=oneline | grep "Modify B" | wc -l) &&
+       test 1 = $(git rev-list --all --pretty=oneline | grep "Merge remote-tracking branch " | wc -l)
+       )
+'
+
 test_done
index 14a23cd8726fbb33df480133ed0466177916b7fb..ace8e54e9b374688751cc470eeb9003ca7f5c48f 100755 (executable)
@@ -37,7 +37,7 @@ test_expect_success 'setup' '
 #        -- C1 --
 #
 test_expect_success 'squash F1 into D1' '
-       FAKE_LINES="1 squash 3 2" git rebase -i -p B1 &&
+       FAKE_LINES="1 squash 4 2 3" git rebase -i -p B1 &&
        test "$(git rev-parse HEAD^2)" = "$(git rev-parse C1)" &&
        test "$(git rev-parse HEAD~2)" = "$(git rev-parse B1)" &&
        git tag E2
index 9aefe3a1becac200f2c29beee84fab278f9fcfa0..e27f39d1e5b0fb0ac3a1fc6417f0f3240934f07d 100755 (executable)
@@ -16,12 +16,20 @@ test_expect_success setup '
        echo second > file2 &&
        git add file2 &&
        test_tick &&
-       git commit -m "second"
+       git commit -m "second" &&
+
+       git symbolic-ref HEAD refs/heads/third &&
+       rm .git/index file2 &&
+       echo third > file3 &&
+       git add file3 &&
+       test_tick &&
+       git commit -m "third"
 
 '
 
 test_expect_success 'cherry-pick a root commit' '
 
+       git checkout second^0 &&
        git cherry-pick master &&
        echo first >expect &&
        test_cmp expect file1
@@ -50,4 +58,21 @@ test_expect_success 'revert a root commit with an external strategy' '
 
 '
 
+test_expect_success 'cherry-pick two root commits' '
+
+       echo first >expect.file1 &&
+       echo second >expect.file2 &&
+       echo third >expect.file3 &&
+
+       git checkout second^0 &&
+       git cherry-pick master third &&
+
+       test_cmp expect.file1 file1 &&
+       test_cmp expect.file2 file2 &&
+       test_cmp expect.file3 file3 &&
+       git rev-parse --verify HEAD^^ &&
+       test_must_fail git rev-parse --verify HEAD^^^
+
+'
+
 test_done
index c06a5ee7660c3fd7ca15860cfb761b2c4d953e08..1f62c151b0aa63b4c85f9bc76f501d53967b0260 100755 (executable)
@@ -147,7 +147,7 @@ test_commit_autosquash_flags () {
                git commit -a -m "intermediate commit" &&
                test_tick &&
                echo $H $flag >>F &&
-               git commit -a --$flag HEAD~1 $3 &&
+               git commit -a --$flag HEAD~1 &&
                E=$(git cat-file commit '$H-$flag' |
                        sed -ne "s/^encoding //p") &&
                test "z$E" = "z$H" &&
@@ -160,6 +160,6 @@ test_commit_autosquash_flags () {
 
 test_commit_autosquash_flags eucJP fixup
 
-test_commit_autosquash_flags ISO-2022-JP squash '-m "squash message"'
+test_commit_autosquash_flags ISO-2022-JP squash
 
 test_done
index da82b655b3b33520b2c39d3992bc40368bf652be..534ee08a44b9d8501b234f228302e9a266d67f8c 100755 (executable)
@@ -10,8 +10,6 @@ test_description='quoted output'
 FN='濱野'
 GN='純'
 HT='   '
-LF='
-'
 DQ='"'
 
 echo foo 2>/dev/null > "Name and an${HT}HT"
index 7197aae1ed10add49aa72748188fecb4c2ef90a4..fcdb18217a777f5dbb77b0071af7159c6985656d 100755 (executable)
@@ -542,7 +542,7 @@ test_expect_success 'ref with non-existent reflog' '
        echo bar6 > file2 &&
        git add file2 &&
        git stash &&
-       ! "git rev-parse --quiet --verify does-not-exist" &&
+       test_must_fail git rev-parse --quiet --verify does-not-exist &&
        test_must_fail git stash drop does-not-exist &&
        test_must_fail git stash drop does-not-exist@{0} &&
        test_must_fail git stash pop does-not-exist &&
diff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh
new file mode 100755 (executable)
index 0000000..4f2eedf
--- /dev/null
@@ -0,0 +1,155 @@
+#!/bin/sh
+#
+# Copyright (c) 2011 David Caldwell
+#
+
+test_description='Test git stash --include-untracked'
+
+. ./test-lib.sh
+
+test_expect_success 'stash save --include-untracked some dirty working directory' '
+       echo 1 > file &&
+       git add file &&
+       test_tick &&
+       git commit -m initial &&
+       echo 2 > file &&
+       git add file &&
+       echo 3 > file &&
+       test_tick &&
+       echo 1 > file2 &&
+       git stash --include-untracked &&
+       git diff-files --quiet &&
+       git diff-index --cached --quiet HEAD
+'
+
+cat > expect <<EOF
+?? expect
+?? output
+EOF
+
+test_expect_success 'stash save --include-untracked cleaned the untracked files' '
+       git status --porcelain > output
+       test_cmp output expect
+'
+
+cat > expect.diff <<EOF
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..d00491f
+--- /dev/null
++++ b/file2
+@@ -0,0 +1 @@
++1
+EOF
+cat > expect.lstree <<EOF
+file2
+EOF
+
+test_expect_success 'stash save --include-untracked stashed the untracked files' '
+       test "!" -f file2 &&
+       git diff HEAD..stash^3 -- file2 > output &&
+       test_cmp output expect.diff &&
+       git ls-tree --name-only stash^3: > output &&
+       test_cmp output expect.lstree
+'
+test_expect_success 'stash save --patch --include-untracked fails' '
+       test_must_fail git stash --patch --include-untracked
+'
+
+test_expect_success 'stash save --patch --all fails' '
+       test_must_fail git stash --patch --all
+'
+
+git clean --force --quiet
+
+cat > expect <<EOF
+ M file
+?? expect
+?? file2
+?? output
+EOF
+
+test_expect_success 'stash pop after save --include-untracked leaves files untracked again' '
+       git stash pop &&
+       git status --porcelain > output
+       test_cmp output expect
+'
+
+git clean --force --quiet
+
+test_expect_success 'stash save -u dirty index' '
+       echo 4 > file3 &&
+       git add file3 &&
+       test_tick &&
+       git stash -u
+'
+
+cat > expect <<EOF
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..b8626c4
+--- /dev/null
++++ b/file3
+@@ -0,0 +1 @@
++4
+EOF
+
+test_expect_success 'stash save --include-untracked dirty index got stashed' '
+       git stash pop --index &&
+       git diff --cached > output &&
+       test_cmp output expect
+'
+
+git reset > /dev/null
+
+test_expect_success 'stash save --include-untracked -q is quiet' '
+       echo 1 > file5 &&
+       git stash save --include-untracked --quiet > output.out 2>&1 &&
+       test ! -s output.out
+'
+
+test_expect_success 'stash save --include-untracked removed files' '
+       rm -f file &&
+       git stash save --include-untracked &&
+       echo 1 > expect &&
+       test_cmp file expect
+'
+
+rm -f expect
+
+test_expect_success 'stash save --include-untracked removed files got stashed' '
+       git stash pop &&
+       test ! -f file
+'
+
+cat > .gitignore <<EOF
+.gitignore
+ignored
+EOF
+
+test_expect_success 'stash save --include-untracked respects .gitignore' '
+       echo ignored > ignored &&
+       git stash -u &&
+       test -s ignored &&
+       test -s .gitignore
+'
+
+test_expect_success 'stash save -u can stash with only untracked files different' '
+       echo 4 > file4 &&
+       git stash -u
+       test "!" -f file4
+'
+
+test_expect_success 'stash save --all does not respect .gitignore' '
+       git stash -a &&
+       test "!" -f ignored &&
+       test "!" -f .gitignore
+'
+
+test_expect_success 'stash save --all is stash poppable' '
+       git stash pop &&
+       test -s ignored &&
+       test -s .gitignore
+'
+
+test_done
index 05ec0628322ccc103343515feb70d27a9a0e6c76..2d9f9a0cf1555cab24af19f264d495e134c27352 100755 (executable)
@@ -12,7 +12,7 @@ test_expect_success 'prepare repository' \
        'echo AIT >a && echo BIT >b && echo CIT >c && echo DIT >d &&
         git update-index --add a b c d &&
         echo git >a &&
-        cat "$TEST_DIRECTORY"/test4012.png >b &&
+        cat "$TEST_DIRECTORY"/test-binary-1.png >b &&
         echo git >c &&
         cat b b >d'
 
index 92248d24c48a65ab89a5b0ad255357f6034e6ad4..67975129bc3703e16fe52eb916c2e08b7908ff87 100755 (executable)
@@ -179,12 +179,21 @@ test_expect_success 'configuration To: header' '
        grep "^To: R. E. Cipient <rcipient@example.com>\$" patch9
 '
 
+# check_patch <patch>: Verify that <patch> looks like a half-sane
+# patch email to avoid a false positive with !grep
+check_patch () {
+       grep -e "^From:" "$1" &&
+       grep -e "^Date:" "$1" &&
+       grep -e "^Subject:" "$1"
+}
+
 test_expect_success '--no-to overrides config.to' '
 
        git config --replace-all format.to \
                "R. E. Cipient <rcipient@example.com>" &&
        git format-patch --no-to --stdout master..side |
        sed -e "/^\$/q" >patch10 &&
+       check_patch patch10 &&
        ! grep "^To: R. E. Cipient <rcipient@example.com>\$" patch10
 '
 
@@ -195,6 +204,7 @@ test_expect_success '--no-to and --to replaces config.to' '
        git format-patch --no-to --to="Someone Else <else@out.there>" \
                --stdout master..side |
        sed -e "/^\$/q" >patch11 &&
+       check_patch patch11 &&
        ! grep "^To: Someone <someone@out.there>\$" patch11 &&
        grep "^To: Someone Else <else@out.there>\$" patch11
 '
@@ -205,15 +215,17 @@ test_expect_success '--no-cc overrides config.cc' '
                "C. E. Cipient <rcipient@example.com>" &&
        git format-patch --no-cc --stdout master..side |
        sed -e "/^\$/q" >patch12 &&
+       check_patch patch12 &&
        ! grep "^Cc: C. E. Cipient <rcipient@example.com>\$" patch12
 '
 
-test_expect_success '--no-add-headers overrides config.headers' '
+test_expect_success '--no-add-header overrides config.headers' '
 
        git config --replace-all format.headers \
                "Header1: B. E. Cipient <rcipient@example.com>" &&
-       git format-patch --no-add-headers --stdout master..side |
+       git format-patch --no-add-header --stdout master..side |
        sed -e "/^\$/q" >patch13 &&
+       check_patch patch13 &&
        ! grep "^Header1: B. E. Cipient <rcipient@example.com>\$" patch13
 '
 
@@ -445,22 +457,22 @@ test_expect_success 'thread deep cover-letter in-reply-to' '
 '
 
 test_expect_success 'thread via config' '
-       git config format.thread true &&
+       test_config format.thread true &&
        check_threading expect.thread master
 '
 
 test_expect_success 'thread deep via config' '
-       git config format.thread deep &&
+       test_config format.thread deep &&
        check_threading expect.deep master
 '
 
 test_expect_success 'thread config + override' '
-       git config format.thread deep &&
+       test_config format.thread deep &&
        check_threading expect.thread --thread master
 '
 
 test_expect_success 'thread config + --no-thread' '
-       git config format.thread deep &&
+       test_config format.thread deep &&
        check_threading expect.no-threading --no-thread master
 '
 
@@ -480,6 +492,7 @@ test_expect_success 'cover-letter inherits diff options' '
        git mv file foo &&
        git commit -m foo &&
        git format-patch --cover-letter -1 &&
+       check_patch 0000-cover-letter.patch &&
        ! grep "file => foo .* 0 *\$" 0000-cover-letter.patch &&
        git format-patch --cover-letter -1 -M &&
        grep "file => foo .* 0 *\$" 0000-cover-letter.patch
@@ -657,6 +670,7 @@ test_expect_success 'format-patch --no-signature ignores format.signature' '
        git config format.signature "config sig" &&
        git format-patch --stdout --signature="my sig" --no-signature \
                -1 >output &&
+       check_patch output &&
        ! grep "config sig" output &&
        ! grep "my sig" output &&
        ! grep "^-- \$" output
@@ -673,17 +687,20 @@ test_expect_success 'format-patch --signature --cover-letter' '
 test_expect_success 'format.signature="" supresses signatures' '
        git config format.signature "" &&
        git format-patch --stdout -1 >output &&
+       check_patch output &&
        ! grep "^-- \$" output
 '
 
 test_expect_success 'format-patch --no-signature supresses signatures' '
        git config --unset-all format.signature &&
        git format-patch --stdout --no-signature -1 >output &&
+       check_patch output &&
        ! grep "^-- \$" output
 '
 
 test_expect_success 'format-patch --signature="" supresses signatures' '
-       git format-patch --signature="" -1 >output &&
+       git format-patch --stdout --signature="" -1 >output &&
+       check_patch output &&
        ! grep "^-- \$" output
 '
 
@@ -869,4 +886,12 @@ test_expect_success 'empty subject prefix does not have extra space' '
        test_cmp expect actual
 '
 
+test_expect_success 'format patch ignores color.ui' '
+       test_unconfig color.ui &&
+       git format-patch --stdout -1 >expect &&
+       test_config color.ui always &&
+       git format-patch --stdout -1 >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 1eb14989df0bffe7d41111607d67da6a90c1fbf5..3c9932edf3f3dd44eaef98c0509a6cbe4b0091f5 100755 (executable)
 test_description='patience diff algorithm'
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-diff-alternative.sh
 
-cat >file1 <<\EOF
-#include <stdio.h>
+test_diff_frobnitz "patience"
 
-// Frobs foo heartily
-int frobnitz(int foo)
-{
-    int i;
-    for(i = 0; i < 10; i++)
-    {
-        printf("Your answer is: ");
-        printf("%d\n", foo);
-    }
-}
-
-int fact(int n)
-{
-    if(n > 1)
-    {
-        return fact(n-1) * n;
-    }
-    return 1;
-}
-
-int main(int argc, char **argv)
-{
-    frobnitz(fact(10));
-}
-EOF
-
-cat >file2 <<\EOF
-#include <stdio.h>
-
-int fib(int n)
-{
-    if(n > 2)
-    {
-        return fib(n-1) + fib(n-2);
-    }
-    return 1;
-}
-
-// Frobs foo heartily
-int frobnitz(int foo)
-{
-    int i;
-    for(i = 0; i < 10; i++)
-    {
-        printf("%d\n", foo);
-    }
-}
-
-int main(int argc, char **argv)
-{
-    frobnitz(fib(10));
-}
-EOF
-
-cat >expect <<\EOF
-diff --git a/file1 b/file2
-index 6faa5a3..e3af329 100644
---- a/file1
-+++ b/file2
-@@ -1,26 +1,25 @@
- #include <stdio.h>
-+int fib(int n)
-+{
-+    if(n > 2)
-+    {
-+        return fib(n-1) + fib(n-2);
-+    }
-+    return 1;
-+}
-+
- // Frobs foo heartily
- int frobnitz(int foo)
- {
-     int i;
-     for(i = 0; i < 10; i++)
-     {
--        printf("Your answer is: ");
-         printf("%d\n", foo);
-     }
- }
--int fact(int n)
--{
--    if(n > 1)
--    {
--        return fact(n-1) * n;
--    }
--    return 1;
--}
--
- int main(int argc, char **argv)
- {
--    frobnitz(fact(10));
-+    frobnitz(fib(10));
- }
-EOF
-
-test_expect_success 'patience diff' '
-
-       test_must_fail git diff --no-index --patience file1 file2 > output &&
-       test_cmp expect output
-
-'
-
-test_expect_success 'patience diff output is valid' '
-
-       mv file2 expect &&
-       git apply < output &&
-       test_cmp expect file2
-
-'
-
-cat >uniq1 <<\EOF
-1
-2
-3
-4
-5
-6
-EOF
-
-cat >uniq2 <<\EOF
-a
-b
-c
-d
-e
-f
-EOF
-
-cat >expect <<\EOF
-diff --git a/uniq1 b/uniq2
-index b414108..0fdf397 100644
---- a/uniq1
-+++ b/uniq2
-@@ -1,6 +1,6 @@
--1
--2
--3
--4
--5
--6
-+a
-+b
-+c
-+d
-+e
-+f
-EOF
-
-test_expect_success 'completely different files' '
-
-       test_must_fail git diff --no-index --patience uniq1 uniq2 > output &&
-       test_cmp expect output
-
-'
+test_diff_unique "patience"
 
 test_done
diff --git a/t/t4048-diff-combined-binary.sh b/t/t4048-diff-combined-binary.sh
new file mode 100755 (executable)
index 0000000..87a8949
--- /dev/null
@@ -0,0 +1,212 @@
+#!/bin/sh
+
+test_description='combined and merge diff handle binary files and textconv'
+. ./test-lib.sh
+
+test_expect_success 'setup binary merge conflict' '
+       echo oneQ1 | q_to_nul >binary &&
+       git add binary &&
+       git commit -m one &&
+       echo twoQ2 | q_to_nul >binary &&
+       git commit -a -m two &&
+       git checkout -b branch-binary HEAD^ &&
+       echo threeQ3 | q_to_nul >binary &&
+       git commit -a -m three &&
+       test_must_fail git merge master &&
+       echo resolvedQhooray | q_to_nul >binary &&
+       git commit -a -m resolved
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --git a/binary b/binary
+index 7ea6ded..9563691 100644
+Binary files a/binary and b/binary differ
+resolved
+
+diff --git a/binary b/binary
+index 6197570..9563691 100644
+Binary files a/binary and b/binary differ
+EOF
+test_expect_success 'diff -m indicates binary-ness' '
+       git show --format=%s -m >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --combined binary
+index 7ea6ded,6197570..9563691
+Binary files differ
+EOF
+test_expect_success 'diff -c indicates binary-ness' '
+       git show --format=%s -c >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --cc binary
+index 7ea6ded,6197570..9563691
+Binary files differ
+EOF
+test_expect_success 'diff --cc indicates binary-ness' '
+       git show --format=%s --cc >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'setup non-binary with binary attribute' '
+       git checkout master &&
+       test_commit one text &&
+       test_commit two text &&
+       git checkout -b branch-text HEAD^ &&
+       test_commit three text &&
+       test_must_fail git merge master &&
+       test_commit resolved text &&
+       echo text -diff >.gitattributes
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --git a/text b/text
+index 2bdf67a..2ab19ae 100644
+Binary files a/text and b/text differ
+resolved
+
+diff --git a/text b/text
+index f719efd..2ab19ae 100644
+Binary files a/text and b/text differ
+EOF
+test_expect_success 'diff -m respects binary attribute' '
+       git show --format=%s -m >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --combined text
+index 2bdf67a,f719efd..2ab19ae
+Binary files differ
+EOF
+test_expect_success 'diff -c respects binary attribute' '
+       git show --format=%s -c >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --cc text
+index 2bdf67a,f719efd..2ab19ae
+Binary files differ
+EOF
+test_expect_success 'diff --cc respects binary attribute' '
+       git show --format=%s --cc >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'setup textconv attribute' '
+       echo "text diff=upcase" >.gitattributes &&
+       git config diff.upcase.textconv "tr a-z A-Z <"
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --git a/text b/text
+index 2bdf67a..2ab19ae 100644
+--- a/text
++++ b/text
+@@ -1 +1 @@
+-THREE
++RESOLVED
+resolved
+
+diff --git a/text b/text
+index f719efd..2ab19ae 100644
+--- a/text
++++ b/text
+@@ -1 +1 @@
+-TWO
++RESOLVED
+EOF
+test_expect_success 'diff -m respects textconv attribute' '
+       git show --format=%s -m >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --combined text
+index 2bdf67a,f719efd..2ab19ae
+--- a/text
++++ b/text
+@@@ -1,1 -1,1 +1,1 @@@
+- THREE
+ -TWO
+++RESOLVED
+EOF
+test_expect_success 'diff -c respects textconv attribute' '
+       git show --format=%s -c >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --cc text
+index 2bdf67a,f719efd..2ab19ae
+--- a/text
++++ b/text
+@@@ -1,1 -1,1 +1,1 @@@
+- THREE
+ -TWO
+++RESOLVED
+EOF
+test_expect_success 'diff --cc respects textconv attribute' '
+       git show --format=%s --cc >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+diff --combined text
+index 2bdf67a,f719efd..2ab19ae
+--- a/text
++++ b/text
+@@@ -1,1 -1,1 +1,1 @@@
+- three
+ -two
+++resolved
+EOF
+test_expect_success 'diff-tree plumbing does not respect textconv' '
+       git diff-tree HEAD -c -p >full &&
+       tail -n +2 full >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+diff --cc text
+index 2bdf67a,f719efd..0000000
+--- a/text
++++ b/text
+@@@ -1,1 -1,1 +1,5 @@@
+++<<<<<<< HEAD
+ +THREE
+++=======
++ TWO
+++>>>>>>> MASTER
+EOF
+test_expect_success 'diff --cc respects textconv on worktree file' '
+       git reset --hard HEAD^ &&
+       test_must_fail git merge master &&
+       git diff >actual &&
+       test_cmp expect actual
+'
+
+test_done
diff --git a/t/t4049-diff-stat-count.sh b/t/t4049-diff-stat-count.sh
new file mode 100755 (executable)
index 0000000..641e70d
--- /dev/null
@@ -0,0 +1,25 @@
+#!/bin/sh
+# Copyright (c) 2011, Google Inc.
+
+test_description='diff --stat-count'
+. ./test-lib.sh
+
+test_expect_success setup '
+       >a &&
+       >b &&
+       >c &&
+       >d &&
+       git add a b c d &&
+       chmod +x c d &&
+       echo a >a &&
+       echo b >b &&
+       cat >expect <<-\EOF
+        a |    1 +
+        b |    1 +
+        2 files changed, 2 insertions(+), 0 deletions(-)
+       EOF
+       git diff --stat --stat-count=2 >actual &&
+       test_cmp expect actual
+'
+
+test_done
diff --git a/t/t4050-diff-histogram.sh b/t/t4050-diff-histogram.sh
new file mode 100755 (executable)
index 0000000..fd3e86a
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+test_description='histogram diff algorithm'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-diff-alternative.sh
+
+test_diff_frobnitz "histogram"
+
+test_diff_unique "histogram"
+
+test_done
index 850fc96d1f07b19310cb4672ab44374b37b82d67..d7d9ccc1c8c31ef3b2d4763532344d0637a18b5c 100755 (executable)
@@ -96,6 +96,13 @@ test_expect_success setup '
                echo "X-Fake-Field: Line Three" &&
                git format-patch --stdout first | sed -e "1d"
        } | append_cr >patch1-crlf.eml &&
+       {
+               printf "%255s\\n" ""
+               echo "X-Fake-Field: Line One" &&
+               echo "X-Fake-Field: Line Two" &&
+               echo "X-Fake-Field: Line Three" &&
+               git format-patch --stdout first | sed -e "1d"
+       } > patch1-ws.eml &&
 
        sed -n -e "3,\$p" msg >file &&
        git add file &&
@@ -167,6 +174,17 @@ test_expect_success 'am applies patch e-mail not in a mbox with CRLF' '
        test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
 '
 
+test_expect_success 'am applies patch e-mail with preceding whitespace' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       git checkout first &&
+       git am patch1-ws.eml &&
+       ! test -d .git/rebase-apply &&
+       git diff --exit-code second &&
+       test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
+       test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
+'
+
 test_expect_success 'setup: new author and committer' '
        GIT_AUTHOR_NAME="Another Thor" &&
        GIT_AUTHOR_EMAIL="a.thor@example.com" &&
@@ -465,7 +483,7 @@ test_expect_success 'am newline in subject' '
        test_tick &&
        sed -e "s/second/second \\\n foo/" patch1 >patchnl &&
        git am <patchnl >output.out 2>&1 &&
-       grep "^Applying: second \\\n foo$" output.out
+       test_i18ngrep "^Applying: second \\\n foo$" output.out
 '
 
 test_expect_success 'am -q is quiet' '
index c95c4ccc393d0863ad53b6a2a684893282d7d9e6..1176bcccf3b3f3708df04f49bfd084190cd27600 100755 (executable)
@@ -45,8 +45,9 @@ do
 
        test_expect_success "am$with3 --skip continue after failed am$with3" '
                test_must_fail git am$with3 --skip >output &&
-               test "$(grep "^Applying" output)" = "Applying: 6" &&
-               test_cmp file-2-expect file-2 &&
+               test_i18ngrep "^Applying" output >output.applying &&
+               test_i18ngrep "^Applying: 6$" output.applying &&
+               test_i18ncmp file-2-expect file-2 &&
                test ! -f .git/MERGE_RR
        '
 
diff --git a/t/t4254-am-corrupt.sh b/t/t4254-am-corrupt.sh
new file mode 100755 (executable)
index 0000000..b7da95f
--- /dev/null
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+test_description='git am with corrupt input'
+. ./test-lib.sh
+
+# Note the missing "+++" line:
+cat > bad-patch.diff <<'EOF'
+From: A U Thor <au.thor@example.com>
+diff --git a/f b/f
+index 7898192..6178079 100644
+--- a/f
+@@ -1 +1 @@
+-a
++b
+EOF
+
+test_expect_success setup '
+       test $? = 0 &&
+       echo a > f &&
+       git add f &&
+       test_tick &&
+       git commit -m initial
+'
+
+# This used to fail before, too, but with a different diagnostic.
+#   fatal: unable to write file '(null)' mode 100644: Bad address
+# Also, it had the unwanted side-effect of deleting f.
+test_expect_success 'try to apply corrupted patch' '
+       git am bad-patch.diff 2> actual
+       test $? = 1
+'
+
+cat > expected <<EOF
+fatal: git diff header lacks filename information (line 4)
+EOF
+
+test_expect_success 'compare diagnostic; ensure file is still here' '
+       test $? = 0 &&
+       test -f f &&
+       test_cmp expected actual
+'
+
+test_done
index cff1b3e0502e96ff6232611ef70c24752a970a96..d9068981f8475c37d30e584bc6b795075a2f3063 100755 (executable)
@@ -26,6 +26,8 @@ commit id embedding:
 
 . ./test-lib.sh
 UNZIP=${UNZIP:-unzip}
+GZIP=${GZIP:-gzip}
+GUNZIP=${GUNZIP:-gzip -d}
 
 SUBSTFORMAT=%H%n
 
@@ -252,4 +254,102 @@ test_expect_success 'git-archive --prefix=olde-' '
        test -f h/olde-a/bin/sh
 '
 
+test_expect_success 'setup tar filters' '
+       git config tar.tar.foo.command "tr ab ba" &&
+       git config tar.bar.command "tr ab ba" &&
+       git config tar.bar.remote true
+'
+
+test_expect_success 'archive --list mentions user filter' '
+       git archive --list >output &&
+       grep "^tar\.foo\$" output &&
+       grep "^bar\$" output
+'
+
+test_expect_success NOT_MINGW 'archive --list shows only enabled remote filters' '
+       git archive --list --remote=. >output &&
+       ! grep "^tar\.foo\$" output &&
+       grep "^bar\$" output
+'
+
+test_expect_success 'invoke tar filter by format' '
+       git archive --format=tar.foo HEAD >config.tar.foo &&
+       tr ab ba <config.tar.foo >config.tar &&
+       test_cmp b.tar config.tar &&
+       git archive --format=bar HEAD >config.bar &&
+       tr ab ba <config.bar >config.tar &&
+       test_cmp b.tar config.tar
+'
+
+test_expect_success 'invoke tar filter by extension' '
+       git archive -o config-implicit.tar.foo HEAD &&
+       test_cmp config.tar.foo config-implicit.tar.foo &&
+       git archive -o config-implicit.bar HEAD &&
+       test_cmp config.tar.foo config-implicit.bar
+'
+
+test_expect_success 'default output format remains tar' '
+       git archive -o config-implicit.baz HEAD &&
+       test_cmp b.tar config-implicit.baz
+'
+
+test_expect_success 'extension matching requires dot' '
+       git archive -o config-implicittar.foo HEAD &&
+       test_cmp b.tar config-implicittar.foo
+'
+
+test_expect_success NOT_MINGW 'only enabled filters are available remotely' '
+       test_must_fail git archive --remote=. --format=tar.foo HEAD \
+               >remote.tar.foo &&
+       git archive --remote=. --format=bar >remote.bar HEAD &&
+       test_cmp remote.bar config.bar
+'
+
+if $GZIP --version >/dev/null 2>&1; then
+       test_set_prereq GZIP
+else
+       say "Skipping some tar.gz tests because gzip not found"
+fi
+
+test_expect_success GZIP 'git archive --format=tgz' '
+       git archive --format=tgz HEAD >j.tgz
+'
+
+test_expect_success GZIP 'git archive --format=tar.gz' '
+       git archive --format=tar.gz HEAD >j1.tar.gz &&
+       test_cmp j.tgz j1.tar.gz
+'
+
+test_expect_success GZIP 'infer tgz from .tgz filename' '
+       git archive --output=j2.tgz HEAD &&
+       test_cmp j.tgz j2.tgz
+'
+
+test_expect_success GZIP 'infer tgz from .tar.gz filename' '
+       git archive --output=j3.tar.gz HEAD &&
+       test_cmp j.tgz j3.tar.gz
+'
+
+if $GUNZIP --version >/dev/null 2>&1; then
+       test_set_prereq GUNZIP
+else
+       say "Skipping some tar.gz tests because gunzip was not found"
+fi
+
+test_expect_success GZIP,GUNZIP 'extract tgz file' '
+       $GUNZIP -c <j.tgz >j.tar &&
+       test_cmp b.tar j.tar
+'
+
+test_expect_success GZIP,NOT_MINGW 'remote tar.gz is allowed by default' '
+       git archive --remote=. --format=tar.gz HEAD >remote.tar.gz &&
+       test_cmp j.tgz remote.tar.gz
+'
+
+test_expect_success GZIP,NOT_MINGW 'remote tar.gz can be disabled' '
+       git config tar.tar.gz.remote false &&
+       test_must_fail git archive --remote=. --format=tar.gz HEAD \
+               >remote.tar.gz
+'
+
 test_done
index b34ea93a8056a7ae0edf111ffe14c99b7c5b33c5..f8fa92446cfc46309468b4ecf142b74b1a812985 100755 (executable)
@@ -65,6 +65,14 @@ test_expect_success \
     'cmp "test-1-${pack1}.idx" "1.idx" &&
      cmp "test-2-${pack2}.idx" "2.idx"'
 
+test_expect_success 'index-pack --verify on index version 1' '
+       git index-pack --verify "test-1-${pack1}.pack"
+'
+
+test_expect_success 'index-pack --verify on index version 2' '
+       git index-pack --verify "test-2-${pack2}.pack"
+'
+
 test_expect_success \
     'index v2: force some 64-bit offsets with pack-objects' \
     'pack3=$(git pack-objects --index-version=2,0x40000 test-3 <obj-list)'
@@ -93,6 +101,16 @@ test_expect_success OFF64_T \
     '64-bit offsets: index-pack result should match pack-objects one' \
     'cmp "test-3-${pack3}.idx" "3.idx"'
 
+test_expect_success OFF64_T 'index-pack --verify on 64-bit offset v2 (cheat)' '
+       # This cheats by knowing which lower offset should still be encoded
+       # in 64-bit representation.
+       git index-pack --verify --index-version=2,0x40000 "test-3-${pack3}.pack"
+'
+
+test_expect_success OFF64_T 'index-pack --verify on 64-bit offset v2' '
+       git index-pack --verify "test-3-${pack3}.pack"
+'
+
 # returns the object number for given object in given pack index
 index_obj_nr()
 {
@@ -208,9 +226,8 @@ test_expect_success \
      ( while read obj
        do git cat-file -p $obj >/dev/null || exit 1
        done <obj-list ) &&
-     err=$(test_must_fail git verify-pack \
-       ".git/objects/pack/pack-${pack1}.pack" 2>&1) &&
-     echo "$err" | grep "CRC mismatch"'
+     test_must_fail git verify-pack ".git/objects/pack/pack-${pack1}.pack"
+'
 
 test_expect_success 'running index-pack in the object store' '
     rm -f .git/objects/pack/* &&
index 0d0222ea2ace733ffe3fbfbdee58520abcd40e92..e8af615e6dcdf365416ef9f71825c17a0d601186 100755 (executable)
@@ -631,6 +631,37 @@ test_expect_success 'rename a remote' '
 
 '
 
+test_expect_success 'rename does not update a non-default fetch refspec' '
+
+       git clone one four.one &&
+       (cd four.one &&
+        git config remote.origin.fetch +refs/heads/*:refs/heads/origin/* &&
+        git remote rename origin upstream &&
+        test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/heads/origin/*" &&
+        git rev-parse -q origin/master)
+
+'
+
+test_expect_success 'rename a remote with name part of fetch spec' '
+
+       git clone one four.two &&
+       (cd four.two &&
+        git remote rename origin remote &&
+        git remote rename remote upstream &&
+        test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/remotes/upstream/*")
+
+'
+
+test_expect_success 'rename a remote with name prefix of other remote' '
+
+       git clone one four.three &&
+       (cd four.three &&
+        git remote add o git://example.com/repo.git &&
+        git remote rename o upstream &&
+        test "$(git rev-parse origin/master)" = "$(git rev-parse master)")
+
+'
+
 cat > remotes_origin << EOF
 URL: $(pwd)/one
 Push: refs/heads/master:refs/heads/upstream
diff --git a/t/t5509-fetch-push-namespaces.sh b/t/t5509-fetch-push-namespaces.sh
new file mode 100755 (executable)
index 0000000..cc0b31f
--- /dev/null
@@ -0,0 +1,85 @@
+#!/bin/sh
+
+test_description='fetch/push involving ref namespaces'
+. ./test-lib.sh
+
+test_expect_success setup '
+       test_tick &&
+       git init original &&
+       (
+               cd original &&
+               echo 0 >count &&
+               git add count &&
+               test_commit 0 &&
+               echo 1 >count &&
+               git add count &&
+               test_commit 1 &&
+               git remote add pushee-namespaced "ext::git --namespace=namespace %s ../pushee" &&
+               git remote add pushee-unnamespaced ../pushee
+       ) &&
+       commit0=$(cd original && git rev-parse HEAD^) &&
+       commit1=$(cd original && git rev-parse HEAD) &&
+       git init pushee &&
+       git init puller
+'
+
+test_expect_success 'pushing into a repository using a ref namespace' '
+       (
+               cd original &&
+               git push pushee-namespaced master &&
+               git ls-remote pushee-namespaced >actual &&
+               printf "$commit1\trefs/heads/master\n" >expected &&
+               test_cmp expected actual &&
+               git push pushee-namespaced --tags &&
+               git ls-remote pushee-namespaced >actual &&
+               printf "$commit0\trefs/tags/0\n" >>expected &&
+               printf "$commit1\trefs/tags/1\n" >>expected &&
+               test_cmp expected actual &&
+               # Verify that the GIT_NAMESPACE environment variable works as well
+               GIT_NAMESPACE=namespace git ls-remote "ext::git %s ../pushee" >actual &&
+               test_cmp expected actual &&
+               # Verify that --namespace overrides GIT_NAMESPACE
+               GIT_NAMESPACE=garbage git ls-remote pushee-namespaced >actual &&
+               test_cmp expected actual &&
+               # Try a namespace with no content
+               git ls-remote "ext::git --namespace=garbage %s ../pushee" >actual &&
+               test_cmp /dev/null actual &&
+               git ls-remote pushee-unnamespaced >actual &&
+               sed -e "s|refs/|refs/namespaces/namespace/refs/|" expected >expected.unnamespaced &&
+               test_cmp expected.unnamespaced actual
+       )
+'
+
+test_expect_success 'pulling from a repository using a ref namespace' '
+       (
+               cd puller &&
+               git remote add -f pushee-namespaced "ext::git --namespace=namespace %s ../pushee" &&
+               git for-each-ref refs/ >actual &&
+               printf "$commit1 commit\trefs/remotes/pushee-namespaced/master\n" >expected &&
+               printf "$commit0 commit\trefs/tags/0\n" >>expected &&
+               printf "$commit1 commit\trefs/tags/1\n" >>expected &&
+               test_cmp expected actual
+       )
+'
+
+# This test with clone --mirror checks for possible regressions in clone
+# or the machinery underneath it. It ensures that no future change
+# causes clone to ignore refs in refs/namespaces/*. In particular, it
+# protects against a regression caused by any future change to the refs
+# machinery that might cause it to ignore refs outside of refs/heads/*
+# or refs/tags/*. More generally, this test also checks the high-level
+# functionality of using clone --mirror to back up a set of repos hosted
+# in the namespaces of a single repo.
+test_expect_success 'mirroring a repository using a ref namespace' '
+       git clone --mirror pushee mirror &&
+       (
+               cd mirror &&
+               git for-each-ref refs/ >actual &&
+               printf "$commit1 commit\trefs/namespaces/namespace/refs/heads/master\n" >expected &&
+               printf "$commit0 commit\trefs/namespaces/namespace/refs/tags/0\n" >>expected &&
+               printf "$commit1 commit\trefs/namespaces/namespace/refs/tags/1\n" >>expected &&
+               test_cmp expected actual
+       )
+'
+
+test_done
index a1fddd4d15fb2c3cb5d0ab6e09a461314fb4b1f1..ca5b027c557014cff85ca4ba7d118db578a57256 100755 (executable)
@@ -47,7 +47,7 @@ test_expect_success setup '
                git init &&
                echo subcontent > subfile &&
                git add subfile &&
-               git submodule add "$pwd/deepsubmodule" deepsubmodule &&
+               git submodule add "$pwd/deepsubmodule" subdir/deepsubmodule &&
                git commit -a -m new
        ) &&
        git submodule add "$pwd/submodule" submodule &&
@@ -58,7 +58,7 @@ test_expect_success setup '
                git submodule update --init --recursive
        ) &&
        echo "Fetching submodule submodule" > expect.out &&
-       echo "Fetching submodule submodule/deepsubmodule" >> expect.out
+       echo "Fetching submodule submodule/subdir/deepsubmodule" >> expect.out
 '
 
 test_expect_success "fetch --recurse-submodules recurses into submodules" '
@@ -277,12 +277,12 @@ test_expect_success "Recursion picks up all submodules when necessary" '
        (
                cd submodule &&
                (
-                       cd deepsubmodule &&
+                       cd subdir/deepsubmodule &&
                        git fetch &&
                        git checkout -q FETCH_HEAD
                ) &&
                head1=$(git rev-parse --short HEAD^) &&
-               git add deepsubmodule &&
+               git add subdir/deepsubmodule &&
                git commit -m "new deepsubmodule"
                head2=$(git rev-parse --short HEAD) &&
                echo "From $pwd/submodule" > ../expect.err.sub &&
@@ -309,12 +309,12 @@ test_expect_success "'--recurse-submodules=on-demand' doesn't recurse when no ne
        (
                cd submodule &&
                (
-                       cd deepsubmodule &&
+                       cd subdir/deepsubmodule &&
                        git fetch &&
                        git checkout -q FETCH_HEAD
                ) &&
                head1=$(git rev-parse --short HEAD^) &&
-               git add deepsubmodule &&
+               git add subdir/deepsubmodule &&
                git commit -m "new deepsubmodule"
                head2=$(git rev-parse --short HEAD) &&
                echo "From $pwd/submodule" > ../expect.err.sub &&
@@ -345,13 +345,13 @@ test_expect_success "'--recurse-submodules=on-demand' recurses as deep as necess
                git config fetch.recurseSubmodules false &&
                (
                        cd submodule &&
-                       git config -f .gitmodules submodule.deepsubmodule.fetchRecursive false
+                       git config -f .gitmodules submodule.subdir/deepsubmodule.fetchRecursive false
                ) &&
                git fetch --recurse-submodules=on-demand >../actual.out 2>../actual.err &&
                git config --unset fetch.recurseSubmodules
                (
                        cd submodule &&
-                       git config --unset -f .gitmodules submodule.deepsubmodule.fetchRecursive
+                       git config --unset -f .gitmodules submodule.subdir/deepsubmodule.fetchRecursive
                )
        ) &&
        test_i18ncmp expect.out actual.out &&
index faa2e9633783e96609f4a31beb4bcccedb876338..30bec4b5f9f286cd97c57b70e3635c6c5c8cf85a 100755 (executable)
@@ -32,4 +32,91 @@ test_expect_success push '
        )
 '
 
+test_expect_success 'push if submodule has no remote' '
+       (
+               cd work/gar/bage &&
+               >junk2 &&
+               git add junk2 &&
+               git commit -m "Second junk"
+       ) &&
+       (
+               cd work &&
+               git add gar/bage &&
+               git commit -m "Second commit for gar/bage" &&
+               git push --recurse-submodules=check ../pub.git master
+       )
+'
+
+test_expect_success 'push fails if submodule commit not on remote' '
+       (
+               cd work/gar &&
+               git clone --bare bage ../../submodule.git &&
+               cd bage &&
+               git remote add origin ../../../submodule.git &&
+               git fetch &&
+               >junk3 &&
+               git add junk3 &&
+               git commit -m "Third junk"
+       ) &&
+       (
+               cd work &&
+               git add gar/bage &&
+               git commit -m "Third commit for gar/bage" &&
+               test_must_fail git push --recurse-submodules=check ../pub.git master
+       )
+'
+
+test_expect_success 'push succeeds after commit was pushed to remote' '
+       (
+               cd work/gar/bage &&
+               git push origin master
+       ) &&
+       (
+               cd work &&
+               git push --recurse-submodules=check ../pub.git master
+       )
+'
+
+test_expect_success 'push fails when commit on multiple branches if one branch has no remote' '
+       (
+               cd work/gar/bage &&
+               >junk4 &&
+               git add junk4 &&
+               git commit -m "Fourth junk"
+       ) &&
+       (
+               cd work &&
+               git branch branch2 &&
+               git add gar/bage &&
+               git commit -m "Fourth commit for gar/bage" &&
+               git checkout branch2 &&
+               (
+                       cd gar/bage &&
+                       git checkout HEAD~1
+               ) &&
+               >junk1 &&
+               git add junk1 &&
+               git commit -m "First junk" &&
+               test_must_fail git push --recurse-submodules=check ../pub.git
+       )
+'
+
+test_expect_success 'push succeeds if submodule has no remote and is on the first superproject commit' '
+       git init --bare a
+       git clone a a1 &&
+       (
+               cd a1 &&
+               git init b
+               (
+                       cd b &&
+                       >junk &&
+                       git add junk &&
+                       git commit -m "initial"
+               ) &&
+               git add b &&
+               git commit -m "added submodule" &&
+               git push --recurse-submodule=check origin master
+       )
+'
+
 test_done
index a266ca56361347fe751495e1531b5a26e4733493..64767d87055c9ad65a225432b5193497c9fad405 100755 (executable)
@@ -132,8 +132,12 @@ x38="$x5$x5$x5$x5$x5$x5$x5$x1$x1$x1"
 x40="$x38$x2"
 
 test_expect_success 'PUT and MOVE sends object to URLs with SHA-1 hash suffix' '
-       sed -e "s/PUT /OP /" -e "s/MOVE /OP /" "$HTTPD_ROOT_PATH"/access.log |
-       grep -e "\"OP .*/objects/$x2/${x38}_$x40 HTTP/[.0-9]*\" 20[0-9] "
+       sed \
+               -e "s/PUT /OP /" \
+               -e "s/MOVE /OP /" \
+           -e "s|/objects/$x2/${x38}_$x40|WANTED_PATH_REQUEST|" \
+               "$HTTPD_ROOT_PATH"/access.log |
+       grep -e "\"OP .*WANTED_PATH_REQUEST HTTP/[.0-9]*\" 20[0-9] "
 
 '
 
index 151ea531bdfeb5a012e57407749beb4f26de6d2a..e8103144bb026afb12f5b058b9ec399b70abebbd 100755 (executable)
@@ -202,9 +202,36 @@ test_expect_success 'clone separate gitdir: output' '
        test_cmp expected dst/.git
 '
 
+test_expect_success 'clone from .git file' '
+       git clone dst/.git dst2
+'
+
 test_expect_success 'clone separate gitdir where target already exists' '
        rm -rf dst &&
        test_must_fail git clone --separate-git-dir realgitdir src dst
 '
 
+test_expect_success 'clone --reference from original' '
+       git clone --shared --bare src src-1 &&
+       git clone --bare src src-2 &&
+       git clone --reference=src-2 --bare src-1 target-8 &&
+       grep /src-2/ target-8/objects/info/alternates
+'
+
+test_expect_success 'clone with more than one --reference' '
+       git clone --bare src src-3 &&
+       git clone --bare src src-4 &&
+       git clone --reference=src-3 --reference=src-4 src target-9 &&
+       grep /src-3/ target-9/.git/objects/info/alternates &&
+       grep /src-4/ target-9/.git/objects/info/alternates
+'
+
+test_expect_success 'clone from original with relative alternate' '
+       mkdir nest &&
+       git clone --bare src nest/src-5 &&
+       echo ../../../src/.git/objects >nest/src-5/objects/info/alternates &&
+       git clone --bare nest/src-5 target-10 &&
+       grep /src/\\.git/objects target-10/objects/info/alternates
+'
+
 test_done
diff --git a/t/t5707-clone-detached.sh b/t/t5707-clone-detached.sh
new file mode 100755 (executable)
index 0000000..8b0d607
--- /dev/null
@@ -0,0 +1,76 @@
+#!/bin/sh
+
+test_description='test cloning a repository with detached HEAD'
+. ./test-lib.sh
+
+head_is_detached() {
+       git --git-dir=$1/.git rev-parse --verify HEAD &&
+       test_must_fail git --git-dir=$1/.git symbolic-ref HEAD
+}
+
+test_expect_success 'setup' '
+       echo one >file &&
+       git add file &&
+       git commit -m one &&
+       echo two >file &&
+       git commit -a -m two &&
+       git tag two &&
+       echo three >file &&
+       git commit -a -m three
+'
+
+test_expect_success 'clone repo (detached HEAD points to branch)' '
+       git checkout master^0 &&
+       git clone "file://$PWD" detached-branch
+'
+test_expect_success 'cloned HEAD matches' '
+       echo three >expect &&
+       git --git-dir=detached-branch/.git log -1 --format=%s >actual &&
+       test_cmp expect actual
+'
+test_expect_failure 'cloned HEAD is detached' '
+       head_is_detached detached-branch
+'
+
+test_expect_success 'clone repo (detached HEAD points to tag)' '
+       git checkout two^0 &&
+       git clone "file://$PWD" detached-tag
+'
+test_expect_success 'cloned HEAD matches' '
+       echo two >expect &&
+       git --git-dir=detached-tag/.git log -1 --format=%s >actual &&
+       test_cmp expect actual
+'
+test_expect_success 'cloned HEAD is detached' '
+       head_is_detached detached-tag
+'
+
+test_expect_success 'clone repo (detached HEAD points to history)' '
+       git checkout two^ &&
+       git clone "file://$PWD" detached-history
+'
+test_expect_success 'cloned HEAD matches' '
+       echo one >expect &&
+       git --git-dir=detached-history/.git log -1 --format=%s >actual &&
+       test_cmp expect actual
+'
+test_expect_success 'cloned HEAD is detached' '
+       head_is_detached detached-history
+'
+
+test_expect_success 'clone repo (orphan detached HEAD)' '
+       git checkout master^0 &&
+       echo four >file &&
+       git commit -a -m four &&
+       git clone "file://$PWD" detached-orphan
+'
+test_expect_success 'cloned HEAD matches' '
+       echo four >expect &&
+       git --git-dir=detached-orphan/.git log -1 --format=%s >actual &&
+       test_cmp expect actual
+'
+test_expect_success 'cloned HEAD is detached' '
+       head_is_detached detached-orphan
+'
+
+test_done
diff --git a/t/t5708-clone-config.sh b/t/t5708-clone-config.sh
new file mode 100755 (executable)
index 0000000..27d730c
--- /dev/null
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+test_description='tests for git clone -c key=value'
+. ./test-lib.sh
+
+test_expect_success 'clone -c sets config in cloned repo' '
+       rm -rf child &&
+       git clone -c core.foo=bar . child &&
+       echo bar >expect &&
+       git --git-dir=child/.git config core.foo >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'clone -c can set multi-keys' '
+       rm -rf child &&
+       git clone -c core.foo=bar -c core.foo=baz . child &&
+       { echo bar; echo baz; } >expect &&
+       git --git-dir=child/.git config --get-all core.foo >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'clone -c without a value is boolean true' '
+       rm -rf child &&
+       git clone -c core.foo . child &&
+       echo true >expect &&
+       git --git-dir=child/.git config --bool core.foo >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'clone -c config is available during clone' '
+       echo content >file &&
+       git add file &&
+       git commit -m one &&
+       rm -rf child &&
+       git clone -c core.autocrlf . child &&
+       printf "content\\r\\n" >expect &&
+       test_cmp expect child/file
+'
+
+test_done
index 1fb6380fceb294f1a7cc73359f5e39740260d8f0..1c62001fce76a73cc951dd18bcf6cb6f650f69e8 100755 (executable)
@@ -7,17 +7,27 @@ test_description='Test remote-helper import and export commands'
 
 . ./test-lib.sh
 
-if test_have_prereq PYTHON && "$PYTHON_PATH" -c '
+if ! test_have_prereq PYTHON ; then
+       skip_all='skipping git-remote-hg tests, python not available'
+       test_done
+fi
+
+"$PYTHON_PATH" -c '
 import sys
 if sys.hexversion < 0x02040000:
     sys.exit(1)
-'
-then
-    # Requires Python 2.4 or newer
-       test_set_prereq PYTHON_24
-fi
+' || {
+       skip_all='skipping git-remote-hg tests, python version < 2.4'
+       test_done
+}
+
+compare_refs() {
+       git --git-dir="$1/.git" rev-parse --verify $2 >expect &&
+       git --git-dir="$3/.git" rev-parse --verify $4 >actual &&
+       test_cmp expect actual
+}
 
-test_expect_success PYTHON_24 'setup repository' '
+test_expect_success 'setup repository' '
        git init --bare server/.git &&
        git clone server public &&
        (cd public &&
@@ -27,54 +37,99 @@ test_expect_success PYTHON_24 'setup repository' '
         git push origin master)
 '
 
-test_expect_success PYTHON_24 'cloning from local repo' '
+test_expect_success 'cloning from local repo' '
        git clone "testgit::${PWD}/server" localclone &&
        test_cmp public/file localclone/file
 '
 
-test_expect_success PYTHON_24 'cloning from remote repo' '
+test_expect_success 'cloning from remote repo' '
        git clone "testgit::file://${PWD}/server" clone &&
        test_cmp public/file clone/file
 '
 
-test_expect_success PYTHON_24 'create new commit on remote' '
+test_expect_success 'create new commit on remote' '
        (cd public &&
         echo content >>file &&
         git commit -a -m two &&
         git push)
 '
 
-test_expect_success PYTHON_24 'pulling from local repo' '
+test_expect_success 'pulling from local repo' '
        (cd localclone && git pull) &&
        test_cmp public/file localclone/file
 '
 
-test_expect_success PYTHON_24 'pulling from remote remote' '
+test_expect_success 'pulling from remote remote' '
        (cd clone && git pull) &&
        test_cmp public/file clone/file
 '
 
-test_expect_success PYTHON_24 'pushing to local repo' '
+test_expect_success 'pushing to local repo' '
        (cd localclone &&
        echo content >>file &&
        git commit -a -m three &&
        git push) &&
-       HEAD=$(git --git-dir=localclone/.git rev-parse --verify HEAD) &&
-       test $HEAD = $(git --git-dir=server/.git rev-parse --verify HEAD)
+       compare_refs localclone HEAD server HEAD
 '
 
-test_expect_success PYTHON_24 'synch with changes from localclone' '
+test_expect_success 'synch with changes from localclone' '
        (cd clone &&
         git pull)
 '
 
-test_expect_success PYTHON_24 'pushing remote local repo' '
+test_expect_success 'pushing remote local repo' '
        (cd clone &&
        echo content >>file &&
        git commit -a -m four &&
        git push) &&
-       HEAD=$(git --git-dir=clone/.git rev-parse --verify HEAD) &&
-       test $HEAD = $(git --git-dir=server/.git rev-parse --verify HEAD)
+       compare_refs clone HEAD server HEAD
+'
+
+test_expect_success 'fetch new branch' '
+       (cd public &&
+        git checkout -b new &&
+        echo content >>file &&
+        git commit -a -m five &&
+        git push origin new
+       ) &&
+       (cd localclone &&
+        git fetch origin new
+       ) &&
+       compare_refs public HEAD localclone FETCH_HEAD
+'
+
+test_expect_success 'fetch multiple branches' '
+       (cd localclone &&
+        git fetch
+       ) &&
+       compare_refs server master localclone refs/remotes/origin/master &&
+       compare_refs server new localclone refs/remotes/origin/new
+'
+
+test_expect_success 'push when remote has extra refs' '
+       (cd clone &&
+        echo content >>file &&
+        git commit -a -m six &&
+        git push
+       ) &&
+       compare_refs clone master server master
+'
+
+test_expect_success 'push new branch by name' '
+       (cd clone &&
+        git checkout -b new-name  &&
+        echo content >>file &&
+        git commit -a -m seven &&
+        git push origin new-name
+       ) &&
+       compare_refs clone HEAD server refs/heads/new-name
+'
+
+test_expect_failure 'push new branch with old:new refspec' '
+       (cd clone &&
+        git push origin new-name:new-refspec
+       ) &&
+       compare_refs clone HEAD server refs/heads/new-refspec
 '
 
 test_done
index 76410293b34ecede219445e09b1eccb8175ee115..39b4cb0ecdc8801b355b815a0566c6e2df5c5a39 100755 (executable)
@@ -70,4 +70,42 @@ test_expect_success 'rev-list --ancestry-patch D..M -- M.t' '
        test_cmp expect actual
 '
 
+#   b---bc
+#  / \ /
+# a   X
+#  \ / \
+#   c---cb
+#
+# All refnames prefixed with 'x' to avoid confusion with the tags
+# generated by test_commit on case-insensitive systems.
+test_expect_success 'setup criss-cross' '
+       mkdir criss-cross &&
+       (cd criss-cross &&
+        git init &&
+        test_commit A &&
+        git checkout -b xb master &&
+        test_commit B &&
+        git checkout -b xc master &&
+        test_commit C &&
+        git checkout -b xbc xb -- &&
+        git merge xc &&
+        git checkout -b xcb xc -- &&
+        git merge xb &&
+        git checkout master)
+'
+
+# no commits in bc descend from cb
+test_expect_success 'criss-cross: rev-list --ancestry-path cb..bc' '
+       (cd criss-cross &&
+        git rev-list --ancestry-path xcb..xbc > actual &&
+        test -z "$(cat actual)")
+'
+
+# no commits in repository descend from cb
+test_expect_success 'criss-cross: rev-list --ancestry-path --all ^cb' '
+       (cd criss-cross &&
+        git rev-list --ancestry-path --all ^xcb > actual &&
+        test -z "$(cat actual)")
+'
+
 test_done
index eec8f4e3edd85e350f5b8d8c86415317cce2aba1..27c3d73961dfc9b50c3eafc7360662076cb31260 100755 (executable)
@@ -59,15 +59,19 @@ test_expect_success 'setup modify/delete + directory/file conflict' '
        git add letters &&
        git commit -m initial &&
 
+       # Throw in letters.txt for sorting order fun
+       # ("letters.txt" sorts between "letters" and "letters/file")
        echo i >>letters &&
-       git add letters &&
+       echo "version 2" >letters.txt &&
+       git add letters letters.txt &&
        git commit -m modified &&
 
        git checkout -b delete HEAD^ &&
        git rm letters &&
        mkdir letters &&
        >letters/file &&
-       git add letters &&
+       echo "version 1" >letters.txt &&
+       git add letters letters.txt &&
        git commit -m deleted
 '
 
@@ -75,25 +79,31 @@ test_expect_success 'modify/delete + directory/file conflict' '
        git checkout delete^0 &&
        test_must_fail git merge modify &&
 
-       test 3 = $(git ls-files -s | wc -l) &&
-       test 2 = $(git ls-files -u | wc -l) &&
-       test 1 = $(git ls-files -o | wc -l) &&
+       test 5 -eq $(git ls-files -s | wc -l) &&
+       test 4 -eq $(git ls-files -u | wc -l) &&
+       test 1 -eq $(git ls-files -o | wc -l) &&
 
        test -f letters/file &&
+       test -f letters.txt &&
        test -f letters~modify
 '
 
 test_expect_success 'modify/delete + directory/file conflict; other way' '
+       # Yes, we really need the double reset since "letters" appears as
+       # both a file and a directory.
+       git reset --hard &&
        git reset --hard &&
        git clean -f &&
        git checkout modify^0 &&
+
        test_must_fail git merge delete &&
 
-       test 3 = $(git ls-files -s | wc -l) &&
-       test 2 = $(git ls-files -u | wc -l) &&
-       test 1 = $(git ls-files -o | wc -l) &&
+       test 5 -eq $(git ls-files -s | wc -l) &&
+       test 4 -eq $(git ls-files -u | wc -l) &&
+       test 1 -eq $(git ls-files -o | wc -l) &&
 
        test -f letters/file &&
+       test -f letters.txt &&
        test -f letters~HEAD
 '
 
index 1ed259d864b4ef4c8c7060fa5f22634a25c8e032..9d8584e957a26cadda2f04d38d27fd0c4b97ae29 100755 (executable)
@@ -252,6 +252,7 @@ test_expect_success 'setup for rename + d/f conflicts' '
        git reset --hard &&
        git checkout --orphan dir-in-way &&
        git rm -rf . &&
+       git clean -fdqx &&
 
        mkdir sub &&
        mkdir dir &&
@@ -302,11 +303,11 @@ test_expect_success 'Rename+D/F conflict; renamed file merges but dir in way' '
        git checkout -q renamed-file-has-no-conflicts^0 &&
        test_must_fail git merge --strategy=recursive dir-in-way >output &&
 
-       grep "CONFLICT (delete/modify): dir/file-in-the-way" output &&
+       grep "CONFLICT (modify/delete): dir/file-in-the-way" output &&
        grep "Auto-merging dir" output &&
        grep "Adding as dir~HEAD instead" output &&
 
-       test 2 -eq "$(git ls-files -u | wc -l)" &&
+       test 3 -eq "$(git ls-files -u | wc -l)" &&
        test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" &&
 
        test_must_fail git diff --quiet &&
@@ -324,11 +325,11 @@ test_expect_success 'Same as previous, but merged other way' '
        test_must_fail git merge --strategy=recursive renamed-file-has-no-conflicts >output 2>errors &&
 
        ! grep "error: refusing to lose untracked file at" errors &&
-       grep "CONFLICT (delete/modify): dir/file-in-the-way" output &&
+       grep "CONFLICT (modify/delete): dir/file-in-the-way" output &&
        grep "Auto-merging dir" output &&
        grep "Adding as dir~renamed-file-has-no-conflicts instead" output &&
 
-       test 2 -eq "$(git ls-files -u | wc -l)" &&
+       test 3 -eq "$(git ls-files -u | wc -l)" &&
        test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" &&
 
        test_must_fail git diff --quiet &&
@@ -350,11 +351,11 @@ cat >expected <<\EOF &&
 8
 9
 10
-<<<<<<< HEAD
+<<<<<<< HEAD:dir
 12
 =======
 11
->>>>>>> dir-not-in-way
+>>>>>>> dir-not-in-way:sub/file
 EOF
 
 test_expect_success 'Rename+D/F conflict; renamed file cannot merge, dir not in way' '
@@ -404,11 +405,11 @@ cat >expected <<\EOF &&
 8
 9
 10
-<<<<<<< HEAD
+<<<<<<< HEAD:sub/file
 11
 =======
 12
->>>>>>> renamed-file-has-conflicts
+>>>>>>> renamed-file-has-conflicts:dir
 EOF
 
 test_expect_success 'Same as previous, but merged other way' '
@@ -609,4 +610,278 @@ test_expect_success 'check handling of differently renamed file with D/F conflic
        ! test -f original
 '
 
+test_expect_success 'setup avoid unnecessary update, normal rename' '
+       git reset --hard &&
+       git checkout --orphan avoid-unnecessary-update-1 &&
+       git rm -rf . &&
+       git clean -fdqx &&
+
+       printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >original &&
+       git add -A &&
+       git commit -m "Common commmit" &&
+
+       git mv original rename &&
+       echo 11 >>rename &&
+       git add -u &&
+       git commit -m "Renamed and modified" &&
+
+       git checkout -b merge-branch-1 HEAD~1 &&
+       echo "random content" >random-file &&
+       git add -A &&
+       git commit -m "Random, unrelated changes"
+'
+
+test_expect_success 'avoid unnecessary update, normal rename' '
+       git checkout -q avoid-unnecessary-update-1^0 &&
+       test-chmtime =1000000000 rename &&
+       test-chmtime -v +0 rename >expect &&
+       git merge merge-branch-1 &&
+       test-chmtime -v +0 rename >actual &&
+       test_cmp expect actual # "rename" should have stayed intact
+'
+
+test_expect_success 'setup to test avoiding unnecessary update, with D/F conflict' '
+       git reset --hard &&
+       git checkout --orphan avoid-unnecessary-update-2 &&
+       git rm -rf . &&
+       git clean -fdqx &&
+
+       mkdir df &&
+       printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >df/file &&
+       git add -A &&
+       git commit -m "Common commmit" &&
+
+       git mv df/file temp &&
+       rm -rf df &&
+       git mv temp df &&
+       echo 11 >>df &&
+       git add -u &&
+       git commit -m "Renamed and modified" &&
+
+       git checkout -b merge-branch-2 HEAD~1 &&
+       >unrelated-change &&
+       git add unrelated-change &&
+       git commit -m "Only unrelated changes"
+'
+
+test_expect_success 'avoid unnecessary update, with D/F conflict' '
+       git checkout -q avoid-unnecessary-update-2^0 &&
+       test-chmtime =1000000000 df &&
+       test-chmtime -v +0 df >expect &&
+       git merge merge-branch-2 &&
+       test-chmtime -v +0 df >actual &&
+       test_cmp expect actual # "df" should have stayed intact
+'
+
+test_expect_success 'setup avoid unnecessary update, dir->(file,nothing)' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       >irrelevant &&
+       mkdir df &&
+       >df/file &&
+       git add -A &&
+       git commit -mA &&
+
+       git checkout -b side
+       git rm -rf df &&
+       git commit -mB &&
+
+       git checkout master &&
+       git rm -rf df &&
+       echo bla >df &&
+       git add -A &&
+       git commit -m "Add a newfile"
+'
+
+test_expect_success 'avoid unnecessary update, dir->(file,nothing)' '
+       git checkout -q master^0 &&
+       test-chmtime =1000000000 df &&
+       test-chmtime -v +0 df >expect &&
+       git merge side &&
+       test-chmtime -v +0 df >actual &&
+       test_cmp expect actual # "df" should have stayed intact
+'
+
+test_expect_success 'setup avoid unnecessary update, modify/delete' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       >irrelevant &&
+       >file &&
+       git add -A &&
+       git commit -mA &&
+
+       git checkout -b side
+       git rm -f file &&
+       git commit -m "Delete file" &&
+
+       git checkout master &&
+       echo bla >file &&
+       git add -A &&
+       git commit -m "Modify file"
+'
+
+test_expect_success 'avoid unnecessary update, modify/delete' '
+       git checkout -q master^0 &&
+       test-chmtime =1000000000 file &&
+       test-chmtime -v +0 file >expect &&
+       test_must_fail git merge side &&
+       test-chmtime -v +0 file >actual &&
+       test_cmp expect actual # "file" should have stayed intact
+'
+
+test_expect_success 'setup avoid unnecessary update, rename/add-dest' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       printf "1\n2\n3\n4\n5\n6\n7\n8\n" >file &&
+       git add -A &&
+       git commit -mA &&
+
+       git checkout -b side
+       cp file newfile &&
+       git add -A &&
+       git commit -m "Add file copy" &&
+
+       git checkout master &&
+       git mv file newfile &&
+       git commit -m "Rename file"
+'
+
+test_expect_success 'avoid unnecessary update, rename/add-dest' '
+       git checkout -q master^0 &&
+       test-chmtime =1000000000 newfile &&
+       test-chmtime -v +0 newfile >expect &&
+       git merge side &&
+       test-chmtime -v +0 newfile >actual &&
+       test_cmp expect actual # "file" should have stayed intact
+'
+
+test_expect_success 'setup merge of rename + small change' '
+       git reset --hard &&
+       git checkout --orphan rename-plus-small-change &&
+       git rm -rf . &&
+       git clean -fdqx &&
+
+       echo ORIGINAL >file &&
+       git add file &&
+
+       test_tick &&
+       git commit -m Initial &&
+       git checkout -b rename_branch &&
+       git mv file renamed_file &&
+       git commit -m Rename &&
+       git checkout rename-plus-small-change &&
+       echo NEW-VERSION >file &&
+       git commit -a -m Reformat
+'
+
+test_expect_success 'merge rename + small change' '
+       git merge rename_branch &&
+
+       test 1 -eq $(git ls-files -s | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+       test $(git rev-parse HEAD:renamed_file) = $(git rev-parse HEAD~1:file)
+'
+
+test_expect_success 'setup for use of extended merge markers' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       printf "1\n2\n3\n4\n5\n6\n7\n8\n" >original_file &&
+       git add original_file &&
+       git commit -mA &&
+
+       git checkout -b rename &&
+       echo 9 >>original_file &&
+       git add original_file &&
+       git mv original_file renamed_file &&
+       git commit -mB &&
+
+       git checkout master &&
+       echo 8.5 >>original_file &&
+       git add original_file &&
+       git commit -mC
+'
+
+cat >expected <<\EOF &&
+1
+2
+3
+4
+5
+6
+7
+8
+<<<<<<< HEAD:renamed_file
+9
+=======
+8.5
+>>>>>>> master^0:original_file
+EOF
+
+test_expect_success 'merge master into rename has correct extended markers' '
+       git checkout rename^0 &&
+       test_must_fail git merge -s recursive master^0 &&
+       test_cmp expected renamed_file
+'
+
+cat >expected <<\EOF &&
+1
+2
+3
+4
+5
+6
+7
+8
+<<<<<<< HEAD:original_file
+8.5
+=======
+9
+>>>>>>> rename^0:renamed_file
+EOF
+
+test_expect_success 'merge rename into master has correct extended markers' '
+       git reset --hard &&
+       git checkout master^0 &&
+       test_must_fail git merge -s recursive rename^0 &&
+       test_cmp expected renamed_file
+'
+
+test_expect_success 'setup spurious "refusing to lose untracked" message' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       > irrelevant_file &&
+       printf "1\n2\n3\n4\n5\n6\n7\n8\n" >original_file &&
+       git add irrelevant_file original_file &&
+       git commit -mA &&
+
+       git checkout -b rename &&
+       git mv original_file renamed_file &&
+       git commit -mB &&
+
+       git checkout master &&
+       git rm original_file &&
+       git commit -mC
+'
+
+test_expect_success 'no spurious "refusing to lose untracked" message' '
+       git checkout master^0 &&
+       test_must_fail git merge rename^0 2>errors.txt &&
+       ! grep "refusing to lose untracked file" errors.txt
+'
+
 test_done
index d9f343942c7eb9d1e80e98db2252cb29135544fa..432f086c063198e82ad8a2f7c8dd175a8685fdf5 100755 (executable)
@@ -154,7 +154,7 @@ test_expect_success "expected conflict markers" "test_cmp expect out"
 
 test_expect_success 'binary files cannot be merged' '
        test_must_fail git merge-file -p \
-               orig.txt "$TEST_DIRECTORY"/test4012.png new1.txt 2> merge.err &&
+               orig.txt "$TEST_DIRECTORY"/test-binary-1.png new1.txt 2> merge.err &&
        grep "Cannot merge binary files" merge.err
 '
 
index b519626ca09255a433b0d1cb32780a6feafa09f6..07735410b9536ba7639134c7ca0cda4f486f8291 100755 (executable)
@@ -6,7 +6,7 @@ test_description='ask merge-recursive to merge binary files'
 
 test_expect_success setup '
 
-       cat "$TEST_DIRECTORY"/test4012.png >m &&
+       cat "$TEST_DIRECTORY"/test-binary-1.png >m &&
        git add m &&
        git ls-files -s | sed -e "s/ 0  / 1     /" >E1 &&
        test_tick &&
index b5063b6fe6c37f4b41a89b71c6d52ac0b5c07127..c6f1f9f8ab2353ec81401828f8500cb7c1a869fb 100755 (executable)
@@ -126,6 +126,18 @@ test_expect_success 'bisect reset removes packed refs' '
        test -z "$(git for-each-ref "refs/heads/bisect")"
 '
 
+test_expect_success 'bisect reset removes bisect state after --no-checkout' '
+       git bisect reset &&
+       git bisect start --no-checkout &&
+       git bisect good $HASH1 &&
+       git bisect bad $HASH3 &&
+       git bisect next &&
+       git bisect reset &&
+       test -z "$(git for-each-ref "refs/bisect/*")" &&
+       test -z "$(git for-each-ref "refs/heads/bisect")" &&
+       test -z "$(git for-each-ref "BISECT_HEAD")"
+'
+
 test_expect_success 'bisect start: back in good branch' '
        git branch > branch.output &&
        grep "* other" branch.output > /dev/null &&
@@ -138,15 +150,23 @@ test_expect_success 'bisect start: back in good branch' '
        grep "* other" branch.output > /dev/null
 '
 
-test_expect_success 'bisect start: no ".git/BISECT_START" if junk rev' '
-       git bisect start $HASH4 $HASH1 -- &&
-       git bisect good &&
+test_expect_success 'bisect start: no ".git/BISECT_START" created if junk rev' '
+       git bisect reset &&
        test_must_fail git bisect start $HASH4 foo -- &&
        git branch > branch.output &&
        grep "* other" branch.output > /dev/null &&
        test_must_fail test -e .git/BISECT_START
 '
 
+test_expect_success 'bisect start: existing ".git/BISECT_START" not modified if junk rev' '
+       git bisect start $HASH4 $HASH1 -- &&
+       git bisect good &&
+       cp .git/BISECT_START saved &&
+       test_must_fail git bisect start $HASH4 foo -- &&
+       git branch > branch.output &&
+       grep "* (no branch)" branch.output > /dev/null &&
+       test_cmp saved .git/BISECT_START
+'
 test_expect_success 'bisect start: no ".git/BISECT_START" if mistaken rev' '
        git bisect start $HASH4 $HASH1 -- &&
        git bisect good &&
@@ -572,6 +592,155 @@ test_expect_success 'erroring out when using bad path parameters' '
        grep "bad path parameters" error.txt
 '
 
+test_expect_success 'test bisection on bare repo - --no-checkout specified' '
+       git clone --bare . bare.nocheckout &&
+       (
+               cd bare.nocheckout &&
+               git bisect start --no-checkout &&
+               git bisect good $HASH1 &&
+               git bisect bad $HASH4 &&
+               git bisect run eval \
+                       "test \$(git rev-list BISECT_HEAD ^$HASH2 --max-count=1 | wc -l) = 0" \
+                       >../nocheckout.log &&
+               git bisect reset
+       ) &&
+       grep "$HASH3 is the first bad commit" nocheckout.log
+'
+
+
+test_expect_success 'test bisection on bare repo - --no-checkout defaulted' '
+       git clone --bare . bare.defaulted &&
+       (
+               cd bare.defaulted &&
+               git bisect start &&
+               git bisect good $HASH1 &&
+               git bisect bad $HASH4 &&
+               git bisect run eval \
+                       "test \$(git rev-list BISECT_HEAD ^$HASH2 --max-count=1 | wc -l) = 0" \
+                       >../defaulted.log &&
+               git bisect reset
+       ) &&
+       grep "$HASH3 is the first bad commit" defaulted.log
+'
+
 #
+# This creates a broken branch which cannot be checked out because
+# the tree created has been deleted.
 #
+# H1-H2-H3-H4-H5-H6-H7  <--other
+#            \
+#             S5-S6'-S7'-S8'-S9  <--broken
+#
+# Commits marked with ' have a missing tree.
+#
+test_expect_success 'broken branch creation' '
+       git bisect reset &&
+       git checkout -b broken $HASH4 &&
+       git tag BROKEN_HASH4 $HASH4 &&
+       add_line_into_file "5(broken): first line on a broken branch" hello2 &&
+       git tag BROKEN_HASH5 &&
+       mkdir missing &&
+       :> missing/MISSING &&
+       git add missing/MISSING &&
+       git commit -m "6(broken): Added file that will be deleted"
+       git tag BROKEN_HASH6 &&
+       add_line_into_file "7(broken): second line on a broken branch" hello2 &&
+       git tag BROKEN_HASH7 &&
+       add_line_into_file "8(broken): third line on a broken branch" hello2 &&
+       git tag BROKEN_HASH8 &&
+       git rm missing/MISSING &&
+       git commit -m "9(broken): Remove missing file"
+       git tag BROKEN_HASH9 &&
+       rm .git/objects/39/f7e61a724187ab767d2e08442d9b6b9dab587d
+'
+
+echo "" > expected.ok
+cat > expected.missing-tree.default <<EOF
+fatal: unable to read tree 39f7e61a724187ab767d2e08442d9b6b9dab587d
+EOF
+
+test_expect_success 'bisect fails if tree is broken on start commit' '
+       git bisect reset &&
+       test_must_fail git bisect start BROKEN_HASH7 BROKEN_HASH4 2>error.txt &&
+       test_cmp expected.missing-tree.default error.txt
+'
+
+test_expect_success 'bisect fails if tree is broken on trial commit' '
+       git bisect reset &&
+       test_must_fail git bisect start BROKEN_HASH9 BROKEN_HASH4 2>error.txt &&
+       git reset --hard broken &&
+       git checkout broken &&
+       test_cmp expected.missing-tree.default error.txt
+'
+
+check_same()
+{
+       echo "Checking $1 is the same as $2" &&
+       git rev-parse "$1" > expected.same &&
+       git rev-parse "$2" > expected.actual &&
+       test_cmp expected.same expected.actual
+}
+
+test_expect_success 'bisect: --no-checkout - start commit bad' '
+       git bisect reset &&
+       git bisect start BROKEN_HASH7 BROKEN_HASH4 --no-checkout &&
+       check_same BROKEN_HASH6 BISECT_HEAD &&
+       git bisect reset
+'
+
+test_expect_success 'bisect: --no-checkout - trial commit bad' '
+       git bisect reset &&
+       git bisect start broken BROKEN_HASH4 --no-checkout &&
+       check_same BROKEN_HASH6 BISECT_HEAD &&
+       git bisect reset
+'
+
+test_expect_success 'bisect: --no-checkout - target before breakage' '
+       git bisect reset &&
+       git bisect start broken BROKEN_HASH4 --no-checkout &&
+       check_same BROKEN_HASH6 BISECT_HEAD &&
+       git bisect bad BISECT_HEAD &&
+       check_same BROKEN_HASH5 BISECT_HEAD &&
+       git bisect bad BISECT_HEAD &&
+       check_same BROKEN_HASH5 bisect/bad &&
+       git bisect reset
+'
+
+test_expect_success 'bisect: --no-checkout - target in breakage' '
+       git bisect reset &&
+       git bisect start broken BROKEN_HASH4 --no-checkout &&
+       check_same BROKEN_HASH6 BISECT_HEAD &&
+       git bisect bad BISECT_HEAD &&
+       check_same BROKEN_HASH5 BISECT_HEAD &&
+       git bisect good BISECT_HEAD &&
+       check_same BROKEN_HASH6 bisect/bad &&
+       git bisect reset
+'
+
+test_expect_success 'bisect: --no-checkout - target after breakage' '
+       git bisect reset &&
+       git bisect start broken BROKEN_HASH4 --no-checkout &&
+       check_same BROKEN_HASH6 BISECT_HEAD &&
+       git bisect good BISECT_HEAD &&
+       check_same BROKEN_HASH8 BISECT_HEAD &&
+       git bisect good BISECT_HEAD &&
+       check_same BROKEN_HASH9 bisect/bad &&
+       git bisect reset
+'
+
+test_expect_success 'bisect: demonstrate identification of damage boundary' "
+       git bisect reset &&
+       git checkout broken &&
+       git bisect start broken master --no-checkout &&
+       git bisect run \"\$SHELL_PATH\" -c '
+               GOOD=\$(git for-each-ref \"--format=%(objectname)\" refs/bisect/good-*) &&
+               git rev-list --objects BISECT_HEAD --not \$GOOD >tmp.\$\$ &&
+               git pack-objects --stdout >/dev/null < tmp.\$\$
+               rc=\$?
+               rm -f tmp.\$\$
+               test \$rc = 0' &&
+       check_same BROKEN_HASH6 bisect/bad &&
+       git bisect reset
+"
+
 test_done
index 871577d90cc2817ffcc0cfe1ecf76aab6be61723..dfee7d159b3b5198b4773f2d3869e809095b11f7 100755 (executable)
@@ -1,9 +1,15 @@
 #!/bin/sh
 
-test_description='recursive merge corner cases'
+test_description='recursive merge corner cases involving criss-cross merges'
 
 . ./test-lib.sh
 
+get_clean_checkout () {
+       git reset --hard &&
+       git clean -fdqx &&
+       git checkout "$1"
+}
+
 #
 #  L1  L2
 #   o---o
@@ -51,23 +57,15 @@ test_expect_success 'merge simple rename+criss-cross with no modifications' '
 
        test_must_fail git merge -s recursive R2^0 &&
 
-       test 5 = $(git ls-files -s | wc -l) &&
-       test 3 = $(git ls-files -u | wc -l) &&
-       test 0 = $(git ls-files -o | wc -l) &&
+       test 2 = $(git ls-files -s | wc -l) &&
+       test 2 = $(git ls-files -u | wc -l) &&
+       test 2 = $(git ls-files -o | wc -l) &&
 
-       test $(git rev-parse :0:one) = $(git rev-parse L2:one) &&
-       test $(git rev-parse :0:two) = $(git rev-parse R2:two) &&
        test $(git rev-parse :2:three) = $(git rev-parse L2:three) &&
        test $(git rev-parse :3:three) = $(git rev-parse R2:three) &&
 
-       cp two merged &&
-       >empty &&
-       test_must_fail git merge-file \
-               -L "Temporary merge branch 2" \
-               -L "" \
-               -L "Temporary merge branch 1" \
-               merged empty one &&
-       test $(git rev-parse :1:three) = $(git hash-object merged)
+       test $(git rev-parse L2:three) = $(git hash-object three~HEAD) &&
+       test $(git rev-parse R2:three) = $(git hash-object three~R2^0)
 '
 
 #
@@ -126,24 +124,15 @@ test_expect_success 'merge criss-cross + rename merges with basic modification'
 
        test_must_fail git merge -s recursive R2^0 &&
 
-       test 5 = $(git ls-files -s | wc -l) &&
-       test 3 = $(git ls-files -u | wc -l) &&
-       test 0 = $(git ls-files -o | wc -l) &&
+       test 2 = $(git ls-files -s | wc -l) &&
+       test 2 = $(git ls-files -u | wc -l) &&
+       test 2 = $(git ls-files -o | wc -l) &&
 
-       test $(git rev-parse :0:one) = $(git rev-parse L2:one) &&
-       test $(git rev-parse :0:two) = $(git rev-parse R2:two) &&
        test $(git rev-parse :2:three) = $(git rev-parse L2:three) &&
        test $(git rev-parse :3:three) = $(git rev-parse R2:three) &&
 
-       head -n 10 two >merged &&
-       cp one merge-me &&
-       >empty &&
-       test_must_fail git merge-file \
-               -L "Temporary merge branch 2" \
-               -L "" \
-               -L "Temporary merge branch 1" \
-               merged empty merge-me &&
-       test $(git rev-parse :1:three) = $(git hash-object merged)
+       test $(git rev-parse L2:three) = $(git hash-object three~HEAD) &&
+       test $(git rev-parse R2:three) = $(git hash-object three~R2^0)
 '
 
 #
@@ -231,4 +220,557 @@ test_expect_success 'git detects differently handled merges conflict' '
        test $(git rev-parse :1:new_a) = $(git hash-object merged)
 '
 
+#
+# criss-cross + modify/delete:
+#
+#      B   D
+#      o---o
+#     / \ / \
+#  A o   X   ? F
+#     \ / \ /
+#      o---o
+#      C   E
+#
+#   Commit A: file with contents 'A\n'
+#   Commit B: file with contents 'B\n'
+#   Commit C: file not present
+#   Commit D: file with contents 'B\n'
+#   Commit E: file not present
+#
+# Merging commits D & E should result in modify/delete conflict.
+
+test_expect_success 'setup criss-cross + modify/delete resolved differently' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       echo A >file &&
+       git add file &&
+       test_tick &&
+       git commit -m A &&
+
+       git branch B &&
+       git checkout -b C &&
+       git rm file &&
+       test_tick &&
+       git commit -m C &&
+
+       git checkout B &&
+       echo B >file &&
+       git add file &&
+       test_tick &&
+       git commit -m B &&
+
+       git checkout B^0 &&
+       test_must_fail git merge C &&
+       echo B >file &&
+       git add file &&
+       test_tick &&
+       git commit -m D &&
+       git tag D &&
+
+       git checkout C^0 &&
+       test_must_fail git merge B &&
+       git rm file &&
+       test_tick &&
+       git commit -m E &&
+       git tag E
+'
+
+test_expect_success 'git detects conflict merging criss-cross+modify/delete' '
+       git checkout D^0 &&
+
+       test_must_fail git merge -s recursive E^0 &&
+
+       test 2 -eq $(git ls-files -s | wc -l) &&
+       test 2 -eq $(git ls-files -u | wc -l) &&
+
+       test $(git rev-parse :1:file) = $(git rev-parse master:file) &&
+       test $(git rev-parse :2:file) = $(git rev-parse B:file)
+'
+
+test_expect_success 'git detects conflict merging criss-cross+modify/delete, reverse direction' '
+       git reset --hard &&
+       git checkout E^0 &&
+
+       test_must_fail git merge -s recursive D^0 &&
+
+       test 2 -eq $(git ls-files -s | wc -l) &&
+       test 2 -eq $(git ls-files -u | wc -l) &&
+
+       test $(git rev-parse :1:file) = $(git rev-parse master:file) &&
+       test $(git rev-parse :3:file) = $(git rev-parse B:file)
+'
+
+#
+# criss-cross + modify/modify with very contrived file contents:
+#
+#      B   D
+#      o---o
+#     / \ / \
+#  A o   X   ? F
+#     \ / \ /
+#      o---o
+#      C   E
+#
+#   Commit A: file with contents 'A\n'
+#   Commit B: file with contents 'B\n'
+#   Commit C: file with contents 'C\n'
+#   Commit D: file with contents 'D\n'
+#   Commit E: file with contents:
+#      <<<<<<< Temporary merge branch 1
+#      C
+#      =======
+#      B
+#      >>>>>>> Temporary merge branch 2
+#
+# Now, when we merge commits D & E, does git detect the conflict?
+
+test_expect_success 'setup differently handled merges of content conflict' '
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       echo A >file &&
+       git add file &&
+       test_tick &&
+       git commit -m A &&
+
+       git branch B &&
+       git checkout -b C &&
+       echo C >file &&
+       git add file &&
+       test_tick &&
+       git commit -m C &&
+
+       git checkout B &&
+       echo B >file &&
+       git add file &&
+       test_tick &&
+       git commit -m B &&
+
+       git checkout B^0 &&
+       test_must_fail git merge C &&
+       echo D >file &&
+       git add file &&
+       test_tick &&
+       git commit -m D &&
+       git tag D &&
+
+       git checkout C^0 &&
+       test_must_fail git merge B &&
+       cat <<EOF >file &&
+<<<<<<< Temporary merge branch 1
+C
+=======
+B
+>>>>>>> Temporary merge branch 2
+EOF
+       git add file &&
+       test_tick &&
+       git commit -m E &&
+       git tag E
+'
+
+test_expect_failure 'git detects conflict w/ criss-cross+contrived resolution' '
+       git checkout D^0 &&
+
+       test_must_fail git merge -s recursive E^0 &&
+
+       test 3 -eq $(git ls-files -s | wc -l) &&
+       test 3 -eq $(git ls-files -u | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse :2:file) = $(git rev-parse D:file) &&
+       test $(git rev-parse :3:file) = $(git rev-parse E:file)
+'
+
+#
+# criss-cross + d/f conflict via add/add:
+#   Commit A: Neither file 'a' nor directory 'a/' exist.
+#   Commit B: Introduce 'a'
+#   Commit C: Introduce 'a/file'
+#   Commit D: Merge B & C, keeping 'a' and deleting 'a/'
+#
+# Two different later cases:
+#   Commit E1: Merge B & C, deleting 'a' but keeping 'a/file'
+#   Commit E2: Merge B & C, deleting 'a' but keeping a slightly modified 'a/file'
+#
+#      B   D
+#      o---o
+#     / \ / \
+#  A o   X   ? F
+#     \ / \ /
+#      o---o
+#      C   E1 or E2
+#
+# Merging D & E1 requires we first create a virtual merge base X from
+# merging A & B in memory.  Now, if X could keep both 'a' and 'a/file' in
+# the index, then the merge of D & E1 could be resolved cleanly with both
+# 'a' and 'a/file' removed.  Since git does not currently allow creating
+# such a tree, the best we can do is have X contain both 'a~<unique>' and
+# 'a/file' resulting in the merge of D and E1 having a rename/delete
+# conflict for 'a'.  (Although this merge appears to be unsolvable with git
+# currently, git could do a lot better than it currently does with these
+# d/f conflicts, which is the purpose of this test.)
+#
+# Merge of D & E2 has similar issues for path 'a', but should always result
+# in a modify/delete conflict for path 'a/file'.
+#
+# We run each merge in both directions, to check for directional issues
+# with D/F conflict handling.
+#
+
+test_expect_success 'setup differently handled merges of directory/file conflict' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       >ignore-me &&
+       git add ignore-me &&
+       test_tick &&
+       git commit -m A &&
+       git tag A &&
+
+       git branch B &&
+       git checkout -b C &&
+       mkdir a &&
+       echo 10 >a/file &&
+       git add a/file &&
+       test_tick &&
+       git commit -m C &&
+
+       git checkout B &&
+       echo 5 >a &&
+       git add a &&
+       test_tick &&
+       git commit -m B &&
+
+       git checkout B^0 &&
+       test_must_fail git merge C &&
+       git clean -f &&
+       rm -rf a/ &&
+       echo 5 >a &&
+       git add a &&
+       test_tick &&
+       git commit -m D &&
+       git tag D &&
+
+       git checkout C^0 &&
+       test_must_fail git merge B &&
+       git clean -f &&
+       git rm --cached a &&
+       echo 10 >a/file &&
+       git add a/file &&
+       test_tick &&
+       git commit -m E1 &&
+       git tag E1 &&
+
+       git checkout C^0 &&
+       test_must_fail git merge B &&
+       git clean -f &&
+       git rm --cached a &&
+       printf "10\n11\n" >a/file &&
+       git add a/file &&
+       test_tick &&
+       git commit -m E2 &&
+       git tag E2
+'
+
+test_expect_success 'merge of D & E1 fails but has appropriate contents' '
+       get_clean_checkout D^0 &&
+
+       test_must_fail git merge -s recursive E1^0 &&
+
+       test 2 -eq $(git ls-files -s | wc -l) &&
+       test 1 -eq $(git ls-files -u | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
+       test $(git rev-parse :2:a) = $(git rev-parse B:a)
+'
+
+test_expect_success 'merge of E1 & D fails but has appropriate contents' '
+       get_clean_checkout E1^0 &&
+
+       test_must_fail git merge -s recursive D^0 &&
+
+       test 2 -eq $(git ls-files -s | wc -l) &&
+       test 1 -eq $(git ls-files -u | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
+       test $(git rev-parse :3:a) = $(git rev-parse B:a)
+'
+
+test_expect_success 'merge of D & E2 fails but has appropriate contents' '
+       get_clean_checkout D^0 &&
+
+       test_must_fail git merge -s recursive E2^0 &&
+
+       test 4 -eq $(git ls-files -s | wc -l) &&
+       test 3 -eq $(git ls-files -u | wc -l) &&
+       test 1 -eq $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse :2:a) = $(git rev-parse B:a) &&
+       test $(git rev-parse :3:a/file) = $(git rev-parse E2:a/file) &&
+       test $(git rev-parse :1:a/file) = $(git rev-parse C:a/file) &&
+       test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
+
+       test -f a~HEAD
+'
+
+test_expect_success 'merge of E2 & D fails but has appropriate contents' '
+       get_clean_checkout E2^0 &&
+
+       test_must_fail git merge -s recursive D^0 &&
+
+       test 4 -eq $(git ls-files -s | wc -l) &&
+       test 3 -eq $(git ls-files -u | wc -l) &&
+       test 1 -eq $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse :3:a) = $(git rev-parse B:a) &&
+       test $(git rev-parse :2:a/file) = $(git rev-parse E2:a/file) &&
+       test $(git rev-parse :1:a/file) = $(git rev-parse C:a/file)
+       test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
+
+       test -f a~D^0
+'
+
+#
+# criss-cross with rename/rename(1to2)/modify followed by
+# rename/rename(2to1)/modify:
+#
+#      B   D
+#      o---o
+#     / \ / \
+#  A o   X   ? F
+#     \ / \ /
+#      o---o
+#      C   E
+#
+#   Commit A: new file: a
+#   Commit B: rename a->b, modifying by adding a line
+#   Commit C: rename a->c
+#   Commit D: merge B&C, resolving conflict by keeping contents in newname
+#   Commit E: merge B&C, resolving conflict similar to D but adding another line
+#
+# There is a conflict merging B & C, but one of filename not of file
+# content.  Whoever created D and E chose specific resolutions for that
+# conflict resolution.  Now, since: (1) there is no content conflict
+# merging B & C, (2) D does not modify that merged content further, and (3)
+# both D & E resolve the name conflict in the same way, the modification to
+# newname in E should not cause any conflicts when it is merged with D.
+# (Note that this can be accomplished by having the virtual merge base have
+# the merged contents of b and c stored in a file named a, which seems like
+# the most logical choice anyway.)
+#
+# Comment from Junio: I do not necessarily agree with the choice "a", but
+# it feels sound to say "B and C do not agree what the final pathname
+# should be, but we know this content was derived from the common A:a so we
+# use one path whose name is arbitrary in the virtual merge base X between
+# D and E" and then further let the rename detection to notice that that
+# arbitrary path gets renamed between X-D to "newname" and X-E also to
+# "newname" to resolve it as both sides renaming it to the same new
+# name. It is akin to what we do at the content level, i.e. "B and C do not
+# agree what the final contents should be, so we leave the conflict marker
+# but that may cancel out at the final merge stage".
+
+test_expect_success 'setup rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' '
+       git reset --hard &&
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       printf "1\n2\n3\n4\n5\n6\n" >a &&
+       git add a &&
+       git commit -m A &&
+       git tag A &&
+
+       git checkout -b B A &&
+       git mv a b &&
+       echo 7 >>b &&
+       git add -u &&
+       git commit -m B &&
+
+       git checkout -b C A &&
+       git mv a c &&
+       git commit -m C &&
+
+       git checkout -q B^0 &&
+       git merge --no-commit -s ours C^0 &&
+       git mv b newname &&
+       git commit -m "Merge commit C^0 into HEAD" &&
+       git tag D &&
+
+       git checkout -q C^0 &&
+       git merge --no-commit -s ours B^0 &&
+       git mv c newname &&
+       printf "7\n8\n" >>newname &&
+       git add -u &&
+       git commit -m "Merge commit B^0 into HEAD" &&
+       git tag E
+'
+
+test_expect_success 'handle rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' '
+       git checkout D^0 &&
+
+       git merge -s recursive E^0 &&
+
+       test 1 -eq $(git ls-files -s | wc -l) &&
+       test 0 -eq $(git ls-files -u | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse HEAD:newname) = $(git rev-parse E:newname)
+'
+
+#
+# criss-cross with rename/rename(1to2)/add-source + resolvable modify/modify:
+#
+#      B   D
+#      o---o
+#     / \ / \
+#  A o   X   ? F
+#     \ / \ /
+#      o---o
+#      C   E
+#
+#   Commit A: new file: a
+#   Commit B: rename a->b
+#   Commit C: rename a->c, add different a
+#   Commit D: merge B&C, keeping b&c and (new) a modified at beginning
+#   Commit E: merge B&C, keeping b&c and (new) a modified at end
+#
+# Merging commits D & E should result in no conflict; doing so correctly
+# requires getting the virtual merge base (from merging B&C) right, handling
+# renaming carefully (both in the virtual merge base and later), and getting
+# content merge handled.
+
+test_expect_success 'setup criss-cross + rename/rename/add + modify/modify' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       printf "lots\nof\nwords\nand\ncontent\n" >a &&
+       git add a &&
+       git commit -m A &&
+       git tag A &&
+
+       git checkout -b B A &&
+       git mv a b &&
+       git commit -m B &&
+
+       git checkout -b C A &&
+       git mv a c &&
+       printf "2\n3\n4\n5\n6\n7\n" >a &&
+       git add a &&
+       git commit -m C &&
+
+       git checkout B^0 &&
+       git merge --no-commit -s ours C^0 &&
+       git checkout C -- a c &&
+       mv a old_a &&
+       echo 1 >a &&
+       cat old_a >>a &&
+       rm old_a &&
+       git add -u &&
+       git commit -m "Merge commit C^0 into HEAD" &&
+       git tag D &&
+
+       git checkout C^0 &&
+       git merge --no-commit -s ours B^0 &&
+       git checkout B -- b &&
+       echo 8 >>a &&
+       git add -u &&
+       git commit -m "Merge commit B^0 into HEAD" &&
+       git tag E
+'
+
+test_expect_failure 'detect rename/rename/add-source for virtual merge-base' '
+       git checkout D^0 &&
+
+       git merge -s recursive E^0 &&
+
+       test 3 -eq $(git ls-files -s | wc -l) &&
+       test 0 -eq $(git ls-files -u | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse HEAD:b) = $(git rev-parse A:a) &&
+       test $(git rev-parse HEAD:c) = $(git rev-parse A:a) &&
+       test "$(cat a)" = "$(printf "1\n2\n3\n4\n5\n6\n7\n8\n")"
+'
+
+#
+# criss-cross with rename/rename(1to2)/add-dest + simple modify:
+#
+#      B   D
+#      o---o
+#     / \ / \
+#  A o   X   ? F
+#     \ / \ /
+#      o---o
+#      C   E
+#
+#   Commit A: new file: a
+#   Commit B: rename a->b, add c
+#   Commit C: rename a->c
+#   Commit D: merge B&C, keeping A:a and B:c
+#   Commit E: merge B&C, keeping A:a and slightly modified c from B
+#
+# Merging commits D & E should result in no conflict.  The virtual merge
+# base of B & C needs to not delete B:c for that to work, though...
+
+test_expect_success 'setup criss-cross+rename/rename/add-dest + simple modify' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       >a &&
+       git add a &&
+       git commit -m A &&
+       git tag A &&
+
+       git checkout -b B A &&
+       git mv a b &&
+       printf "1\n2\n3\n4\n5\n6\n7\n" >c &&
+       git add c &&
+       git commit -m B &&
+
+       git checkout -b C A &&
+       git mv a c &&
+       git commit -m C &&
+
+       git checkout B^0 &&
+       git merge --no-commit -s ours C^0 &&
+       git mv b a &&
+       git commit -m "D is like B but renames b back to a" &&
+       git tag D &&
+
+       git checkout B^0 &&
+       git merge --no-commit -s ours C^0 &&
+       git mv b a &&
+       echo 8 >>c &&
+       git add c &&
+       git commit -m "E like D but has mod in c" &&
+       git tag E
+'
+
+test_expect_success 'virtual merge base handles rename/rename(1to2)/add-dest' '
+       git checkout D^0 &&
+
+       git merge -s recursive E^0 &&
+
+       test 2 -eq $(git ls-files -s | wc -l) &&
+       test 0 -eq $(git ls-files -u | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse HEAD:a) = $(git rev-parse A:a) &&
+       test $(git rev-parse HEAD:c) = $(git rev-parse E:c)
+'
+
 test_done
index a9b0ac1efc0eac3f50bd9fe26e5900307eddaba2..19de5b16eb530d428f66e5161a3ab48fce34049d 100755 (executable)
@@ -110,4 +110,18 @@ test_expect_success '--set-upstream does not change branch' '
        grep -q "^refs/heads/master$" actual &&
        cmp expect2 actual2
 '
+
+test_expect_success '--set-upstream @{-1}' '
+       git checkout from-master &&
+       git checkout from-master2 &&
+       git config branch.from-master2.merge > expect2 &&
+       git branch --set-upstream @{-1} follower &&
+       git config branch.from-master.merge > actual &&
+       git config branch.from-master2.merge > actual2 &&
+       git branch --set-upstream from-master follower &&
+       git config branch.from-master.merge > expect &&
+       test_cmp expect2 actual2 &&
+       test_cmp expect actual
+'
+
 test_done
diff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh
new file mode 100755 (executable)
index 0000000..32591f9
--- /dev/null
@@ -0,0 +1,578 @@
+#!/bin/sh
+
+test_description="recursive merge corner cases w/ renames but not criss-crosses"
+# t6036 has corner cases that involve both criss-cross merges and renames
+
+. ./test-lib.sh
+
+test_expect_success 'setup rename/delete + untracked file' '
+       echo "A pretty inscription" >ring &&
+       git add ring &&
+       test_tick &&
+       git commit -m beginning &&
+
+       git branch people &&
+       git checkout -b rename-the-ring &&
+       git mv ring one-ring-to-rule-them-all &&
+       test_tick &&
+       git commit -m fullname &&
+
+       git checkout people &&
+       git rm ring &&
+       echo gollum >owner &&
+       git add owner &&
+       test_tick &&
+       git commit -m track-people-instead-of-objects &&
+       echo "Myyy PRECIOUSSS" >ring
+'
+
+test_expect_success "Does git preserve Gollum's precious artifact?" '
+       test_must_fail git merge -s recursive rename-the-ring &&
+
+       # Make sure git did not delete an untracked file
+       test -f ring
+'
+
+# Testcase setup for rename/modify/add-source:
+#   Commit A: new file: a
+#   Commit B: modify a slightly
+#   Commit C: rename a->b, add completely different a
+#
+# We should be able to merge B & C cleanly
+
+test_expect_success 'setup rename/modify/add-source conflict' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       printf "1\n2\n3\n4\n5\n6\n7\n" >a &&
+       git add a &&
+       git commit -m A &&
+       git tag A &&
+
+       git checkout -b B A &&
+       echo 8 >>a &&
+       git add a &&
+       git commit -m B &&
+
+       git checkout -b C A &&
+       git mv a b &&
+       echo something completely different >a &&
+       git add a &&
+       git commit -m C
+'
+
+test_expect_failure 'rename/modify/add-source conflict resolvable' '
+       git checkout B^0 &&
+
+       git merge -s recursive C^0 &&
+
+       test $(git rev-parse B:a) = $(git rev-parse b) &&
+       test $(git rev-parse C:a) = $(git rev-parse a)
+'
+
+test_expect_success 'setup resolvable conflict missed if rename missed' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       printf "1\n2\n3\n4\n5\n" >a &&
+       echo foo >b &&
+       git add a b &&
+       git commit -m A &&
+       git tag A &&
+
+       git checkout -b B A &&
+       git mv a c &&
+       echo "Completely different content" >a &&
+       git add a &&
+       git commit -m B &&
+
+       git checkout -b C A &&
+       echo 6 >>a &&
+       git add a &&
+       git commit -m C
+'
+
+test_expect_failure 'conflict caused if rename not detected' '
+       git checkout -q C^0 &&
+       git merge -s recursive B^0 &&
+
+       test 3 -eq $(git ls-files -s | wc -l) &&
+       test 0 -eq $(git ls-files -u | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       test 6 -eq $(wc -l < c) &&
+       test $(git rev-parse HEAD:a) = $(git rev-parse B:a) &&
+       test $(git rev-parse HEAD:b) = $(git rev-parse A:b)
+'
+
+test_expect_success 'setup conflict resolved wrong if rename missed' '
+       git reset --hard &&
+       git clean -f &&
+
+       git checkout -b D A &&
+       echo 7 >>a &&
+       git add a &&
+       git mv a c &&
+       echo "Completely different content" >a &&
+       git add a &&
+       git commit -m D &&
+
+       git checkout -b E A &&
+       git rm a &&
+       echo "Completely different content" >>a &&
+       git add a &&
+       git commit -m E
+'
+
+test_expect_failure 'missed conflict if rename not detected' '
+       git checkout -q E^0 &&
+       test_must_fail git merge -s recursive D^0
+'
+
+# Tests for undetected rename/add-source causing a file to erroneously be
+# deleted (and for mishandled rename/rename(1to1) causing the same issue).
+#
+# This test uses a rename/rename(1to1)+add-source conflict (1to1 means the
+# same file is renamed on both sides to the same thing; it should trigger
+# the 1to2 logic, which it would do if the add-source didn't cause issues
+# for git's rename detection):
+#   Commit A: new file: a
+#   Commit B: rename a->b
+#   Commit C: rename a->b, add unrelated a
+
+test_expect_success 'setup undetected rename/add-source causes data loss' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       printf "1\n2\n3\n4\n5\n" >a &&
+       git add a &&
+       git commit -m A &&
+       git tag A &&
+
+       git checkout -b B A &&
+       git mv a b &&
+       git commit -m B &&
+
+       git checkout -b C A &&
+       git mv a b &&
+       echo foobar >a &&
+       git add a &&
+       git commit -m C
+'
+
+test_expect_failure 'detect rename/add-source and preserve all data' '
+       git checkout B^0 &&
+
+       git merge -s recursive C^0 &&
+
+       test 2 -eq $(git ls-files -s | wc -l) &&
+       test 2 -eq $(git ls-files -u | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       test -f a &&
+       test -f b &&
+
+       test $(git rev-parse HEAD:b) = $(git rev-parse A:a) &&
+       test $(git rev-parse HEAD:a) = $(git rev-parse C:a)
+'
+
+test_expect_failure 'detect rename/add-source and preserve all data, merge other way' '
+       git checkout C^0 &&
+
+       git merge -s recursive B^0 &&
+
+       test 2 -eq $(git ls-files -s | wc -l) &&
+       test 2 -eq $(git ls-files -u | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       test -f a &&
+       test -f b &&
+
+       test $(git rev-parse HEAD:b) = $(git rev-parse A:a) &&
+       test $(git rev-parse HEAD:a) = $(git rev-parse C:a)
+'
+
+test_expect_success 'setup content merge + rename/directory conflict' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       printf "1\n2\n3\n4\n5\n6\n" >file &&
+       git add file &&
+       test_tick &&
+       git commit -m base &&
+       git tag base &&
+
+       git checkout -b right &&
+       echo 7 >>file &&
+       mkdir newfile &&
+       echo junk >newfile/realfile &&
+       git add file newfile/realfile &&
+       test_tick &&
+       git commit -m right &&
+
+       git checkout -b left-conflict base &&
+       echo 8 >>file &&
+       git add file &&
+       git mv file newfile &&
+       test_tick &&
+       git commit -m left &&
+
+       git checkout -b left-clean base &&
+       echo 0 >newfile &&
+       cat file >>newfile &&
+       git add newfile &&
+       git rm file &&
+       test_tick &&
+       git commit -m left
+'
+
+test_expect_success 'rename/directory conflict + clean content merge' '
+       git reset --hard &&
+       git reset --hard &&
+       git clean -fdqx &&
+
+       git checkout left-clean^0 &&
+
+       test_must_fail git merge -s recursive right^0 &&
+
+       test 2 -eq $(git ls-files -s | wc -l) &&
+       test 1 -eq $(git ls-files -u | wc -l) &&
+       test 1 -eq $(git ls-files -o | wc -l) &&
+
+       echo 0 >expect &&
+       git cat-file -p base:file >>expect &&
+       echo 7 >>expect &&
+       test_cmp expect newfile~HEAD &&
+
+       test $(git rev-parse :2:newfile) = $(git hash-object expect) &&
+
+       test -f newfile/realfile &&
+       test -f newfile~HEAD
+'
+
+test_expect_success 'rename/directory conflict + content merge conflict' '
+       git reset --hard &&
+       git reset --hard &&
+       git clean -fdqx &&
+
+       git checkout left-conflict^0 &&
+
+       test_must_fail git merge -s recursive right^0 &&
+
+       test 4 -eq $(git ls-files -s | wc -l) &&
+       test 3 -eq $(git ls-files -u | wc -l) &&
+       test 1 -eq $(git ls-files -o | wc -l) &&
+
+       git cat-file -p left-conflict:newfile >left &&
+       git cat-file -p base:file    >base &&
+       git cat-file -p right:file   >right &&
+       test_must_fail git merge-file \
+               -L "HEAD:newfile" \
+               -L "" \
+               -L "right^0:file" \
+               left base right &&
+       test_cmp left newfile~HEAD &&
+
+       test $(git rev-parse :1:newfile) = $(git rev-parse base:file) &&
+       test $(git rev-parse :2:newfile) = $(git rev-parse left-conflict:newfile) &&
+       test $(git rev-parse :3:newfile) = $(git rev-parse right:file) &&
+
+       test -f newfile/realfile &&
+       test -f newfile~HEAD
+'
+
+test_expect_success 'setup content merge + rename/directory conflict w/ disappearing dir' '
+       git reset --hard &&
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       mkdir sub &&
+       printf "1\n2\n3\n4\n5\n6\n" >sub/file &&
+       git add sub/file &&
+       test_tick &&
+       git commit -m base &&
+       git tag base &&
+
+       git checkout -b right &&
+       echo 7 >>sub/file &&
+       git add sub/file &&
+       test_tick &&
+       git commit -m right &&
+
+       git checkout -b left base &&
+       echo 0 >newfile &&
+       cat sub/file >>newfile &&
+       git rm sub/file &&
+       mv newfile sub &&
+       git add sub &&
+       test_tick &&
+       git commit -m left
+'
+
+test_expect_success 'disappearing dir in rename/directory conflict handled' '
+       git reset --hard &&
+       git clean -fdqx &&
+
+       git checkout left^0 &&
+
+       git merge -s recursive right^0 &&
+
+       test 1 -eq $(git ls-files -s | wc -l) &&
+       test 0 -eq $(git ls-files -u | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       echo 0 >expect &&
+       git cat-file -p base:sub/file >>expect &&
+       echo 7 >>expect &&
+       test_cmp expect sub &&
+
+       test -f sub
+'
+
+# Test for all kinds of things that can go wrong with rename/rename (2to1):
+#   Commit A: new files: a & b
+#   Commit B: rename a->c, modify b
+#   Commit C: rename b->c, modify a
+#
+# Merging of B & C should NOT be clean.  Questions:
+#   * Both a & b should be removed by the merge; are they?
+#   * The two c's should contain modifications to a & b; do they?
+#   * The index should contain two files, both for c; does it?
+#   * The working copy should have two files, both of form c~<unique>; does it?
+#   * Nothing else should be present.  Is anything?
+
+test_expect_success 'setup rename/rename (2to1) + modify/modify' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       printf "1\n2\n3\n4\n5\n" >a &&
+       printf "5\n4\n3\n2\n1\n" >b &&
+       git add a b &&
+       git commit -m A &&
+       git tag A &&
+
+       git checkout -b B A &&
+       git mv a c &&
+       echo 0 >>b &&
+       git add b &&
+       git commit -m B &&
+
+       git checkout -b C A &&
+       git mv b c &&
+       echo 6 >>a &&
+       git add a &&
+       git commit -m C
+'
+
+test_expect_success 'handle rename/rename (2to1) conflict correctly' '
+       git checkout B^0 &&
+
+       test_must_fail git merge -s recursive C^0 >out &&
+       grep "CONFLICT (rename/rename)" out &&
+
+       test 2 -eq $(git ls-files -s | wc -l) &&
+       test 2 -eq $(git ls-files -u | wc -l) &&
+       test 2 -eq $(git ls-files -u c | wc -l) &&
+       test 3 -eq $(git ls-files -o | wc -l) &&
+
+       test ! -f a &&
+       test ! -f b &&
+       test -f c~HEAD &&
+       test -f c~C^0 &&
+
+       test $(git hash-object c~HEAD) = $(git rev-parse C:a) &&
+       test $(git hash-object c~C^0) = $(git rev-parse B:b)
+'
+
+# Testcase setup for simple rename/rename (1to2) conflict:
+#   Commit A: new file: a
+#   Commit B: rename a->b
+#   Commit C: rename a->c
+test_expect_success 'setup simple rename/rename (1to2) conflict' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       echo stuff >a &&
+       git add a &&
+       test_tick &&
+       git commit -m A &&
+       git tag A &&
+
+       git checkout -b B A &&
+       git mv a b &&
+       test_tick &&
+       git commit -m B &&
+
+       git checkout -b C A &&
+       git mv a c &&
+       test_tick &&
+       git commit -m C
+'
+
+test_expect_success 'merge has correct working tree contents' '
+       git checkout C^0 &&
+
+       test_must_fail git merge -s recursive B^0 &&
+
+       test 3 -eq $(git ls-files -s | wc -l) &&
+       test 3 -eq $(git ls-files -u | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse :1:a) = $(git rev-parse A:a) &&
+       test $(git rev-parse :3:b) = $(git rev-parse A:a) &&
+       test $(git rev-parse :2:c) = $(git rev-parse A:a) &&
+
+       test ! -f a &&
+       test $(git hash-object b) = $(git rev-parse A:a) &&
+       test $(git hash-object c) = $(git rev-parse A:a)
+'
+
+# Testcase setup for rename/rename(1to2)/add-source conflict:
+#   Commit A: new file: a
+#   Commit B: rename a->b
+#   Commit C: rename a->c, add completely different a
+#
+# Merging of B & C should NOT be clean; there's a rename/rename conflict
+
+test_expect_success 'setup rename/rename(1to2)/add-source conflict' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       printf "1\n2\n3\n4\n5\n6\n7\n" >a &&
+       git add a &&
+       git commit -m A &&
+       git tag A &&
+
+       git checkout -b B A &&
+       git mv a b &&
+       git commit -m B &&
+
+       git checkout -b C A &&
+       git mv a c &&
+       echo something completely different >a &&
+       git add a &&
+       git commit -m C
+'
+
+test_expect_failure 'detect conflict with rename/rename(1to2)/add-source merge' '
+       git checkout B^0 &&
+
+       test_must_fail git merge -s recursive C^0 &&
+
+       test 4 -eq $(git ls-files -s | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse 3:a) = $(git rev-parse C:a) &&
+       test $(git rev-parse 1:a) = $(git rev-parse A:a) &&
+       test $(git rev-parse 2:b) = $(git rev-parse B:b) &&
+       test $(git rev-parse 3:c) = $(git rev-parse C:c) &&
+
+       test -f a &&
+       test -f b &&
+       test -f c
+'
+
+test_expect_success 'setup rename/rename(1to2)/add-source resolvable conflict' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       >a &&
+       git add a &&
+       test_tick &&
+       git commit -m base &&
+       git tag A &&
+
+       git checkout -b B A &&
+       git mv a b &&
+       test_tick &&
+       git commit -m one &&
+
+       git checkout -b C A &&
+       git mv a b &&
+       echo important-info >a &&
+       git add a &&
+       test_tick &&
+       git commit -m two
+'
+
+test_expect_failure 'rename/rename/add-source still tracks new a file' '
+       git checkout C^0 &&
+       git merge -s recursive B^0 &&
+
+       test 2 -eq $(git ls-files -s | wc -l) &&
+       test 0 -eq $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse HEAD:a) = $(git rev-parse C:a) &&
+       test $(git rev-parse HEAD:b) = $(git rev-parse A:a)
+'
+
+test_expect_success 'setup rename/rename(1to2)/add-dest conflict' '
+       git rm -rf . &&
+       git clean -fdqx &&
+       rm -rf .git &&
+       git init &&
+
+       echo stuff >a &&
+       git add a &&
+       test_tick &&
+       git commit -m base &&
+       git tag A &&
+
+       git checkout -b B A &&
+       git mv a b &&
+       echo precious-data >c &&
+       git add c &&
+       test_tick &&
+       git commit -m one &&
+
+       git checkout -b C A &&
+       git mv a c &&
+       echo important-info >b &&
+       git add b &&
+       test_tick &&
+       git commit -m two
+'
+
+test_expect_success 'rename/rename/add-dest merge still knows about conflicting file versions' '
+       git checkout C^0 &&
+       test_must_fail git merge -s recursive B^0 &&
+
+       test 5 -eq $(git ls-files -s | wc -l) &&
+       test 2 -eq $(git ls-files -u b | wc -l) &&
+       test 2 -eq $(git ls-files -u c | wc -l) &&
+       test 4 -eq $(git ls-files -o | wc -l) &&
+
+       test $(git rev-parse :1:a) = $(git rev-parse A:a) &&
+       test $(git rev-parse :2:b) = $(git rev-parse C:b) &&
+       test $(git rev-parse :3:b) = $(git rev-parse B:b) &&
+       test $(git rev-parse :2:c) = $(git rev-parse C:c) &&
+       test $(git rev-parse :3:c) = $(git rev-parse B:c) &&
+
+       test $(git hash-object c~HEAD) = $(git rev-parse C:c) &&
+       test $(git hash-object c~B\^0) = $(git rev-parse B:c) &&
+       test $(git hash-object b~HEAD) = $(git rev-parse C:b) &&
+       test $(git hash-object b~B\^0) = $(git rev-parse B:b) &&
+
+       test ! -f b &&
+       test ! -f c
+'
+
+test_done
index 2ac1c66079b7656a60a25fbbb91c48c0b1db010b..097ce2bc8382e748925aa25c2dc0a1f06f6c7b4c 100755 (executable)
@@ -257,6 +257,11 @@ test_expect_success \
        test_cmp expect actual
 '
 
+test_expect_success 'tag -l can accept multiple patterns' '
+       git tag -l "v1*" "v0*" >actual &&
+       test_cmp expect actual
+'
+
 # creating and verifying lightweight tags:
 
 test_expect_success \
index ed7575d0fdf9eebf65d808fa810ab4cc03095025..320e1d1dbe62c81e29186d7a32b73447d2f998b8 100755 (executable)
@@ -13,7 +13,7 @@ cleanup_fail() {
 
 test_expect_success 'setup' '
        sane_unset GIT_PAGER GIT_PAGER_IN_USE &&
-       test_might_fail git config --unset core.pager &&
+       test_unconfig core.pager &&
 
        PAGER="cat >paginated.out" &&
        export PAGER &&
@@ -94,21 +94,19 @@ test_expect_success TTY 'no pager with --no-pager' '
 
 test_expect_success TTY 'configuration can disable pager' '
        rm -f paginated.out &&
-       test_might_fail git config --unset pager.grep &&
+       test_unconfig pager.grep &&
        test_terminal git grep initial &&
        test -e paginated.out &&
 
        rm -f paginated.out &&
-       git config pager.grep false &&
-       test_when_finished "git config --unset pager.grep" &&
+       test_config pager.grep false &&
        test_terminal git grep initial &&
        ! test -e paginated.out
 '
 
 test_expect_success TTY 'git config uses a pager if configured to' '
        rm -f paginated.out &&
-       git config pager.config true &&
-       test_when_finished "git config --unset pager.config" &&
+       test_config pager.config true &&
        test_terminal git config --list &&
        test -e paginated.out
 '
@@ -116,8 +114,7 @@ test_expect_success TTY 'git config uses a pager if configured to' '
 test_expect_success TTY 'configuration can enable pager (from subdir)' '
        rm -f paginated.out &&
        mkdir -p subdir &&
-       git config pager.bundle true &&
-       test_when_finished "git config --unset pager.bundle" &&
+       test_config pager.bundle true &&
 
        git bundle create test.bundle --all &&
        rm -f paginated.out subdir/paginated.out &&
@@ -150,7 +147,7 @@ test_expect_success 'tests can detect color' '
 
 test_expect_success 'no color when stdout is a regular file' '
        rm -f colorless.log &&
-       git config color.ui auto ||
+       test_config color.ui auto ||
        cleanup_fail &&
 
        git log >colorless.log &&
@@ -159,7 +156,7 @@ test_expect_success 'no color when stdout is a regular file' '
 
 test_expect_success TTY 'color when writing to a pager' '
        rm -f paginated.out &&
-       git config color.ui auto ||
+       test_config color.ui auto ||
        cleanup_fail &&
 
        (
@@ -170,9 +167,21 @@ test_expect_success TTY 'color when writing to a pager' '
        colorful paginated.out
 '
 
+test_expect_success TTY 'colors are suppressed by color.pager' '
+       rm -f paginated.out &&
+       test_config color.ui auto &&
+       test_config color.pager false &&
+       (
+               TERM=vt100 &&
+               export TERM &&
+               test_terminal git log
+       ) &&
+       ! colorful paginated.out
+'
+
 test_expect_success 'color when writing to a file intended for a pager' '
        rm -f colorful.log &&
-       git config color.ui auto ||
+       test_config color.ui auto ||
        cleanup_fail &&
 
        (
@@ -184,6 +193,17 @@ test_expect_success 'color when writing to a file intended for a pager' '
        colorful colorful.log
 '
 
+test_expect_success TTY 'colors are sent to pager for external commands' '
+       test_config alias.externallog "!git log" &&
+       test_config color.ui auto &&
+       (
+               TERM=vt100 &&
+               export TERM &&
+               test_terminal git -p externallog
+       ) &&
+       colorful paginated.out
+'
+
 # Use this helper to make it easy for the caller of your
 # terminal-using function to specify whether it should fail.
 # If you write
@@ -221,7 +241,7 @@ test_default_pager() {
 
        $test_expectation SIMPLEPAGER,TTY "$cmd - default pager is used by default" "
                sane_unset PAGER GIT_PAGER &&
-               test_might_fail git config --unset core.pager &&
+               test_unconfig core.pager &&
                rm -f default_pager_used ||
                cleanup_fail &&
 
@@ -244,7 +264,7 @@ test_PAGER_overrides() {
 
        $test_expectation TTY "$cmd - PAGER overrides default pager" "
                sane_unset GIT_PAGER &&
-               test_might_fail git config --unset core.pager &&
+               test_unconfig core.pager &&
                rm -f PAGER_used ||
                cleanup_fail &&
 
@@ -277,7 +297,7 @@ test_core_pager() {
 
                PAGER=wc &&
                export PAGER &&
-               git config core.pager 'wc >core.pager_used' &&
+               test_config core.pager 'wc >core.pager_used' &&
                $full_command &&
                ${if_local_config}test -e core.pager_used
        "
@@ -307,7 +327,7 @@ test_pager_subdir_helper() {
                PAGER=wc &&
                stampname=\$(pwd)/core.pager_used &&
                export PAGER stampname &&
-               git config core.pager 'wc >\"\$stampname\"' &&
+               test_config core.pager 'wc >\"\$stampname\"' &&
                mkdir sub &&
                (
                        cd sub &&
@@ -324,7 +344,7 @@ test_GIT_PAGER_overrides() {
                rm -f GIT_PAGER_used ||
                cleanup_fail &&
 
-               git config core.pager wc &&
+               test_config core.pager wc &&
                GIT_PAGER='wc >GIT_PAGER_used' &&
                export GIT_PAGER &&
                $full_command &&
@@ -402,21 +422,21 @@ test_core_pager_subdir    expect_success test_must_fail \
                                         'git -p apply </dev/null'
 
 test_expect_success TTY 'command-specific pager' '
-       unset PAGER GIT_PAGER;
+       sane_unset PAGER GIT_PAGER &&
        echo "foo:initial" >expect &&
        >actual &&
-       git config --unset core.pager &&
-       git config pager.log "sed s/^/foo:/ >actual" &&
+       test_unconfig core.pager &&
+       test_config pager.log "sed s/^/foo:/ >actual" &&
        test_terminal git log --format=%s -1 &&
        test_cmp expect actual
 '
 
 test_expect_success TTY 'command-specific pager overrides core.pager' '
-       unset PAGER GIT_PAGER;
+       sane_unset PAGER GIT_PAGER &&
        echo "foo:initial" >expect &&
        >actual &&
-       git config core.pager "exit 1"
-       git config pager.log "sed s/^/foo:/ >actual" &&
+       test_config core.pager "exit 1"
+       test_config pager.log "sed s/^/foo:/ >actual" &&
        test_terminal git log --format=%s -1 &&
        test_cmp expect actual
 '
@@ -425,9 +445,45 @@ test_expect_success TTY 'command-specific pager overridden by environment' '
        GIT_PAGER="sed s/^/foo:/ >actual" && export GIT_PAGER &&
        >actual &&
        echo "foo:initial" >expect &&
-       git config pager.log "exit 1" &&
+       test_config pager.log "exit 1" &&
        test_terminal git log --format=%s -1 &&
        test_cmp expect actual
 '
 
+test_expect_success 'setup external command' '
+       cat >git-external <<-\EOF &&
+       #!/bin/sh
+       git "$@"
+       EOF
+       chmod +x git-external
+'
+
+test_expect_success TTY 'command-specific pager works for external commands' '
+       sane_unset PAGER GIT_PAGER &&
+       echo "foo:initial" >expect &&
+       >actual &&
+       test_config pager.external "sed s/^/foo:/ >actual" &&
+       test_terminal git --exec-path="`pwd`" external log --format=%s -1 &&
+       test_cmp expect actual
+'
+
+test_expect_success TTY 'sub-commands of externals use their own pager' '
+       sane_unset PAGER GIT_PAGER &&
+       echo "foo:initial" >expect &&
+       >actual &&
+       test_config pager.log "sed s/^/foo:/ >actual" &&
+       test_terminal git --exec-path=. external log --format=%s -1 &&
+       test_cmp expect actual
+'
+
+test_expect_success TTY 'external command pagers override sub-commands' '
+       sane_unset PAGER GIT_PAGER &&
+       >expect &&
+       >actual &&
+       test_config pager.external false &&
+       test_config pager.log "sed s/^/log:/ >actual" &&
+       test_terminal git --exec-path=. external log --format=%s -1 &&
+       test_cmp expect actual
+'
+
 test_done
index e058d184d1c072bd3078fe17ad41f1026f093201..917a264eea6c6b3593e9f19caadbef715daace20 100755 (executable)
@@ -84,7 +84,7 @@ test_expect_success 'git grep -Fi Y<NUL>f a' "
        git grep -f f -Fi a
 "
 
-test_expect_failure 'git grep -Fi Y<NUL>x a' "
+test_expect_success 'git grep -Fi Y<NUL>x a' "
        printf 'YQx' | q_to_nul >f &&
        test_must_fail git grep -f f -Fi a
 "
@@ -94,7 +94,7 @@ test_expect_success 'git grep y<NUL>f a' "
        git grep -f f a
 "
 
-test_expect_failure 'git grep y<NUL>x a' "
+test_expect_success 'git grep y<NUL>x a' "
        printf 'yQx' | q_to_nul >f &&
        test_must_fail git grep -f f a
 "
index f1cfc9ac959ee281d50f7b8a51fdcefd2d0670e7..b096dc88c2f4caf28887cf8fea5d700f0132f5a3 100755 (executable)
@@ -429,6 +429,21 @@ test_expect_success '--mixed refreshes the index' '
        test_i18ncmp expect output
 '
 
+test_expect_success 'resetting specific path that is unmerged' '
+       git rm --cached file2 &&
+       F1=$(git rev-parse HEAD:file1) &&
+       F2=$(git rev-parse HEAD:file2) &&
+       F3=$(git rev-parse HEAD:secondfile) &&
+       {
+               echo "100644 $F1 1      file2" &&
+               echo "100644 $F2 2      file2" &&
+               echo "100644 $F3 3      file2"
+       } | git update-index --index-info &&
+       git ls-files -u &&
+       test_must_fail git reset HEAD file2 &&
+       git diff-index --exit-code --cached HEAD
+'
+
 test_expect_success 'disambiguation (1)' '
 
        git reset --hard &&
index 874279e32da98ac2c20137a207a0d115f073e444..69115269c2a284dcfc99abd2f7feb3d87f4e5bcc 100755 (executable)
@@ -47,8 +47,10 @@ test_expect_success 'setup - repository to add submodules to' '
 '
 
 # The 'submodule add' tests need some repository to add as a submodule.
-# The trash directory is a good one as any.
-submodurl=$TRASH_DIRECTORY
+# The trash directory is a good one as any. We need to canonicalize
+# the name, though, as some tests compare it to the absolute path git
+# generates, which will expand symbolic links.
+submodurl=$(pwd -P)
 
 listbranches() {
        git for-each-ref --format='%(refname)' 'refs/heads/*'
@@ -75,7 +77,8 @@ test_expect_success 'submodule add' '
 
        (
                cd addtest &&
-               git submodule add "$submodurl" submod &&
+               git submodule add -q "$submodurl" submod >actual &&
+               test ! -s actual &&
                git submodule init
        ) &&
 
@@ -99,7 +102,7 @@ test_expect_success 'submodule add to .gitignored path fails' '
                git add --force .gitignore &&
                git commit -m"Ignore everything" &&
                ! git submodule add "$submodurl" submod >actual 2>&1 &&
-               test_cmp expect actual
+               test_i18ncmp expect actual
        )
 '
 
@@ -273,7 +276,8 @@ test_expect_success 'update should work when path is an empty dir' '
        echo "$rev1" >expect &&
 
        mkdir init &&
-       git submodule update &&
+       git submodule update -q >update.out &&
+       test ! -s update.out &&
 
        inspect init &&
        test_cmp expect head-sha1
@@ -357,7 +361,7 @@ test_expect_success 'update --init' '
 
        git submodule update init > update.out &&
        cat update.out &&
-       grep "not initialized" update.out &&
+       test_i18ngrep "not initialized" update.out &&
        ! test -d init/.git &&
 
        git submodule update --init init &&
@@ -446,6 +450,16 @@ test_expect_success 'add should fail when path is used by an existing directory'
        )
 '
 
+test_expect_success 'use superproject as upstream when path is relative and no url is set there' '
+       (
+               cd addtest &&
+               git submodule add ../repo relative &&
+               test "$(git config -f .gitmodules submodule.relative.url)" = ../repo &&
+               git submodule sync relative &&
+               test "$(git config submodule.relative.url)" = "$submodurl/repo"
+       )
+'
+
 test_expect_success 'set up for relative path tests' '
        mkdir reltest &&
        (
index 7d7fde057b04e4615e32b0ddc71fd3b07968a129..30b429e7dcbcd56af21ded67128aa18901133275 100755 (executable)
@@ -128,7 +128,7 @@ test_expect_success 'typechanged submodule(submodule->blob), --cached' "
   < Add foo5
 
 EOF
-       test_cmp actual expected
+       test_i18ncmp actual expected
 "
 
 test_expect_success 'typechanged submodule(submodule->blob), --files' "
@@ -138,7 +138,7 @@ test_expect_success 'typechanged submodule(submodule->blob), --files' "
   > Add foo5
 
 EOF
-    test_cmp actual expected
+    test_i18ncmp actual expected
 "
 
 rm -rf sm1 &&
@@ -149,7 +149,7 @@ test_expect_success 'typechanged submodule(submodule->blob)' "
 * sm1 $head4(submodule)->$head5(blob):
 
 EOF
-    test_cmp actual expected
+    test_i18ncmp actual expected
 "
 
 rm -f sm1 &&
@@ -162,7 +162,7 @@ test_expect_success 'nonexistent commit' "
   Warn: sm1 doesn't contain commit $head4_full
 
 EOF
-    test_cmp actual expected
+    test_i18ncmp actual expected
 "
 
 commit_file
@@ -173,7 +173,7 @@ test_expect_success 'typechanged submodule(blob->submodule)' "
   > Add foo7
 
 EOF
-    test_cmp expected actual
+    test_i18ncmp expected actual
 "
 
 commit_file sm1 &&
@@ -228,7 +228,7 @@ EOF
 
 test_expect_success '--for-status' "
     git submodule summary --for-status HEAD^ >actual &&
-    test_cmp actual - <<EOF
+    test_i18ncmp actual - <<EOF
 # Submodule changes to be committed:
 #
 # * sm1 $head6...0000000:
index d600583cef8aa4a19d98ffa63b2cc4f76f698c9e..95ffe349a769a84d2eec850d376f11bdfcf2b56f 100755 (executable)
@@ -25,7 +25,8 @@ test_expect_success setup '
        git clone super super-clone &&
        (cd super-clone && git submodule update --init) &&
        git clone super empty-clone &&
-       (cd empty-clone && git submodule init)
+       (cd empty-clone && git submodule init) &&
+       git clone super top-only-clone
 '
 
 test_expect_success 'change submodule' '
@@ -66,7 +67,7 @@ test_expect_success '"git submodule sync" should update submodule URLs' '
        )
 '
 
-test_expect_success '"git submodule sync" should update submodule URLs if not yet cloned' '
+test_expect_success '"git submodule sync" should update known submodule URLs' '
        (cd empty-clone &&
         git pull &&
         git submodule sync &&
@@ -74,4 +75,14 @@ test_expect_success '"git submodule sync" should update submodule URLs if not ye
        )
 '
 
+test_expect_success '"git submodule sync" should not vivify uninteresting submodule' '
+       (cd top-only-clone &&
+        git pull &&
+        git submodule sync &&
+        test -z "$(git config submodule.submodule.url)" &&
+        git submodule sync submodule &&
+        test -z "$(git config submodule.submodule.url)"
+       )
+'
+
 test_done
index 4f16fcce2bfcb63f437fa6b495fdb5c4370fccc1..c679f36a4daeab3f114c9cf0c84c93549d28c680 100755 (executable)
@@ -90,7 +90,7 @@ test_expect_success 'submodule update does not fetch already present commits' '
        (cd super &&
          git submodule update > ../actual 2> ../actual.err
        ) &&
-       test_cmp expected actual &&
+       test_i18ncmp expected actual &&
        ! test -s actual.err
 '
 
@@ -298,4 +298,148 @@ test_expect_success 'submodule update ignores update=rebase config for new submo
        )
 '
 
+test_expect_success 'submodule update continues after checkout error' '
+       (cd super &&
+        git reset --hard HEAD &&
+        git submodule add ../submodule submodule2 &&
+        git submodule init &&
+        git commit -am "new_submodule" &&
+        (cd submodule2 &&
+         git rev-parse --max-count=1 HEAD > ../expect
+        ) &&
+        (cd submodule &&
+         test_commit "update_submodule" file
+        ) &&
+        (cd submodule2 &&
+         test_commit "update_submodule2" file
+        ) &&
+        git add submodule &&
+        git add submodule2 &&
+        git commit -m "two_new_submodule_commits" &&
+        (cd submodule &&
+         echo "" > file
+        ) &&
+        git checkout HEAD^ &&
+        test_must_fail git submodule update &&
+        (cd submodule2 &&
+         git rev-parse --max-count=1 HEAD > ../actual
+        ) &&
+        test_cmp expect actual
+       )
+'
+test_expect_success 'submodule update continues after recursive checkout error' '
+       (cd super &&
+        git reset --hard HEAD &&
+        git checkout master &&
+        git submodule update &&
+        (cd submodule &&
+         git submodule add ../submodule subsubmodule &&
+         git submodule init &&
+         git commit -m "new_subsubmodule"
+        ) &&
+        git add submodule &&
+        git commit -m "update_submodule" &&
+        (cd submodule &&
+         (cd subsubmodule &&
+          test_commit "update_subsubmodule" file
+         ) &&
+         git add subsubmodule &&
+         test_commit "update_submodule_again" file &&
+         (cd subsubmodule &&
+          test_commit "update_subsubmodule_again" file
+         ) &&
+         test_commit "update_submodule_again_again" file
+        ) &&
+        (cd submodule2 &&
+         git rev-parse --max-count=1 HEAD > ../expect &&
+         test_commit "update_submodule2_again" file
+        ) &&
+        git add submodule &&
+        git add submodule2 &&
+        git commit -m "new_commits" &&
+        git checkout HEAD^ &&
+        (cd submodule &&
+         git checkout HEAD^ &&
+         (cd subsubmodule &&
+          echo "" > file
+         )
+        ) &&
+        test_must_fail git submodule update --recursive &&
+        (cd submodule2 &&
+         git rev-parse --max-count=1 HEAD > ../actual
+        ) &&
+        test_cmp expect actual
+       )
+'
+
+test_expect_success 'submodule update exit immediately in case of merge conflict' '
+       (cd super &&
+        git checkout master &&
+        git reset --hard HEAD &&
+        (cd submodule &&
+         (cd subsubmodule &&
+          git reset --hard HEAD
+         )
+        ) &&
+        git submodule update --recursive &&
+        (cd submodule &&
+         test_commit "update_submodule_2" file
+        ) &&
+        (cd submodule2 &&
+         test_commit "update_submodule2_2" file
+        ) &&
+        git add submodule &&
+        git add submodule2 &&
+        git commit -m "two_new_submodule_commits" &&
+        (cd submodule &&
+         git checkout master &&
+         test_commit "conflict" file &&
+         echo "conflict" > file
+        ) &&
+        git checkout HEAD^ &&
+        (cd submodule2 &&
+         git rev-parse --max-count=1 HEAD > ../expect
+        ) &&
+        git config submodule.submodule.update merge &&
+        test_must_fail git submodule update &&
+        (cd submodule2 &&
+         git rev-parse --max-count=1 HEAD > ../actual
+        ) &&
+        test_cmp expect actual
+       )
+'
+test_expect_success 'submodule update exit immediately after recursive rebase error' '
+       (cd super &&
+        git checkout master &&
+        git reset --hard HEAD &&
+        (cd submodule &&
+         git reset --hard HEAD &&
+         git submodule update --recursive
+        ) &&
+        (cd submodule &&
+         test_commit "update_submodule_3" file
+        ) &&
+        (cd submodule2 &&
+         test_commit "update_submodule2_3" file
+        ) &&
+        git add submodule &&
+        git add submodule2 &&
+        git commit -m "two_new_submodule_commits" &&
+        (cd submodule &&
+         git checkout master &&
+         test_commit "conflict2" file &&
+         echo "conflict" > file
+        ) &&
+        git checkout HEAD^ &&
+        (cd submodule2 &&
+         git rev-parse --max-count=1 HEAD > ../expect
+        ) &&
+        git config submodule.submodule.update rebase &&
+        test_must_fail git submodule update &&
+        (cd submodule2 &&
+         git rev-parse --max-count=1 HEAD > ../actual
+        ) &&
+        test_cmp expect actual
+       )
+'
 test_done
index e5be13c271c92ce7c51601ee1eaf966d849d8ae4..be745fb23f3f27ed319143d3d3ab3f26d01857bc 100755 (executable)
@@ -77,7 +77,7 @@ test_expect_success 'test basic "submodule foreach" usage' '
                git config foo.bar zar &&
                git submodule foreach "git config --file \"\$toplevel/.git/config\" foo.bar"
        ) &&
-       test_cmp expect actual
+       test_i18ncmp expect actual
 '
 
 test_expect_success 'setup nested submodules' '
@@ -158,7 +158,7 @@ test_expect_success 'test messages from "foreach --recursive"' '
                cd clone2 &&
                git submodule foreach --recursive "true" > ../actual
        ) &&
-       test_cmp expect actual
+       test_i18ncmp expect actual
 '
 
 cat > expect <<EOF
@@ -292,4 +292,22 @@ test_expect_success 'use "update --recursive nested1" to checkout all submodules
        )
 '
 
+test_expect_success 'command passed to foreach retains notion of stdin' '
+       (
+               cd super &&
+               git submodule foreach echo success >../expected &&
+               yes | git submodule foreach "read y && test \"x\$y\" = xy && echo success" >../actual
+       ) &&
+       test_cmp expected actual
+'
+
+test_expect_success 'command passed to foreach --recursive retains notion of stdin' '
+       (
+               cd clone2 &&
+               git submodule foreach --recursive echo success >../expected &&
+               yes | git submodule foreach --recursive "read y && test \"x\$y\" = xy && echo success" >../actual
+       ) &&
+       test_cmp expected actual
+'
+
 test_done
index 8528f64c8d1491fd3c279f030b6f8aee2050cdf7..ee7f0cd4596f982f16cbf3859675e6faba424faa 100755 (executable)
@@ -84,5 +84,38 @@ test_expect_success POSIXPERM '--no-verify with non-executable hook' '
        git commit --no-verify -m "more content"
 
 '
+chmod +x "$HOOK"
+
+# a hook that checks $GIT_PREFIX and succeeds inside the
+# success/ subdirectory only
+cat > "$HOOK" <<EOF
+#!/bin/sh
+test \$GIT_PREFIX = success/
+EOF
+
+test_expect_success 'with hook requiring GIT_PREFIX' '
+
+       echo "more content" >> file &&
+       git add file &&
+       mkdir success &&
+       (
+               cd success &&
+               git commit -m "hook requires GIT_PREFIX = success/"
+       ) &&
+       rmdir success
+'
+
+test_expect_success 'with failing hook requiring GIT_PREFIX' '
+
+       echo "more content" >> file &&
+       git add file &&
+       mkdir fail &&
+       (
+               cd fail &&
+               test_must_fail git commit -m "hook must fail"
+       ) &&
+       rmdir fail &&
+       git checkout -- file
+'
 
 test_done
index 1fdfbd38654a83771f677f2219403e3713abef51..905255adf0ca5b15d9befa772cda4a650bd15f34 100755 (executable)
@@ -131,6 +131,127 @@ test_expect_success 'status -s' '
 
 '
 
+test_expect_success 'status with gitignore' '
+       {
+               echo ".gitignore" &&
+               echo "expect" &&
+               echo "output" &&
+               echo "untracked"
+       } >.gitignore &&
+
+       cat >expect <<-\EOF &&
+        M dir1/modified
+       A  dir2/added
+       ?? dir2/modified
+       EOF
+       git status -s >output &&
+       test_cmp expect output &&
+
+       cat >expect <<-\EOF &&
+        M dir1/modified
+       A  dir2/added
+       ?? dir2/modified
+       !! .gitignore
+       !! dir1/untracked
+       !! dir2/untracked
+       !! expect
+       !! output
+       !! untracked
+       EOF
+       git status -s --ignored >output &&
+       test_cmp expect output &&
+
+       cat >expect <<-\EOF &&
+       # On branch master
+       # Changes to be committed:
+       #   (use "git reset HEAD <file>..." to unstage)
+       #
+       #       new file:   dir2/added
+       #
+       # Changes not staged for commit:
+       #   (use "git add <file>..." to update what will be committed)
+       #   (use "git checkout -- <file>..." to discard changes in working directory)
+       #
+       #       modified:   dir1/modified
+       #
+       # Untracked files:
+       #   (use "git add <file>..." to include in what will be committed)
+       #
+       #       dir2/modified
+       # Ignored files:
+       #   (use "git add -f <file>..." to include in what will be committed)
+       #
+       #       .gitignore
+       #       dir1/untracked
+       #       dir2/untracked
+       #       expect
+       #       output
+       #       untracked
+       EOF
+       git status --ignored >output &&
+       test_cmp expect output
+'
+
+test_expect_success 'status with gitignore (nothing untracked)' '
+       {
+               echo ".gitignore" &&
+               echo "expect" &&
+               echo "dir2/modified" &&
+               echo "output" &&
+               echo "untracked"
+       } >.gitignore &&
+
+       cat >expect <<-\EOF &&
+        M dir1/modified
+       A  dir2/added
+       EOF
+       git status -s >output &&
+       test_cmp expect output &&
+
+       cat >expect <<-\EOF &&
+        M dir1/modified
+       A  dir2/added
+       !! .gitignore
+       !! dir1/untracked
+       !! dir2/modified
+       !! dir2/untracked
+       !! expect
+       !! output
+       !! untracked
+       EOF
+       git status -s --ignored >output &&
+       test_cmp expect output &&
+
+       cat >expect <<-\EOF &&
+       # On branch master
+       # Changes to be committed:
+       #   (use "git reset HEAD <file>..." to unstage)
+       #
+       #       new file:   dir2/added
+       #
+       # Changes not staged for commit:
+       #   (use "git add <file>..." to update what will be committed)
+       #   (use "git checkout -- <file>..." to discard changes in working directory)
+       #
+       #       modified:   dir1/modified
+       #
+       # Ignored files:
+       #   (use "git add -f <file>..." to include in what will be committed)
+       #
+       #       .gitignore
+       #       dir1/untracked
+       #       dir2/modified
+       #       dir2/untracked
+       #       expect
+       #       output
+       #       untracked
+       EOF
+       git status --ignored >output &&
+       test_cmp expect output
+'
+
+rm -f .gitignore
+
 cat >expect <<\EOF
 ## master
  M dir1/modified
index 0a46795ae785fd4dfa0e565bfa0153ca107a904d..61f36baa1f3459d68ac30e67680e096093cbe414 100755 (executable)
@@ -53,7 +53,7 @@ cat >expected <<\EOF
 Trying simple merge with c2
 Trying simple merge with c3
 Trying simple merge with c4
-Merge made by octopus.
+Merge made by the 'octopus' strategy.
  c2.c |    1 +
  c3.c |    1 +
  c4.c |    1 +
@@ -72,7 +72,7 @@ test_expect_success 'merge output uses pretty names' '
 cat >expected <<\EOF
 Already up-to-date with c4
 Trying simple merge with c5
-Merge made by octopus.
+Merge made by the 'octopus' strategy.
  c5.c |    1 +
  1 files changed, 1 insertions(+), 0 deletions(-)
  create mode 100644 c5.c
@@ -86,7 +86,7 @@ test_expect_success 'merge up-to-date output uses pretty names' '
 cat >expected <<\EOF
 Fast-forwarding to: c1
 Trying simple merge with c2
-Merge made by octopus.
+Merge made by the 'octopus' strategy.
  c1.c |    1 +
  c2.c |    1 +
  2 files changed, 2 insertions(+), 0 deletions(-)
index 72a8731d5e28d71577d0c53fc25a5ddc1d2ad6de..aa74184c31cd6b2bd2e0e566e6805e60eed7aff8 100755 (executable)
@@ -107,6 +107,7 @@ error: The following untracked working tree files would be overwritten by merge:
        sub
        sub2
 Please move or remove them before you can merge.
+Aborting
 EOF
 
 test_expect_success 'will not overwrite untracked file in leading path' '
index c994836c53224dd0a8302f7a6ab63b35f8b493e3..0e4a682c6428a96af4d98bb7e24b0de7f4be865c 100755 (executable)
@@ -32,6 +32,7 @@ error: The following untracked working tree files would be overwritten by merge:
        three
        two
 Please move or remove them before you can merge.
+Aborting
 EOF
 
 test_expect_success 'untracked files overwritten by merge (fast and non-fast forward)' '
@@ -56,6 +57,7 @@ Please, commit your changes or stash them before you can merge.
 error: The following untracked working tree files would be overwritten by merge:
        five
 Please move or remove them before you can merge.
+Aborting
 EOF
 
 test_expect_success 'untracked files or local changes ovewritten by merge' '
@@ -71,6 +73,7 @@ error: Your local changes to the following files would be overwritten by checkou
        rep/one
        rep/two
 Please, commit your changes or stash them before you can switch branches.
+Aborting
 EOF
 
 test_expect_success 'cannot switch branches because of local changes' '
@@ -92,6 +95,7 @@ error: Your local changes to the following files would be overwritten by checkou
        rep/one
        rep/two
 Please, commit your changes or stash them before you can switch branches.
+Aborting
 EOF
 
 test_expect_success 'not uptodate file porcelain checkout error' '
@@ -105,6 +109,7 @@ error: Updating the following directories would lose untracked files in it:
        rep
        rep2
 
+Aborting
 EOF
 
 test_expect_success 'not_uptodate_dir porcelain checkout error' '
index cbc08e32761c6e35a3eb25519873e74218fd441a..4aab2a75b8ef5836a58f30b6c27a8eec4a733afc 100755 (executable)
@@ -16,6 +16,7 @@ Testing basic merge tool invocation'
 test_expect_success 'setup' '
     git config rerere.enabled true &&
     echo master >file1 &&
+    echo master spaced >"spaced name" &&
     echo master file11 >file11 &&
     echo master file12 >file12 &&
     echo master file13 >file13 &&
@@ -30,13 +31,14 @@ test_expect_success 'setup' '
        git commit -m "Add foo"
     ) &&
     git submodule add git://example.com/submod submod &&
-    git add file1 file1[1-4] subdir/file3 .gitmodules submod &&
+    git add file1 "spaced name" file1[1-4] subdir/file3 .gitmodules submod &&
     git commit -m "add initial versions" &&
 
     git checkout -b branch1 master &&
     git submodule update -N &&
     echo branch1 change >file1 &&
     echo branch1 newfile >file2 &&
+    echo branch1 spaced >"spaced name" &&
     echo branch1 change file11 >file11 &&
     echo branch1 change file13 >file13 &&
     echo branch1 sub >subdir/file3 &&
@@ -47,7 +49,7 @@ test_expect_success 'setup' '
        git commit -m "Add bar on branch1" &&
        git checkout -b submod-branch1
     ) &&
-    git add file1 file11 file13 file2 subdir/file3 submod &&
+    git add file1 "spaced name" file11 file13 file2 subdir/file3 submod &&
     git rm file12 &&
     git commit -m "branch1 changes" &&
 
@@ -55,6 +57,7 @@ test_expect_success 'setup' '
     git submodule update -N &&
     echo master updated >file1 &&
     echo master new >file2 &&
+    echo master updated spaced >"spaced name" &&
     echo master updated file12 >file12 &&
     echo master updated file14 >file14 &&
     echo master new sub >subdir/file3 &&
@@ -65,7 +68,7 @@ test_expect_success 'setup' '
        git commit -m "Add bar on master" &&
        git checkout -b submod-master
     ) &&
-    git add file1 file12 file14 file2 subdir/file3 submod &&
+    git add file1 "spaced name" file12 file14 file2 subdir/file3 submod &&
     git rm file11 &&
     git commit -m "master updates" &&
 
@@ -78,8 +81,8 @@ test_expect_success 'custom mergetool' '
     git checkout -b test1 branch1 &&
     git submodule update -N &&
     test_must_fail git merge master >/dev/null 2>&1 &&
-    ( yes "" | git mergetool file1 >/dev/null 2>&1 ) &&
-    ( yes "" | git mergetool file2 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool file1 file1 ) &&
+    ( yes "" | git mergetool file2 "spaced name" >/dev/null 2>&1 ) &&
     ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) &&
@@ -97,6 +100,7 @@ test_expect_success 'mergetool crlf' '
     test_must_fail git merge master >/dev/null 2>&1 &&
     ( yes "" | git mergetool file1 >/dev/null 2>&1 ) &&
     ( yes "" | git mergetool file2 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool "spaced name" >/dev/null 2>&1 ) &&
     ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) &&
@@ -126,7 +130,7 @@ test_expect_success 'mergetool on file in parent dir' '
     (
        cd subdir &&
        ( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) &&
-       ( yes "" | git mergetool ../file2 >/dev/null 2>&1 ) &&
+       ( yes "" | git mergetool ../file2 ../spaced\ name >/dev/null 2>&1 ) &&
        ( yes "d" | git mergetool ../file11 >/dev/null 2>&1 ) &&
        ( yes "d" | git mergetool ../file12 >/dev/null 2>&1 ) &&
        ( yes "l" | git mergetool ../submod >/dev/null 2>&1 ) &&
@@ -180,6 +184,24 @@ test_expect_success 'mergetool skips resolved paths when rerere is active' '
     git reset --hard
 '
 
+test_expect_success 'mergetool takes partial path' '
+    git config rerere.enabled false &&
+    git checkout -b test12 branch1 &&
+    git submodule update -N &&
+    test_must_fail git merge master &&
+
+    #shouldnt need these lines
+    #( yes "d" | git mergetool file11 >/dev/null 2>&1 ) &&
+    #( yes "d" | git mergetool file12 >/dev/null 2>&1 ) &&
+    #( yes "l" | git mergetool submod >/dev/null 2>&1 ) &&
+    #( yes "" | git mergetool file1 file2 >/dev/null 2>&1 ) &&
+
+    ( yes "" | git mergetool subdir ) &&
+
+    test "$(cat subdir/file3)" = "master new sub" &&
+    git reset --hard
+'
+
 test_expect_success 'deleted vs modified submodule' '
     git checkout -b test6 branch1 &&
     git submodule update -N &&
@@ -189,7 +211,7 @@ test_expect_success 'deleted vs modified submodule' '
     git checkout -b test6.a test6 &&
     test_must_fail git merge master &&
     test -n "$(git ls-files -u)" &&
-    ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
     ( yes "r" | git mergetool submod ) &&
     rmdir submod && mv submod-movedaside submod &&
@@ -205,7 +227,7 @@ test_expect_success 'deleted vs modified submodule' '
     git submodule update -N &&
     test_must_fail git merge master &&
     test -n "$(git ls-files -u)" &&
-    ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
     ( yes "l" | git mergetool submod ) &&
     test ! -e submod &&
@@ -218,7 +240,7 @@ test_expect_success 'deleted vs modified submodule' '
     git submodule update -N &&
     test_must_fail git merge test6 &&
     test -n "$(git ls-files -u)" &&
-    ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
     ( yes "r" | git mergetool submod ) &&
     test ! -e submod &&
@@ -233,7 +255,7 @@ test_expect_success 'deleted vs modified submodule' '
     git submodule update -N &&
     test_must_fail git merge test6 &&
     test -n "$(git ls-files -u)" &&
-    ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
     ( yes "l" | git mergetool submod ) &&
     test "$(cat submod/bar)" = "master submodule" &&
@@ -256,7 +278,7 @@ test_expect_success 'file vs modified submodule' '
     git checkout -b test7.a branch1 &&
     test_must_fail git merge master &&
     test -n "$(git ls-files -u)" &&
-    ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
     ( yes "r" | git mergetool submod ) &&
     rmdir submod && mv submod-movedaside submod &&
@@ -271,7 +293,7 @@ test_expect_success 'file vs modified submodule' '
     git checkout -b test7.b test7 &&
     test_must_fail git merge master &&
     test -n "$(git ls-files -u)" &&
-    ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
     ( yes "l" | git mergetool submod ) &&
     git submodule update -N &&
@@ -286,7 +308,7 @@ test_expect_success 'file vs modified submodule' '
     git submodule update -N &&
     test_must_fail git merge test7 &&
     test -n "$(git ls-files -u)" &&
-    ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
     ( yes "r" | git mergetool submod ) &&
     test -d submod.orig &&
@@ -301,7 +323,7 @@ test_expect_success 'file vs modified submodule' '
     git submodule update -N &&
     test_must_fail git merge test7 &&
     test -n "$(git ls-files -u)" &&
-    ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
     ( yes "l" | git mergetool submod ) &&
     test "$(cat submod/bar)" = "master submodule" &&
@@ -388,7 +410,7 @@ test_expect_success 'directory vs modified submodule' '
     test "$(cat submod/file16)" = "not a submodule" &&
     rm -rf submod.orig &&
 
-    git reset --hard &&
+    git reset --hard >/dev/null 2>&1 &&
     test_must_fail git merge master &&
     test -n "$(git ls-files -u)" &&
     test ! -e submod.orig &&
@@ -400,7 +422,7 @@ test_expect_success 'directory vs modified submodule' '
     ( cd submod && git clean -f && git reset --hard ) &&
     git submodule update -N &&
     test "$(cat submod/bar)" = "master submodule" &&
-    git reset --hard && rm -rf submod-movedaside &&
+    git reset --hard >/dev/null 2>&1 && rm -rf submod-movedaside &&
 
     git checkout -b test11.c master &&
     git submodule update -N &&
@@ -410,7 +432,7 @@ test_expect_success 'directory vs modified submodule' '
     git submodule update -N &&
     test "$(cat submod/bar)" = "master submodule" &&
 
-    git reset --hard &&
+    git reset --hard >/dev/null 2>&1 &&
     git submodule update -N &&
     test_must_fail git merge test11 &&
     test -n "$(git ls-files -u)" &&
@@ -418,7 +440,7 @@ test_expect_success 'directory vs modified submodule' '
     ( yes "r" | git mergetool submod ) &&
     test "$(cat submod/file16)" = "not a submodule" &&
 
-    git reset --hard master &&
+    git reset --hard master >/dev/null 2>&1 &&
     ( cd submod && git clean -f && git reset --hard ) &&
     git submodule update -N
 '
index 4048d106d4936ae1eef2ca3788a147e659bda65d..395adfc8a946bfd67b8a61cbef1366b78d9d855e 100755 (executable)
@@ -10,9 +10,6 @@ Testing basic diff tool invocation
 
 . ./test-lib.sh
 
-LF='
-'
-
 remove_config_vars()
 {
        # Unset all config variables used by git-difftool
index 6379ad60bcb9b5b56eb773b5a987745356c24df6..0d600163c8284a318fbd21f3a00dd7853b2f8956 100755 (executable)
@@ -509,6 +509,20 @@ test_expect_success 'grep -p -B5' '
        test_cmp expected actual
 '
 
+cat >expected <<EOF
+hello.c=int main(int argc, const char **argv)
+hello.c-{
+hello.c-       printf("Hello world.\n");
+hello.c:       return 0;
+hello.c-       /* char ?? */
+hello.c-}
+EOF
+
+test_expect_success 'grep -W' '
+       git grep -W return >actual &&
+       test_cmp expected actual
+'
+
 test_expect_success 'grep from a subdirectory to search wider area (1)' '
        mkdir -p s &&
        (
@@ -716,4 +730,99 @@ test_expect_success LIBPCRE 'grep -G -F -E -P pattern' '
        test_cmp expected actual
 '
 
+test_config() {
+       git config "$1" "$2" &&
+       test_when_finished "git config --unset $1"
+}
+
+cat >expected <<EOF
+hello.c<RED>:<RESET>int main(int argc, const char **argv)
+hello.c<RED>-<RESET>{
+<RED>--<RESET>
+hello.c<RED>:<RESET>   /* char ?? */
+hello.c<RED>-<RESET>}
+<RED>--<RESET>
+hello_world<RED>:<RESET>Hello_world
+hello_world<RED>-<RESET>HeLLo_world
+EOF
+
+test_expect_success 'grep --color, separator' '
+       test_config color.grep.context          normal &&
+       test_config color.grep.filename         normal &&
+       test_config color.grep.function         normal &&
+       test_config color.grep.linenumber       normal &&
+       test_config color.grep.match            normal &&
+       test_config color.grep.selected         normal &&
+       test_config color.grep.separator        red &&
+
+       git grep --color=always -A1 -e char -e lo_w hello.c hello_world |
+       test_decode_color >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+hello.c:int main(int argc, const char **argv)
+hello.c:       /* char ?? */
+
+hello_world:Hello_world
+EOF
+
+test_expect_success 'grep --break' '
+       git grep --break -e char -e lo_w hello.c hello_world >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+hello.c:int main(int argc, const char **argv)
+hello.c-{
+--
+hello.c:       /* char ?? */
+hello.c-}
+
+hello_world:Hello_world
+hello_world-HeLLo_world
+EOF
+
+test_expect_success 'grep --break with context' '
+       git grep --break -A1 -e char -e lo_w hello.c hello_world >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+hello.c
+int main(int argc, const char **argv)
+       /* char ?? */
+hello_world
+Hello_world
+EOF
+
+test_expect_success 'grep --heading' '
+       git grep --heading -e char -e lo_w hello.c hello_world >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+<BOLD;GREEN>hello.c<RESET>
+2:int main(int argc, const <BLACK;BYELLOW>char<RESET> **argv)
+6:     /* <BLACK;BYELLOW>char<RESET> ?? */
+
+<BOLD;GREEN>hello_world<RESET>
+3:Hel<BLACK;BYELLOW>lo_w<RESET>orld
+EOF
+
+test_expect_success 'mimic ack-grep --group' '
+       test_config color.grep.context          normal &&
+       test_config color.grep.filename         "bold green" &&
+       test_config color.grep.function         normal &&
+       test_config color.grep.linenumber       normal &&
+       test_config color.grep.match            "black yellow" &&
+       test_config color.grep.selected         normal &&
+       test_config color.grep.separator        normal &&
+
+       git grep --break --heading -n --color \
+               -e char -e lo_w hello.c hello_world |
+       test_decode_color >actual &&
+       test_cmp expected actual
+'
+
 test_done
index 3ab43902b3412d887794b7ca41d37b67af657361..8c9539e1b47a817fff98bad1ed1a99e890386ec9 100755 (executable)
@@ -38,4 +38,17 @@ test_expect_success 'verify svn:mergeinfo' '
        test "$mergeinfo" = "/branches/foo:1-10"
 '
 
+test_expect_success 'change svn:mergeinfo multiline' '
+       touch baz &&
+       git add baz &&
+       git commit -m "baz" &&
+       git svn dcommit --mergeinfo="/branches/bar:1-10 /branches/other:3-5,8,10-11"
+'
+
+test_expect_success 'verify svn:mergeinfo multiline' '
+       mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/trunk)
+       test "$mergeinfo" = "/branches/bar:1-10
+/branches/other:3-5,8,10-11"
+'
+
 test_done
diff --git a/t/t9159-git-svn-no-parent-mergeinfo.sh b/t/t9159-git-svn-no-parent-mergeinfo.sh
new file mode 100755 (executable)
index 0000000..69e4815
--- /dev/null
@@ -0,0 +1,41 @@
+#!/bin/sh
+test_description='git svn handling of root commits in merge ranges'
+. ./lib-git-svn.sh
+
+svn_ver="$(svn --version --quiet)"
+case $svn_ver in
+0.* | 1.[0-4].*)
+       skip_all="skipping git-svn test - SVN too old ($svn_ver)"
+       test_done
+       ;;
+esac
+
+test_expect_success 'test handling of root commits in merge ranges' '
+       mkdir -p init/trunk init/branches init/tags &&
+       echo "r1" > init/trunk/file.txt &&
+       svn_cmd import -m "initial import" init "$svnrepo" &&
+       svn_cmd co "$svnrepo" tmp &&
+       (
+               cd tmp &&
+               echo "r2" > trunk/file.txt &&
+               svn_cmd commit -m "Modify file.txt on trunk" &&
+               svn_cmd cp trunk@1 branches/a &&
+               svn_cmd commit -m "Create branch a from trunk r1" &&
+               svn_cmd propset svn:mergeinfo /trunk:1-2 branches/a &&
+               svn_cmd commit -m "Fake merge of trunk r2 into branch a" &&
+               mkdir branches/b &&
+               echo "r5" > branches/b/file2.txt &&
+               svn_cmd add branches/b &&
+               svn_cmd commit -m "Create branch b from thin air" &&
+               echo "r6" > branches/b/file2.txt &&
+               svn_cmd commit -m "Modify file2.txt on branch b" &&
+               svn_cmd cp branches/b@5 branches/c &&
+               svn_cmd commit -m "Create branch c from branch b r5" &&
+               svn_cmd propset svn:mergeinfo /branches/b:5-6 branches/c &&
+               svn_cmd commit -m "Fake merge of branch b r6 into branch c"
+       ) &&
+       git svn init -s "$svnrepo" &&
+       git svn fetch
+       '
+
+test_done
diff --git a/t/t9160-git-svn-preserve-empty-dirs.sh b/t/t9160-git-svn-preserve-empty-dirs.sh
new file mode 100755 (executable)
index 0000000..b4a4434
--- /dev/null
@@ -0,0 +1,153 @@
+#!/bin/sh
+#
+# Copyright (c) 2011 Ray Chen
+#
+
+test_description='git svn test (option --preserve-empty-dirs)
+
+This test uses git to clone a Subversion repository that contains empty
+directories, and checks that corresponding directories are created in the
+local Git repository with placeholder files.'
+
+. ./lib-git-svn.sh
+
+say 'define NO_SVN_TESTS to skip git svn tests'
+GIT_REPO=git-svn-repo
+
+test_expect_success 'initialize source svn repo containing empty dirs' '
+       svn_cmd mkdir -m x "$svnrepo"/trunk &&
+       svn_cmd co "$svnrepo"/trunk "$SVN_TREE" &&
+       (
+               cd "$SVN_TREE" &&
+               mkdir -p 1 2 3/a 3/b 4 5 6 &&
+               echo "First non-empty file"  > 2/file1.txt &&
+               echo "Second non-empty file" > 2/file2.txt &&
+               echo "Third non-empty file"  > 3/a/file1.txt &&
+               echo "Fourth non-empty file" > 3/b/file1.txt &&
+               svn_cmd add 1 2 3 4 5 6 &&
+               svn_cmd commit -m "initial commit" &&
+
+               mkdir 4/a &&
+               svn_cmd add 4/a &&
+               svn_cmd commit -m "nested empty directory" &&
+               mkdir 4/a/b &&
+               svn_cmd add 4/a/b &&
+               svn_cmd commit -m "deeply nested empty directory" &&
+               mkdir 4/a/b/c &&
+               svn_cmd add 4/a/b/c &&
+               svn_cmd commit -m "really deeply nested empty directory" &&
+               echo "Kill the placeholder file" > 4/a/b/c/foo &&
+               svn_cmd add 4/a/b/c/foo &&
+               svn_cmd commit -m "Regular file to remove placeholder" &&
+
+               svn_cmd del 2/file2.txt &&
+               svn_cmd del 3/b &&
+               svn_cmd commit -m "delete non-last entry in directory" &&
+
+               svn_cmd del 2/file1.txt &&
+               svn_cmd del 3/a &&
+               svn_cmd commit -m "delete last entry in directory" &&
+
+               echo "Conflict file" > 5/.placeholder &&
+               mkdir 6/.placeholder &&
+               svn_cmd add 5/.placeholder 6/.placeholder &&
+               svn_cmd commit -m "Placeholder Namespace conflict"
+       ) &&
+       rm -rf "$SVN_TREE"
+'
+
+test_expect_success 'clone svn repo with --preserve-empty-dirs' '
+       git svn clone "$svnrepo"/trunk --preserve-empty-dirs "$GIT_REPO"
+'
+
+# "$GIT_REPO"/1 should only contain the placeholder file.
+test_expect_success 'directory empty from inception' '
+       test -f "$GIT_REPO"/1/.gitignore &&
+       test $(find "$GIT_REPO"/1 -type f | wc -l) = "1"
+'
+
+# "$GIT_REPO"/2 and "$GIT_REPO"/3 should only contain the placeholder file.
+test_expect_success 'directory empty from subsequent svn commit' '
+       test -f "$GIT_REPO"/2/.gitignore &&
+       test $(find "$GIT_REPO"/2 -type f | wc -l) = "1" &&
+       test -f "$GIT_REPO"/3/.gitignore &&
+       test $(find "$GIT_REPO"/3 -type f | wc -l) = "1"
+'
+
+# No placeholder files should exist in "$GIT_REPO"/4, even though one was
+# generated for every sub-directory at some point in the repo's history.
+test_expect_success 'add entry to previously empty directory' '
+       test $(find "$GIT_REPO"/4 -type f | wc -l) = "1" &&
+       test -f "$GIT_REPO"/4/a/b/c/foo
+'
+
+# The HEAD~2 commit should not have introduced .gitignore placeholder files.
+test_expect_success 'remove non-last entry from directory' '
+       (
+               cd "$GIT_REPO" &&
+               git checkout HEAD~2
+       ) &&
+       test_must_fail test -f "$GIT_REPO"/2/.gitignore &&
+       test_must_fail test -f "$GIT_REPO"/3/.gitignore
+'
+
+# After re-cloning the repository with --placeholder-file specified, there
+# should be 5 files named ".placeholder" in the local Git repo.
+test_expect_success 'clone svn repo with --placeholder-file specified' '
+       rm -rf "$GIT_REPO" &&
+       git svn clone "$svnrepo"/trunk --preserve-empty-dirs \
+               --placeholder-file=.placeholder "$GIT_REPO" &&
+       find "$GIT_REPO" -type f -name ".placeholder" &&
+       test $(find "$GIT_REPO" -type f -name ".placeholder" | wc -l) = "5"
+'
+
+# "$GIT_REPO"/5/.placeholder should be a file, and non-empty.
+test_expect_success 'placeholder namespace conflict with file' '
+       test -s "$GIT_REPO"/5/.placeholder
+'
+
+# "$GIT_REPO"/6/.placeholder should be a directory, and the "$GIT_REPO"/6 tree
+# should only contain one file: the placeholder.
+test_expect_success 'placeholder namespace conflict with directory' '
+       test -d "$GIT_REPO"/6/.placeholder &&
+       test -f "$GIT_REPO"/6/.placeholder/.placeholder &&
+       test $(find "$GIT_REPO"/6 -type f | wc -l) = "1"
+'
+
+# Prepare a second set of svn commits to test persistence during rebase.
+test_expect_success 'second set of svn commits and rebase' '
+       svn_cmd co "$svnrepo"/trunk "$SVN_TREE" &&
+       (
+               cd "$SVN_TREE" &&
+               mkdir -p 7 &&
+               echo "This should remove placeholder" > 1/file1.txt &&
+               echo "This should not remove placeholder" > 5/file1.txt &&
+               svn_cmd add 7 1/file1.txt 5/file1.txt &&
+               svn_cmd commit -m "subsequent svn commit for persistence tests"
+       ) &&
+       rm -rf "$SVN_TREE" &&
+       (
+               cd "$GIT_REPO" &&
+               git svn rebase
+       )
+'
+
+# Check that --preserve-empty-dirs and --placeholder-file flag state
+# stays persistent over multiple invocations.
+test_expect_success 'flag persistence during subsqeuent rebase' '
+       test -f "$GIT_REPO"/7/.placeholder &&
+       test $(find "$GIT_REPO"/7 -type f | wc -l) = "1"
+'
+
+# Check that placeholder files are properly removed when unnecessary,
+# even across multiple invocations.
+test_expect_success 'placeholder list persistence during subsqeuent rebase' '
+       test -f "$GIT_REPO"/1/file1.txt &&
+       test $(find "$GIT_REPO"/1 -type f | wc -l) = "1" &&
+
+       test -f "$GIT_REPO"/5/file1.txt &&
+       test -f "$GIT_REPO"/5/.placeholder &&
+       test $(find "$GIT_REPO"/5 -type f | wc -l) = "2"
+'
+
+test_done
diff --git a/t/t9161-git-svn-mergeinfo-push.sh b/t/t9161-git-svn-mergeinfo-push.sh
new file mode 100755 (executable)
index 0000000..6ef0c0b
--- /dev/null
@@ -0,0 +1,104 @@
+#!/bin/sh
+#
+# Portions copyright (c) 2007, 2009 Sam Vilain
+# Portions copyright (c) 2011 Bryan Jacobs
+#
+
+test_description='git-svn svn mergeinfo propagation'
+
+. ./lib-git-svn.sh
+
+test_expect_success 'load svn dump' "
+       svnadmin load -q '$rawsvnrepo' \
+         < '$TEST_DIRECTORY/t9161/branches.dump' &&
+       git svn init --minimize-url -R svnmerge \
+         -T trunk -b branches '$svnrepo' &&
+       git svn fetch --all
+       "
+
+test_expect_success 'propagate merge information' '
+       git config svn.pushmergeinfo yes &&
+       git checkout svnb1 &&
+       git merge --no-ff svnb2 &&
+       git svn dcommit
+       '
+
+test_expect_success 'check svn:mergeinfo' '
+       mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/branches/svnb1)
+       test "$mergeinfo" = "/branches/svnb2:3,8"
+       '
+
+test_expect_success 'merge another branch' '
+       git merge --no-ff svnb3 &&
+       git svn dcommit
+       '
+
+test_expect_success 'check primary parent mergeinfo respected' '
+       mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/branches/svnb1)
+       test "$mergeinfo" = "/branches/svnb2:3,8
+/branches/svnb3:4,9"
+       '
+
+test_expect_success 'merge existing merge' '
+       git merge --no-ff svnb4 &&
+       git svn dcommit
+       '
+
+test_expect_success "check both parents' mergeinfo respected" '
+       mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/branches/svnb1)
+       test "$mergeinfo" = "/branches/svnb2:3,8
+/branches/svnb3:4,9
+/branches/svnb4:5-6,10-12
+/branches/svnb5:6,11"
+       '
+
+test_expect_success 'make further commits to branch' '
+       git checkout svnb2 &&
+       touch newb2file &&
+       git add newb2file &&
+       git commit -m "later b2 commit" &&
+       touch newb2file-2 &&
+       git add newb2file-2 &&
+       git commit -m "later b2 commit 2" &&
+       git svn dcommit
+       '
+
+test_expect_success 'second forward merge' '
+       git checkout svnb1 &&
+       git merge --no-ff svnb2 &&
+       git svn dcommit
+       '
+
+test_expect_success 'check new mergeinfo added' '
+       mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/branches/svnb1)
+       test "$mergeinfo" = "/branches/svnb2:3,8,16-17
+/branches/svnb3:4,9
+/branches/svnb4:5-6,10-12
+/branches/svnb5:6,11"
+       '
+
+test_expect_success 'reintegration merge' '
+       git checkout svnb4 &&
+       git merge --no-ff svnb1 &&
+       git svn dcommit
+       '
+
+test_expect_success 'check reintegration mergeinfo' '
+       mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/branches/svnb4)
+       test "$mergeinfo" = "/branches/svnb1:2-4,7-9,13-18
+/branches/svnb2:3,8,16-17
+/branches/svnb3:4,9
+/branches/svnb4:5-6,10-12
+/branches/svnb5:6,11"
+       '
+
+test_expect_success 'dcommit a merge at the top of a stack' '
+       git checkout svnb1 &&
+       touch anotherfile &&
+       git add anotherfile &&
+       git commit -m "a commit" &&
+       git merge svnb4 &&
+       git svn dcommit
+       '
+
+test_done
diff --git a/t/t9161/branches.dump b/t/t9161/branches.dump
new file mode 100644 (file)
index 0000000..e61c3e7
--- /dev/null
@@ -0,0 +1,374 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 1ef08553-f2d1-45df-b38c-19af6b7c926d
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2011-09-02T16:08:02.941384Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 12
+Base commit
+
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:08:27.205062Z
+PROPS-END
+
+Node-path: branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 2
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 19
+Create branch svnb1
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:09:43.628137Z
+PROPS-END
+
+Node-path: branches/svnb1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Revision-number: 3
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 19
+Create branch svnb2
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:09:46.339930Z
+PROPS-END
+
+Node-path: branches/svnb2
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Revision-number: 4
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 19
+Create branch svnb3
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:09:49.394515Z
+PROPS-END
+
+Node-path: branches/svnb3
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Revision-number: 5
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 19
+Create branch svnb4
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:09:54.114607Z
+PROPS-END
+
+Node-path: branches/svnb4
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Revision-number: 6
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 19
+Create branch svnb5
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:09:58.602623Z
+PROPS-END
+
+Node-path: branches/svnb5
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Revision-number: 7
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 9
+b1 commit
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:10:20.292369Z
+PROPS-END
+
+Node-path: branches/svnb1/b1file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 8
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 9
+b2 commit
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:10:38.429199Z
+PROPS-END
+
+Node-path: branches/svnb2/b2file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 9
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 9
+b3 commit
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:10:52.843023Z
+PROPS-END
+
+Node-path: branches/svnb3/b3file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 10
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 9
+b4 commit
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:11:17.489870Z
+PROPS-END
+
+Node-path: branches/svnb4/b4file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 11
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 9
+b5 commit
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:11:32.277404Z
+PROPS-END
+
+Node-path: branches/svnb5/b5file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 12
+Prop-content-length: 192
+Content-length: 192
+
+K 7
+svn:log
+V 90
+Merge remote-tracking branch 'svnb5' into HEAD
+
+* svnb5:
+  b5 commit
+  Create branch svnb5
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:11:54.274722Z
+PROPS-END
+
+Node-path: branches/svnb4
+Node-kind: dir
+Node-action: change
+Prop-content-length: 56
+Content-length: 56
+
+K 13
+svn:mergeinfo
+V 21
+/branches/svnb5:6,11
+
+PROPS-END
+
+
+Node-path: branches/svnb4/b5file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709
+Content-length: 10
+
+PROPS-END
+
+
index e5da65b99fa1459836e516772fe8d13d038f502e..41db05cb4af6f42ef3065ba0576d8cfa3509c0ca 100755 (executable)
@@ -50,8 +50,8 @@ test_expect_success \
     'mkdir A B C D E F &&
      echo hello1 >A/newfile1.txt &&
      echo hello2 >B/newfile2.txt &&
-     cp "$TEST_DIRECTORY"/test9200a.png C/newfile3.png &&
-     cp "$TEST_DIRECTORY"/test9200a.png D/newfile4.png &&
+     cp "$TEST_DIRECTORY"/test-binary-1.png C/newfile3.png &&
+     cp "$TEST_DIRECTORY"/test-binary-1.png D/newfile4.png &&
      git add A/newfile1.txt &&
      git add B/newfile2.txt &&
      git add C/newfile3.png &&
@@ -76,8 +76,8 @@ test_expect_success \
      rm -f B/newfile2.txt &&
      rm -f C/newfile3.png &&
      echo Hello5  >E/newfile5.txt &&
-     cp "$TEST_DIRECTORY"/test9200b.png D/newfile4.png &&
-     cp "$TEST_DIRECTORY"/test9200a.png F/newfile6.png &&
+     cp "$TEST_DIRECTORY"/test-binary-2.png D/newfile4.png &&
+     cp "$TEST_DIRECTORY"/test-binary-1.png F/newfile6.png &&
      git add E/newfile5.txt &&
      git add F/newfile6.png &&
      git commit -a -m "Test: Remove, add and update" &&
@@ -165,7 +165,7 @@ test_expect_success \
      'mkdir "G g" &&
       echo ok then >"G g/with spaces.txt" &&
       git add "G g/with spaces.txt" && \
-      cp "$TEST_DIRECTORY"/test9200a.png "G g/with spaces.png" && \
+      cp "$TEST_DIRECTORY"/test-binary-1.png "G g/with spaces.png" && \
       git add "G g/with spaces.png" &&
       git commit -a -m "With spaces" &&
       id=$(git rev-list --max-count=1 HEAD) &&
@@ -177,7 +177,7 @@ test_expect_success \
 test_expect_success \
      'Update file with spaces in file name' \
      'echo Ok then >>"G g/with spaces.txt" &&
-      cat "$TEST_DIRECTORY"/test9200a.png >>"G g/with spaces.png" && \
+      cat "$TEST_DIRECTORY"/test-binary-1.png >>"G g/with spaces.png" && \
       git add "G g/with spaces.png" &&
       git commit -a -m "Update with spaces" &&
       id=$(git rev-list --max-count=1 HEAD) &&
@@ -202,7 +202,7 @@ test_expect_success \
      'mkdir -p Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö &&
       echo Foo >Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.txt &&
       git add Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.txt &&
-      cp "$TEST_DIRECTORY"/test9200a.png Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png &&
+      cp "$TEST_DIRECTORY"/test-binary-1.png Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png &&
       git add Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png &&
       git commit -a -m "Går det så går det" && \
       id=$(git rev-list --max-count=1 HEAD) &&
index 2a53640c5b29c19a2327570148638dbf94bc2afa..1a6c06631c1c2a8fc29b66c9265541b54e8a9715 100755 (executable)
@@ -94,6 +94,12 @@ data <<EOF
 An annotated tag without a tagger
 EOF
 
+tag series-A-blob
+from :3
+data <<EOF
+An annotated tag that annotates a blob.
+EOF
+
 INPUT_END
 test_expect_success \
     'A: create pack from stdin' \
@@ -151,6 +157,18 @@ test_expect_success 'A: verify tag/series-A' '
        test_cmp expect actual
 '
 
+cat >expect <<EOF
+object $(git rev-parse refs/heads/master:file3)
+type blob
+tag series-A-blob
+
+An annotated tag that annotates a blob.
+EOF
+test_expect_success 'A: verify tag/series-A-blob' '
+       git cat-file tag tags/series-A-blob >actual &&
+       test_cmp expect actual
+'
+
 cat >expect <<EOF
 :2 `git rev-parse --verify master:file2`
 :3 `git rev-parse --verify master:file3`
@@ -169,6 +187,55 @@ test_expect_success \
                </dev/null &&
        test_cmp expect marks.new'
 
+test_tick
+new_blob=$(echo testing | git hash-object --stdin)
+cat >input <<INPUT_END
+tag series-A-blob-2
+from $(git rev-parse refs/heads/master:file3)
+data <<EOF
+Tag blob by sha1.
+EOF
+
+blob
+mark :6
+data <<EOF
+testing
+EOF
+
+commit refs/heads/new_blob
+committer  <> 0 +0000
+data 0
+M 644 :6 new_blob
+#pretend we got sha1 from fast-import
+ls "new_blob"
+
+tag series-A-blob-3
+from $new_blob
+data <<EOF
+Tag new_blob.
+EOF
+INPUT_END
+
+cat >expect <<EOF
+object $(git rev-parse refs/heads/master:file3)
+type blob
+tag series-A-blob-2
+
+Tag blob by sha1.
+object $new_blob
+type blob
+tag series-A-blob-3
+
+Tag new_blob.
+EOF
+
+test_expect_success \
+       'A: tag blob by sha1' \
+       'git fast-import <input &&
+       git cat-file tag tags/series-A-blob-2 >actual &&
+       git cat-file tag tags/series-A-blob-3 >>actual &&
+       test_cmp expect actual'
+
 test_tick
 cat >input <<INPUT_END
 commit refs/heads/verify--import-marks
@@ -324,6 +391,105 @@ test_expect_success \
         test `git rev-parse master` = `git rev-parse TEMP_TAG^`'
 rm -f .git/TEMP_TAG
 
+git gc 2>/dev/null >/dev/null
+git prune 2>/dev/null >/dev/null
+
+cat >input <<INPUT_END
+commit refs/heads/empty-committer-1
+committer  <> $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: accept empty committer' '
+       git fast-import <input &&
+       out=$(git fsck) &&
+       echo "$out" &&
+       test -z "$out"
+'
+git update-ref -d refs/heads/empty-committer-1 || true
+
+git gc 2>/dev/null >/dev/null
+git prune 2>/dev/null >/dev/null
+
+cat >input <<INPUT_END
+commit refs/heads/empty-committer-2
+committer <a@b.com> $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: accept and fixup committer with no name' '
+       git fast-import <input &&
+       out=$(git fsck) &&
+       echo "$out" &&
+       test -z "$out"
+'
+git update-ref -d refs/heads/empty-committer-2 || true
+
+git gc 2>/dev/null >/dev/null
+git prune 2>/dev/null >/dev/null
+
+cat >input <<INPUT_END
+commit refs/heads/invalid-committer
+committer Name email> $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: fail on invalid committer (1)' '
+       test_must_fail git fast-import <input
+'
+git update-ref -d refs/heads/invalid-committer || true
+
+cat >input <<INPUT_END
+commit refs/heads/invalid-committer
+committer Name <e<mail> $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: fail on invalid committer (2)' '
+       test_must_fail git fast-import <input
+'
+git update-ref -d refs/heads/invalid-committer || true
+
+cat >input <<INPUT_END
+commit refs/heads/invalid-committer
+committer Name <email>> $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: fail on invalid committer (3)' '
+       test_must_fail git fast-import <input
+'
+git update-ref -d refs/heads/invalid-committer || true
+
+cat >input <<INPUT_END
+commit refs/heads/invalid-committer
+committer Name <email $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: fail on invalid committer (4)' '
+       test_must_fail git fast-import <input
+'
+git update-ref -d refs/heads/invalid-committer || true
+
+cat >input <<INPUT_END
+commit refs/heads/invalid-committer
+committer Name<email> $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: fail on invalid committer (5)' '
+       test_must_fail git fast-import <input
+'
+git update-ref -d refs/heads/invalid-committer || true
+
 ###
 ### series C
 ###
@@ -734,6 +900,47 @@ test_expect_success \
         git diff-tree --abbrev --raw L^ L >output &&
         test_cmp expect output'
 
+cat >input <<INPUT_END
+blob
+mark :1
+data <<EOF
+the data
+EOF
+
+commit refs/heads/L2
+committer C O Mitter <committer@example.com> 1112912473 -0700
+data <<COMMIT
+init L2
+COMMIT
+M 644 :1 a/b/c
+M 644 :1 a/b/d
+M 644 :1 a/e/f
+
+commit refs/heads/L2
+committer C O Mitter <committer@example.com> 1112912473 -0700
+data <<COMMIT
+update L2
+COMMIT
+C a g
+C a/e g/b
+M 644 :1 g/b/h
+INPUT_END
+
+cat <<EOF >expect
+g/b/f
+g/b/h
+EOF
+
+test_expect_success \
+    'L: nested tree copy does not corrupt deltas' \
+       'git fast-import <input &&
+       git ls-tree L2 g/b/ >tmp &&
+       cat tmp | cut -f 2 >actual &&
+       test_cmp expect actual &&
+       git fsck `git rev-parse L2`'
+
+git update-ref -d refs/heads/L2
+
 ###
 ### series M
 ###
@@ -1882,6 +2089,53 @@ test_expect_success 'R: --import-marks-if-exists' '
        test_cmp expect io.marks
 '
 
+test_expect_success 'R: feature import-marks-if-exists' '
+       rm -f io.marks &&
+       >expect &&
+
+       git fast-import --export-marks=io.marks <<-\EOF &&
+       feature import-marks-if-exists=not_io.marks
+       EOF
+       test_cmp expect io.marks &&
+
+       blob=$(echo hi | git hash-object --stdin) &&
+
+       echo ":1 $blob" >io.marks &&
+       echo ":1 $blob" >expect &&
+       echo ":2 $blob" >>expect &&
+
+       git fast-import --export-marks=io.marks <<-\EOF &&
+       feature import-marks-if-exists=io.marks
+       blob
+       mark :2
+       data 3
+       hi
+
+       EOF
+       test_cmp expect io.marks &&
+
+       echo ":3 $blob" >>expect &&
+
+       git fast-import --import-marks=io.marks \
+                       --export-marks=io.marks <<-\EOF &&
+       feature import-marks-if-exists=not_io.marks
+       blob
+       mark :3
+       data 3
+       hi
+
+       EOF
+       test_cmp expect io.marks &&
+
+       >expect &&
+
+       git fast-import --import-marks-if-exists=not_io.marks \
+                       --export-marks=io.marks <<-\EOF
+       feature import-marks-if-exists=io.marks
+       EOF
+       test_cmp expect io.marks
+'
+
 cat >input << EOF
 feature import-marks=marks.out
 feature export-marks=marks.new
@@ -2197,6 +2451,48 @@ test_expect_success 'R: quiet option results in no stats being output' '
     test_cmp empty output
 '
 
+test_expect_success 'R: feature done means terminating "done" is mandatory' '
+       echo feature done | test_must_fail git fast-import &&
+       test_must_fail git fast-import --done </dev/null
+'
+
+test_expect_success 'R: terminating "done" with trailing gibberish is ok' '
+       git fast-import <<-\EOF &&
+       feature done
+       done
+       trailing gibberish
+       EOF
+       git fast-import <<-\EOF
+       done
+       more trailing gibberish
+       EOF
+'
+
+test_expect_success 'R: terminating "done" within commit' '
+       cat >expect <<-\EOF &&
+       OBJID
+       :000000 100644 OBJID OBJID A    hello.c
+       :000000 100644 OBJID OBJID A    hello2.c
+       EOF
+       git fast-import <<-EOF &&
+       commit refs/heads/done-ends
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<EOT
+       Commit terminated by "done" command
+       EOT
+       M 100644 inline hello.c
+       data <<EOT
+       Hello, world.
+       EOT
+       C hello.c hello2.c
+       done
+       EOF
+       git rev-list done-ends |
+       git diff-tree -r --stdin --root --always |
+       sed -e "s/$_x40/OBJID/g" >actual &&
+       test_cmp expect actual
+'
+
 cat >input <<EOF
 option git non-existing-option
 EOF
index f823c05305e5021a8cca98d49256af3b1c008fb9..950d0ff498fda58c2d3d68dfb2cf50512f8f81ba 100755 (executable)
@@ -228,7 +228,7 @@ test_expect_success 'fast-export -C -C | fast-import' '
        mkdir new &&
        git --git-dir=new/.git init &&
        git fast-export -C -C --signed-tags=strip --all > output &&
-       grep "^C \"file6\" \"file7\"\$" output &&
+       grep "^C file6 file7\$" output &&
        cat output |
        (cd new &&
         git fast-import &&
@@ -414,4 +414,30 @@ test_expect_success SYMLINKS 'directory becomes symlink'        '
        (cd result && git show master:foo)
 '
 
+test_expect_success 'fast-export quotes pathnames' '
+       git init crazy-paths &&
+       (cd crazy-paths &&
+        blob=`echo foo | git hash-object -w --stdin` &&
+        git update-index --add \
+               --cacheinfo 100644 $blob "$(printf "path with\\nnewline")" \
+               --cacheinfo 100644 $blob "path with \"quote\"" \
+               --cacheinfo 100644 $blob "path with \\backslash" \
+               --cacheinfo 100644 $blob "path with space" &&
+        git commit -m addition &&
+        git ls-files -z -s | perl -0pe "s{\\t}{$&subdir/}" >index &&
+        git read-tree --empty &&
+        git update-index -z --index-info <index &&
+        git commit -m rename &&
+        git read-tree --empty &&
+        git commit -m deletion &&
+        git fast-export HEAD >export.out &&
+        git rev-list HEAD >expect &&
+        git init result &&
+        cd result &&
+        git fast-import <../export.out &&
+        git rev-list HEAD >actual &&
+        test_cmp ../expect actual
+       )
+'
+
 test_done
index 33b0127651da0c4c178e62c269179b641e15f9f6..01ba041fdefe61a3e712344bfe257a1b9cba3b4b 100755 (executable)
@@ -45,29 +45,33 @@ test_expect_success 'add p4 files' '
        cd "$TRASH_DIRECTORY"
 '
 
+cleanup_git() {
+       cd "$TRASH_DIRECTORY" &&
+       rm -rf "$git" &&
+       mkdir "$git"
+}
+
 test_expect_success 'basic git-p4 clone' '
        "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
        cd "$git" &&
        git log --oneline >lines &&
-       test_line_count = 1 lines &&
-       cd .. &&
-       rm -rf "$git" && mkdir "$git"
+       test_line_count = 1 lines
 '
 
 test_expect_success 'git-p4 clone @all' '
        "$GITP4" clone --dest="$git" //depot@all &&
+       test_when_finished cleanup_git &&
        cd "$git" &&
        git log --oneline >lines &&
-       test_line_count = 2 lines &&
-       cd .. &&
-       rm -rf "$git" && mkdir "$git"
+       test_line_count = 2 lines
 '
 
 test_expect_success 'git-p4 sync uninitialized repo' '
        test_create_repo "$git" &&
+       test_when_finished cleanup_git &&
        cd "$git" &&
-       test_must_fail "$GITP4" sync &&
-       rm -rf "$git" && mkdir "$git"
+       test_must_fail "$GITP4" sync
 '
 
 #
@@ -76,19 +80,18 @@ test_expect_success 'git-p4 sync uninitialized repo' '
 #
 test_expect_success 'git-p4 sync new branch' '
        test_create_repo "$git" &&
+       test_when_finished cleanup_git &&
        cd "$git" &&
        test_commit head &&
        "$GITP4" sync --branch=refs/remotes/p4/depot //depot@all &&
        git log --oneline p4/depot >lines &&
-       cat lines &&
-       test_line_count = 2 lines &&
-       cd .. &&
-       rm -rf "$git" && mkdir "$git"
+       test_line_count = 2 lines
 '
 
 test_expect_success 'exit when p4 fails to produce marshaled output' '
        badp4dir="$TRASH_DIRECTORY/badp4dir" &&
        mkdir -p "$badp4dir" &&
+       test_when_finished "rm -rf $badp4dir" &&
        cat >"$badp4dir"/p4 <<-EOF &&
        #!$SHELL_PATH
        exit 1
@@ -106,29 +109,26 @@ test_expect_success 'add p4 files with wildcards in the names' '
        echo file-wild-at >file-wild@at &&
        echo file-wild-percent >file-wild%percent &&
        p4 add -f file-wild* &&
-       p4 submit -d "file wildcards" &&
-       cd "$TRASH_DIRECTORY"
+       p4 submit -d "file wildcards"
 '
 
 test_expect_success 'wildcard files git-p4 clone' '
        "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
        cd "$git" &&
        test -f file-wild#hash &&
        test -f file-wild\*star &&
        test -f file-wild@at &&
-       test -f file-wild%percent &&
-       cd "$TRASH_DIRECTORY" &&
-       rm -rf "$git" && mkdir "$git"
+       test -f file-wild%percent
 '
 
 test_expect_success 'clone bare' '
        "$GITP4" clone --dest="$git" --bare //depot &&
+       test_when_finished cleanup_git &&
        cd "$git" &&
        test ! -d .git &&
        bare=`git config --get core.bare` &&
-       test "$bare" = true &&
-       cd "$TRASH_DIRECTORY" &&
-       rm -rf "$git" && mkdir "$git"
+       test "$bare" = true
 '
 
 p4_add_user() {
@@ -173,6 +173,7 @@ test_expect_success 'preserve users' '
        p4_add_user bob Bob &&
        p4_grant_admin alice &&
        "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
        cd "$git" &&
        echo "username: a change by alice" >> file1 &&
        echo "username: a change by bob" >> file2 &&
@@ -181,29 +182,28 @@ test_expect_success 'preserve users' '
        git config git-p4.skipSubmitEditCheck true &&
        P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit --preserve-user &&
        p4_check_commit_author file1 alice &&
-       p4_check_commit_author file2 bob &&
-       cd "$TRASH_DIRECTORY" &&
-       rm -rf "$git" && mkdir "$git"
+       p4_check_commit_author file2 bob
 '
 
 # Test username support, submitting as bob, who lacks admin rights. Should
 # not submit change to p4 (git diff should show deltas).
 test_expect_success 'refuse to preserve users without perms' '
        "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
        cd "$git" &&
+       git config git-p4.skipSubmitEditCheck true &&
        echo "username-noperms: a change by alice" >> file1 &&
        git commit --author "Alice <alice@localhost>" -m "perms: a change by alice" file1 &&
        ! P4EDITOR=touch P4USER=bob P4PASSWD=secret "$GITP4" commit --preserve-user &&
-       ! git diff --exit-code HEAD..p4/master > /dev/null &&
-       cd "$TRASH_DIRECTORY" &&
-       rm -rf "$git" && mkdir "$git"
+       ! git diff --exit-code HEAD..p4/master > /dev/null
 '
 
 # What happens with unknown author? Without allowMissingP4Users it should fail.
 test_expect_success 'preserve user where author is unknown to p4' '
        "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
        cd "$git" &&
-       git config git-p4.skipSubmitEditCheck true
+       git config git-p4.skipSubmitEditCheck true &&
        echo "username-bob: a change by bob" >> file1 &&
        git commit --author "Bob <bob@localhost>" -m "preserve: a change by bob" file1 &&
        echo "username-unknown: a change by charlie" >> file1 &&
@@ -215,9 +215,7 @@ test_expect_success 'preserve user where author is unknown to p4' '
        git config git-p4.preserveUser true &&
        P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit &&
        git diff --exit-code HEAD..p4/master > /dev/null &&
-       p4_check_commit_author file1 alice &&
-       cd "$TRASH_DIRECTORY" &&
-       rm -rf "$git" && mkdir "$git"
+       p4_check_commit_author file1 alice
 '
 
 # If we're *not* using --preserve-user, git-p4 should warn if we're submitting
@@ -226,33 +224,247 @@ test_expect_success 'preserve user where author is unknown to p4' '
 # Test: warning disabled and user is the same.
 test_expect_success 'not preserving user with mixed authorship' '
        "$GITP4" clone --dest="$git" //depot &&
-       (
-               cd "$git" &&
-               git config git-p4.skipSubmitEditCheck true &&
-               p4_add_user derek Derek &&
-
-               make_change_by_user usernamefile3 Derek derek@localhost &&
-               P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
-               grep "git author derek@localhost does not match" actual &&
-
-               make_change_by_user usernamefile3 Charlie charlie@localhost &&
-               P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
-               grep "git author charlie@localhost does not match" actual &&
-
-               make_change_by_user usernamefile3 alice alice@localhost &&
-               P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
-               ! grep "git author.*does not match" actual &&
-
-               git config git-p4.skipUserNameCheck true &&
-               make_change_by_user usernamefile3 Charlie charlie@localhost &&
-               P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
-               ! grep "git author.*does not match" actual &&
-
-               p4_check_commit_author usernamefile3 alice
-       ) &&
-       rm -rf "$git" && mkdir "$git"
+       test_when_finished cleanup_git &&
+       cd "$git" &&
+       git config git-p4.skipSubmitEditCheck true &&
+       p4_add_user derek Derek &&
+
+       make_change_by_user usernamefile3 Derek derek@localhost &&
+       P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
+       grep "git author derek@localhost does not match" actual &&
+
+       make_change_by_user usernamefile3 Charlie charlie@localhost &&
+       P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
+       grep "git author charlie@localhost does not match" actual &&
+
+       make_change_by_user usernamefile3 alice alice@localhost &&
+       P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
+       ! grep "git author.*does not match" actual &&
+
+       git config git-p4.skipUserNameCheck true &&
+       make_change_by_user usernamefile3 Charlie charlie@localhost &&
+       P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
+       ! grep "git author.*does not match" actual &&
+
+       p4_check_commit_author usernamefile3 alice
 '
 
+marshal_dump() {
+       what=$1
+       python -c 'import marshal, sys; d = marshal.load(sys.stdin); print d["'$what'"]'
+}
+
+# Sleep a bit so that the top-most p4 change did not happen "now".  Then
+# import the repo and make sure that the initial import has the same time
+# as the top-most change.
+test_expect_success 'initial import time from top change time' '
+       p4change=$(p4 -G changes -m 1 //depot/... | marshal_dump change) &&
+       p4time=$(p4 -G changes -m 1 //depot/... | marshal_dump time) &&
+       sleep 3 &&
+       "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       cd "$git" &&
+       gittime=$(git show -s --raw --pretty=format:%at HEAD) &&
+       echo $p4time $gittime &&
+       test $p4time = $gittime
+'
+
+# Rename a file and confirm that rename is not detected in P4.
+# Rename the new file again with detectRenames option enabled and confirm that
+# this is detected in P4.
+# Rename the new file again adding an extra line, configure a big threshold in
+# detectRenames and confirm that rename is not detected in P4.
+# Repeat, this time with a smaller threshold and confirm that the rename is
+# detected in P4.
+test_expect_success 'detect renames' '
+       "$GITP4" clone --dest="$git" //depot@all &&
+       test_when_finished cleanup_git &&
+       cd "$git" &&
+       git config git-p4.skipSubmitEditCheck true &&
+
+       git mv file1 file4 &&
+       git commit -a -m "Rename file1 to file4" &&
+       git diff-tree -r -M HEAD &&
+       "$GITP4" submit &&
+       p4 filelog //depot/file4 &&
+       ! p4 filelog //depot/file4 | grep -q "branch from" &&
+
+       git mv file4 file5 &&
+       git commit -a -m "Rename file4 to file5" &&
+       git diff-tree -r -M HEAD &&
+       git config git-p4.detectRenames true &&
+       "$GITP4" submit &&
+       p4 filelog //depot/file5 &&
+       p4 filelog //depot/file5 | grep -q "branch from //depot/file4" &&
+
+       git mv file5 file6 &&
+       echo update >>file6 &&
+       git add file6 &&
+       git commit -a -m "Rename file5 to file6 with changes" &&
+       git diff-tree -r -M HEAD &&
+       level=$(git diff-tree -r -M HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/R0*//") &&
+       test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 &&
+       git config git-p4.detectRenames $((level + 2)) &&
+       "$GITP4" submit &&
+       p4 filelog //depot/file6 &&
+       ! p4 filelog //depot/file6 | grep -q "branch from" &&
+
+       git mv file6 file7 &&
+       echo update >>file7 &&
+       git add file7 &&
+       git commit -a -m "Rename file6 to file7 with changes" &&
+       git diff-tree -r -M HEAD &&
+       level=$(git diff-tree -r -M HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/R0*//") &&
+       test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 &&
+       git config git-p4.detectRenames $((level - 2)) &&
+       "$GITP4" submit &&
+       p4 filelog //depot/file7 &&
+       p4 filelog //depot/file7 | grep -q "branch from //depot/file6"
+'
+
+# Copy a file and confirm that copy is not detected in P4.
+# Copy a file with detectCopies option enabled and confirm that copy is not
+# detected in P4.
+# Modify and copy a file with detectCopies option enabled and confirm that copy
+# is detected in P4.
+# Copy a file with detectCopies and detectCopiesHarder options enabled and
+# confirm that copy is detected in P4.
+# Modify and copy a file, configure a bigger threshold in detectCopies and
+# confirm that copy is not detected in P4.
+# Modify and copy a file, configure a smaller threshold in detectCopies and
+# confirm that copy is detected in P4.
+test_expect_success 'detect copies' '
+       "$GITP4" clone --dest="$git" //depot@all &&
+       test_when_finished cleanup_git &&
+       cd "$git" &&
+       git config git-p4.skipSubmitEditCheck true &&
+
+       cp file2 file8 &&
+       git add file8 &&
+       git commit -a -m "Copy file2 to file8" &&
+       git diff-tree -r -C HEAD &&
+       "$GITP4" submit &&
+       p4 filelog //depot/file8 &&
+       ! p4 filelog //depot/file8 | grep -q "branch from" &&
+
+       cp file2 file9 &&
+       git add file9 &&
+       git commit -a -m "Copy file2 to file9" &&
+       git diff-tree -r -C HEAD &&
+       git config git-p4.detectCopies true &&
+       "$GITP4" submit &&
+       p4 filelog //depot/file9 &&
+       ! p4 filelog //depot/file9 | grep -q "branch from" &&
+
+       echo "file2" >>file2 &&
+       cp file2 file10 &&
+       git add file2 file10 &&
+       git commit -a -m "Modify and copy file2 to file10" &&
+       git diff-tree -r -C HEAD &&
+       "$GITP4" submit &&
+       p4 filelog //depot/file10 &&
+       p4 filelog //depot/file10 | grep -q "branch from //depot/file" &&
+
+       cp file2 file11 &&
+       git add file11 &&
+       git commit -a -m "Copy file2 to file11" &&
+       git diff-tree -r -C --find-copies-harder HEAD &&
+       src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
+       test "$src" = file10 &&
+       git config git-p4.detectCopiesHarder true &&
+       "$GITP4" submit &&
+       p4 filelog //depot/file11 &&
+       p4 filelog //depot/file11 | grep -q "branch from //depot/file" &&
+
+       cp file2 file12 &&
+       echo "some text" >>file12 &&
+       git add file12 &&
+       git commit -a -m "Copy file2 to file12 with changes" &&
+       git diff-tree -r -C --find-copies-harder HEAD &&
+       level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") &&
+       test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 &&
+       src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
+       test "$src" = file10 &&
+       git config git-p4.detectCopies $((level + 2)) &&
+       "$GITP4" submit &&
+       p4 filelog //depot/file12 &&
+       ! p4 filelog //depot/file12 | grep -q "branch from" &&
+
+       cp file2 file13 &&
+       echo "different text" >>file13 &&
+       git add file13 &&
+       git commit -a -m "Copy file2 to file13 with changes" &&
+       git diff-tree -r -C --find-copies-harder HEAD &&
+       level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") &&
+       test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 &&
+       src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
+       test "$src" = file10 &&
+       git config git-p4.detectCopies $((level - 2)) &&
+       "$GITP4" submit &&
+       p4 filelog //depot/file13 &&
+       p4 filelog //depot/file13 | grep -q "branch from //depot/file"
+'
+
+# Create a simple branch structure in P4 depot to check if it is correctly
+# cloned.
+test_expect_success 'add simple p4 branches' '
+       cd "$cli" &&
+       mkdir branch1 &&
+       cd branch1 &&
+       echo file1 >file1 &&
+       echo file2 >file2 &&
+       p4 add file1 file2 &&
+       p4 submit -d "branch1" &&
+       p4 integrate //depot/branch1/... //depot/branch2/... &&
+       p4 submit -d "branch2" &&
+       echo file3 >file3 &&
+       p4 add file3 &&
+       p4 submit -d "add file3 in branch1" &&
+       p4 open file2 &&
+       echo update >>file2 &&
+       p4 submit -d "update file2 in branch1" &&
+       p4 integrate //depot/branch1/... //depot/branch3/... &&
+       p4 submit -d "branch3" &&
+       cd "$TRASH_DIRECTORY"
+'
+
+# Configure branches through git-config and clone them.
+# All files are tested to make sure branches were cloned correctly.
+# Finally, make an update to branch1 on P4 side to check if it is imported
+# correctly by git-p4.
+test_expect_success 'git-p4 clone simple branches' '
+       test_when_finished cleanup_git &&
+       test_create_repo "$git" &&
+       cd "$git" &&
+       git config git-p4.branchList branch1:branch2 &&
+       git config --add git-p4.branchList branch1:branch3 &&
+       "$GITP4" clone --dest=. --detect-branches //depot@all &&
+       git log --all --graph --decorate --stat &&
+       git reset --hard p4/depot/branch1 &&
+       test -f file1 &&
+       test -f file2 &&
+       test -f file3 &&
+       grep -q update file2 &&
+       git reset --hard p4/depot/branch2 &&
+       test -f file1 &&
+       test -f file2 &&
+       test ! -f file3 &&
+       ! grep -q update file2 &&
+       git reset --hard p4/depot/branch3 &&
+       test -f file1 &&
+       test -f file2 &&
+       test -f file3 &&
+       grep -q update file2 &&
+       cd "$cli" &&
+       cd branch1 &&
+       p4 edit file2 &&
+       echo file2_ >>file2 &&
+       p4 submit -d "update file2 in branch1" &&
+       cd "$git" &&
+       git reset --hard p4/depot/branch1 &&
+       "$GITP4" rebase &&
+       grep -q file2_ file2
+'
 
 test_expect_success 'shutdown' '
        pid=`pgrep -f p4d` &&
diff --git a/t/test-binary-1.png b/t/test-binary-1.png
new file mode 100644 (file)
index 0000000..7b181d1
Binary files /dev/null and b/t/test-binary-1.png differ
diff --git a/t/test-binary-2.png b/t/test-binary-2.png
new file mode 100644 (file)
index 0000000..ac22ccb
Binary files /dev/null and b/t/test-binary-2.png differ
index 8c57a0059574ff78626ad0e26d3f75da9204b23d..bdd9513b84301275330d3dd7e49af05081ef9cd7 100644 (file)
@@ -92,6 +92,10 @@ _x40="$_x05$_x05$_x05$_x05$_x05$_x05$_x05$_x05"
 # Zero SHA-1
 _z40=0000000000000000000000000000000000000000
 
+# Line feed
+LF='
+'
+
 # Each test should start with something like this, after copyright notices:
 #
 # test_description='Description of this test...
@@ -357,6 +361,24 @@ test_chmod () {
        git update-index --add "--chmod=$@"
 }
 
+# Unset a configuration variable, but don't fail if it doesn't exist.
+test_unconfig () {
+       git config --unset-all "$@"
+       config_status=$?
+       case "$config_status" in
+       5) # ok, nothing to unset
+               config_status=0
+               ;;
+       esac
+       return $config_status
+}
+
+# Set git config, automatically unsetting it after the test is over.
+test_config () {
+       test_when_finished "test_unconfig '$1'" &&
+       git config "$@"
+}
+
 # Use test_set_prereq to tell that a particular prerequisite is available.
 # The prerequisite can later be checked for in two ways:
 #
@@ -444,15 +466,26 @@ test_debug () {
        test "$debug" = "" || eval "$1"
 }
 
+test_eval_ () {
+       # This is a separate function because some tests use
+       # "return" to end a test_expect_success block early.
+       eval >&3 2>&4 "$*"
+}
+
 test_run_ () {
        test_cleanup=:
-       eval >&3 2>&4 "$1"
+       expecting_failure=$2
+       test_eval_ "$1"
        eval_ret=$?
-       eval >&3 2>&4 "$test_cleanup"
+
+       if test -z "$immediate" || test $eval_ret = 0 || test -n "$expecting_failure"
+       then
+               test_eval_ "$test_cleanup"
+       fi
        if test "$verbose" = "t" && test -n "$HARNESS_ACTIVE"; then
                echo ""
        fi
-       return 0
+       return "$eval_ret"
 }
 
 test_skip () {
@@ -497,8 +530,7 @@ test_expect_failure () {
        if ! test_skip "$@"
        then
                say >&3 "checking known breakage: $2"
-               test_run_ "$2"
-               if [ "$?" = 0 -a "$eval_ret" = 0 ]
+               if test_run_ "$2" expecting_failure
                then
                        test_known_broken_ok_ "$1"
                else
@@ -516,8 +548,7 @@ test_expect_success () {
        if ! test_skip "$@"
        then
                say >&3 "expecting success: $2"
-               test_run_ "$2"
-               if [ "$?" = 0 -a "$eval_ret" = 0 ]
+               if test_run_ "$2"
                then
                        test_ok_ "$1"
                else
@@ -774,6 +805,9 @@ test_cmp() {
 #
 # except that the greeting and config --unset must both succeed for
 # the test to pass.
+#
+# Note that under --immediate mode, no clean-up is done to help diagnose
+# what went wrong.
 
 test_when_finished () {
        test_cleanup="{ $*
@@ -916,6 +950,8 @@ then
        do
                make_valgrind_symlink $file
        done
+       # special-case the mergetools loadables
+       make_symlink "$GIT_BUILD_DIR"/mergetools "$GIT_VALGRIND/bin/mergetools"
        OLDIFS=$IFS
        IFS=:
        for path in $PATH
diff --git a/t/test4012.png b/t/test4012.png
deleted file mode 100644 (file)
index 7b181d1..0000000
Binary files a/t/test4012.png and /dev/null differ
diff --git a/t/test9200a.png b/t/test9200a.png
deleted file mode 100644 (file)
index 7b181d1..0000000
Binary files a/t/test9200a.png and /dev/null differ
diff --git a/t/test9200b.png b/t/test9200b.png
deleted file mode 100644 (file)
index ac22ccb..0000000
Binary files a/t/test9200b.png and /dev/null differ
diff --git a/templates/hooks--post-commit.sample b/templates/hooks--post-commit.sample
deleted file mode 100755 (executable)
index 2266821..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/sh
-#
-# An example hook script that is called after a successful
-# commit is made.
-#
-# To enable this hook, rename this file to "post-commit".
-
-: Nothing
diff --git a/templates/hooks--post-receive.sample b/templates/hooks--post-receive.sample
deleted file mode 100755 (executable)
index 7a83e17..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/sh
-#
-# An example hook script for the "post-receive" event.
-#
-# The "post-receive" script is run after receive-pack has accepted a pack
-# and the repository has been updated.  It is passed arguments in through
-# stdin in the form
-#  <oldrev> <newrev> <refname>
-# For example:
-#  aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
-#
-# see contrib/hooks/ for a sample, or uncomment the next line and
-# rename the file to "post-receive".
-
-#. /usr/share/doc/git-core/contrib/hooks/post-receive-email
index b187c4bb1f256e19c25f80dd64f3451ced77e123..18c48297652174ffae65b877dd131711a5746181 100755 (executable)
@@ -18,6 +18,9 @@ fi
 # If you want to allow non-ascii filenames set this variable to true.
 allownonascii=$(git config hooks.allownonascii)
 
+# Redirect output to stderr.
+exec 1>&2
+
 # Cross platform projects tend to avoid non-ascii filenames; prevent
 # them from being added to the repository. We exploit the fact that the
 # printable range starts at the space character and ends with tilde.
@@ -25,8 +28,8 @@ if [ "$allownonascii" != "true" ] &&
        # Note that the use of brackets around a tr range is ok here, (it's
        # even required, for portability to Solaris 10's /usr/bin/tr), since
        # the square bracket bytes happen to fall in the designated range.
-       test "$(git diff --cached --name-only --diff-filter=A -z $against |
-         LC_ALL=C tr -d '[ -~]\0')"
+       test $(git diff --cached --name-only --diff-filter=A -z $against |
+         LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
 then
        echo "Error: Attempt to add a non-ascii file name."
        echo
@@ -43,4 +46,5 @@ then
        exit 1
 fi
 
+# If there are whitespace errors, print the offending file names and fail.
 exec git diff-index --check --cached $against --
index 4e3710b9a8fc868642e1e0e8f4af37eeb1ce0ce4..91a5701657556e07b4f4daafa947346d3b197cd1 100644 (file)
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "parse-options.h"
+#include "string-list.h"
 
 static int boolean = 0;
 static int integer = 0;
@@ -9,6 +10,7 @@ static int verbose = 0, dry_run = 0, quiet = 0;
 static char *string = NULL;
 static char *file = NULL;
 static int ambiguous;
+static struct string_list list;
 
 static int length_callback(const struct option *opt, const char *arg, int unset)
 {
@@ -54,6 +56,7 @@ int main(int argc, const char **argv)
                OPT_STRING('o', NULL, &string, "str", "get another string"),
                OPT_SET_PTR(0, "default-string", &string,
                        "set string to default", (unsigned long)"default"),
+               OPT_STRING_LIST(0, "list", &list, "str", "add str to list"),
                OPT_GROUP("Magic arguments"),
                OPT_ARGUMENT("quux", "means --quux"),
                OPT_NUMBER_CALLBACK(&integer, "set integer to NUM",
@@ -85,6 +88,9 @@ int main(int argc, const char **argv)
        printf("dry run: %s\n", dry_run ? "yes" : "no");
        printf("file: %s\n", file ? file : "(not set)");
 
+       for (i = 0; i < list.nr; i++)
+               printf("list: %s\n", list.items[i].string);
+
        for (i = 0; i < argc; i++)
                printf("arg %02d: %s\n", i, argv[i]);
 
index e7671593df6bf014f3a9d8bd5173d1edcfd6545b..3bc20e91da561f4edd5c3eddb0988735759234e4 100644 (file)
@@ -20,12 +20,34 @@ int main(int argc, char **argv)
                return 0;
        }
 
+       if (argc >= 2 && !strcmp(argv[1], "absolute_path")) {
+               while (argc > 2) {
+                       puts(absolute_path(argv[2]));
+                       argc--;
+                       argv++;
+               }
+               return 0;
+       }
+
        if (argc == 4 && !strcmp(argv[1], "longest_ancestor_length")) {
                int len = longest_ancestor_length(argv[2], argv[3]);
                printf("%d\n", len);
                return 0;
        }
 
+       if (argc >= 4 && !strcmp(argv[1], "prefix_path")) {
+               char *prefix = argv[2];
+               int prefix_len = strlen(prefix);
+               int nongit_ok;
+               setup_git_directory_gently(&nongit_ok);
+               while (argc > 3) {
+                       puts(prefix_path(prefix, prefix_len, argv[3]));
+                       argc--;
+                       argv++;
+               }
+               return 0;
+       }
+
        if (argc == 4 && !strcmp(argv[1], "strip_path_suffix")) {
                char *prefix = strip_path_suffix(argv[2], argv[3]);
                printf("%s\n", prefix ? prefix : "(null)");
index 660147f9e96be7b69379a9040c4220c141c7be4c..4eab844d4abfa29ce53b6245a5821ee7be03641b 100644 (file)
@@ -23,6 +23,8 @@ struct helper_data {
                push : 1,
                connect : 1,
                no_disconnect_req : 1;
+       char *export_marks;
+       char *import_marks;
        /* These go from remote name (as in "list") to private name */
        struct refspec *refspecs;
        int refspec_nr;
@@ -105,6 +107,12 @@ static struct child_process *get_helper(struct transport *transport)
        int refspec_alloc = 0;
        int duped;
        int code;
+       char git_dir_buf[sizeof(GIT_DIR_ENVIRONMENT) + PATH_MAX + 1];
+       const char *helper_env[] = {
+               git_dir_buf,
+               NULL
+       };
+
 
        if (data->helper)
                return data->helper;
@@ -120,6 +128,10 @@ static struct child_process *get_helper(struct transport *transport)
        helper->argv[2] = remove_ext_force(transport->url);
        helper->git_cmd = 0;
        helper->silent_exec_failure = 1;
+
+       snprintf(git_dir_buf, sizeof(git_dir_buf), "%s=%s", GIT_DIR_ENVIRONMENT, get_git_dir());
+       helper->env = helper_env;
+
        code = start_command(helper);
        if (code < 0 && errno == ENOENT)
                die("Unable to find remote helper for '%s'", data->name);
@@ -171,14 +183,19 @@ static struct child_process *get_helper(struct transport *transport)
                        ALLOC_GROW(refspecs,
                                   refspec_nr + 1,
                                   refspec_alloc);
-                       refspecs[refspec_nr++] = strdup(buf.buf + strlen("refspec "));
+                       refspecs[refspec_nr++] = strdup(capname + strlen("refspec "));
                } else if (!strcmp(capname, "connect")) {
                        data->connect = 1;
-               } else if (!strcmp(buf.buf, "gitdir")) {
-                       struct strbuf gitdir = STRBUF_INIT;
-                       strbuf_addf(&gitdir, "gitdir %s\n", get_git_dir());
-                       sendline(data, &gitdir);
-                       strbuf_release(&gitdir);
+               } else if (!prefixcmp(capname, "export-marks ")) {
+                       struct strbuf arg = STRBUF_INIT;
+                       strbuf_addstr(&arg, "--export-marks=");
+                       strbuf_addstr(&arg, capname + strlen("export-marks "));
+                       data->export_marks = strbuf_detach(&arg, NULL);
+               } else if (!prefixcmp(capname, "import-marks")) {
+                       struct strbuf arg = STRBUF_INIT;
+                       strbuf_addstr(&arg, "--import-marks=");
+                       strbuf_addstr(&arg, capname + strlen("import-marks "));
+                       data->import_marks = strbuf_detach(&arg, NULL);
                } else if (mandatory) {
                        die("Unknown mandatory capability %s. This remote "
                            "helper probably needs newer version of Git.\n",
@@ -204,6 +221,7 @@ static int disconnect_helper(struct transport *transport)
 {
        struct helper_data *data = transport->data;
        struct strbuf buf = STRBUF_INIT;
+       int res = 0;
 
        if (data->helper) {
                if (debug)
@@ -215,13 +233,13 @@ static int disconnect_helper(struct transport *transport)
                close(data->helper->in);
                close(data->helper->out);
                fclose(data->out);
-               finish_command(data->helper);
+               res = finish_command(data->helper);
                free((char *)data->helper->argv[0]);
                free(data->helper->argv);
                free(data->helper);
                data->helper = NULL;
        }
-       return 0;
+       return res;
 }
 
 static const char *unsupported_options[] = {
@@ -299,12 +317,13 @@ static void standard_options(struct transport *t)
 
 static int release_helper(struct transport *transport)
 {
+       int res = 0;
        struct helper_data *data = transport->data;
        free_refspec(data->refspec_nr, data->refspecs);
        data->refspecs = NULL;
-       disconnect_helper(transport);
+       res = disconnect_helper(transport);
        free(transport->data);
-       return 0;
+       return res;
 }
 
 static int fetch_with_fetch(struct transport *transport,
@@ -362,10 +381,9 @@ static int get_importer(struct transport *transport, struct child_process *fasti
 
 static int get_exporter(struct transport *transport,
                        struct child_process *fastexport,
-                       const char *export_marks,
-                       const char *import_marks,
                        struct string_list *revlist_args)
 {
+       struct helper_data *data = transport->data;
        struct child_process *helper = get_helper(transport);
        int argc = 0, i;
        memset(fastexport, 0, sizeof(*fastexport));
@@ -373,12 +391,13 @@ static int get_exporter(struct transport *transport,
        /* we need to duplicate helper->in because we want to use it after
         * fastexport is done with it. */
        fastexport->out = dup(helper->in);
-       fastexport->argv = xcalloc(4 + revlist_args->nr, sizeof(*fastexport->argv));
+       fastexport->argv = xcalloc(5 + revlist_args->nr, sizeof(*fastexport->argv));
        fastexport->argv[argc++] = "fast-export";
-       if (export_marks)
-               fastexport->argv[argc++] = export_marks;
-       if (import_marks)
-               fastexport->argv[argc++] = import_marks;
+       fastexport->argv[argc++] = "--use-done-feature";
+       if (data->export_marks)
+               fastexport->argv[argc++] = data->export_marks;
+       if (data->import_marks)
+               fastexport->argv[argc++] = data->import_marks;
 
        for (i = 0; i < revlist_args->nr; i++)
                fastexport->argv[argc++] = revlist_args->items[i].string;
@@ -410,8 +429,11 @@ static int fetch_with_import(struct transport *transport,
                sendline(data, &buf);
                strbuf_reset(&buf);
        }
-       disconnect_helper(transport);
-       finish_command(&fastimport);
+
+       write_constant(data->helper->in, "\n");
+
+       if (finish_command(&fastimport))
+               die("Error while running fast-import");
        free(fastimport.argv);
        fastimport.argv = NULL;
 
@@ -554,6 +576,88 @@ static int fetch(struct transport *transport,
        return -1;
 }
 
+static void push_update_ref_status(struct strbuf *buf,
+                                  struct ref **ref,
+                                  struct ref *remote_refs)
+{
+       char *refname, *msg;
+       int status;
+
+       if (!prefixcmp(buf->buf, "ok ")) {
+               status = REF_STATUS_OK;
+               refname = buf->buf + 3;
+       } else if (!prefixcmp(buf->buf, "error ")) {
+               status = REF_STATUS_REMOTE_REJECT;
+               refname = buf->buf + 6;
+       } else
+               die("expected ok/error, helper said '%s'\n", buf->buf);
+
+       msg = strchr(refname, ' ');
+       if (msg) {
+               struct strbuf msg_buf = STRBUF_INIT;
+               const char *end;
+
+               *msg++ = '\0';
+               if (!unquote_c_style(&msg_buf, msg, &end))
+                       msg = strbuf_detach(&msg_buf, NULL);
+               else
+                       msg = xstrdup(msg);
+               strbuf_release(&msg_buf);
+
+               if (!strcmp(msg, "no match")) {
+                       status = REF_STATUS_NONE;
+                       free(msg);
+                       msg = NULL;
+               }
+               else if (!strcmp(msg, "up to date")) {
+                       status = REF_STATUS_UPTODATE;
+                       free(msg);
+                       msg = NULL;
+               }
+               else if (!strcmp(msg, "non-fast forward")) {
+                       status = REF_STATUS_REJECT_NONFASTFORWARD;
+                       free(msg);
+                       msg = NULL;
+               }
+       }
+
+       if (*ref)
+               *ref = find_ref_by_name(*ref, refname);
+       if (!*ref)
+               *ref = find_ref_by_name(remote_refs, refname);
+       if (!*ref) {
+               warning("helper reported unexpected status of %s", refname);
+               return;
+       }
+
+       if ((*ref)->status != REF_STATUS_NONE) {
+               /*
+                * Earlier, the ref was marked not to be pushed, so ignore the ref
+                * status reported by the remote helper if the latter is 'no match'.
+                */
+               if (status == REF_STATUS_NONE)
+                       return;
+       }
+
+       (*ref)->status = status;
+       (*ref)->remote_status = msg;
+}
+
+static void push_update_refs_status(struct helper_data *data,
+                                   struct ref *remote_refs)
+{
+       struct strbuf buf = STRBUF_INIT;
+       struct ref *ref = remote_refs;
+       for (;;) {
+               recvline(data, &buf);
+               if (!buf.len)
+                       break;
+
+               push_update_ref_status(&buf, &ref, remote_refs);
+       }
+       strbuf_release(&buf);
+}
+
 static int push_refs_with_push(struct transport *transport,
                struct ref *remote_refs, int flags)
 {
@@ -608,76 +712,9 @@ static int push_refs_with_push(struct transport *transport,
 
        strbuf_addch(&buf, '\n');
        sendline(data, &buf);
-
-       ref = remote_refs;
-       while (1) {
-               char *refname, *msg;
-               int status;
-
-               recvline(data, &buf);
-               if (!buf.len)
-                       break;
-
-               if (!prefixcmp(buf.buf, "ok ")) {
-                       status = REF_STATUS_OK;
-                       refname = buf.buf + 3;
-               } else if (!prefixcmp(buf.buf, "error ")) {
-                       status = REF_STATUS_REMOTE_REJECT;
-                       refname = buf.buf + 6;
-               } else
-                       die("expected ok/error, helper said '%s'\n", buf.buf);
-
-               msg = strchr(refname, ' ');
-               if (msg) {
-                       struct strbuf msg_buf = STRBUF_INIT;
-                       const char *end;
-
-                       *msg++ = '\0';
-                       if (!unquote_c_style(&msg_buf, msg, &end))
-                               msg = strbuf_detach(&msg_buf, NULL);
-                       else
-                               msg = xstrdup(msg);
-                       strbuf_release(&msg_buf);
-
-                       if (!strcmp(msg, "no match")) {
-                               status = REF_STATUS_NONE;
-                               free(msg);
-                               msg = NULL;
-                       }
-                       else if (!strcmp(msg, "up to date")) {
-                               status = REF_STATUS_UPTODATE;
-                               free(msg);
-                               msg = NULL;
-                       }
-                       else if (!strcmp(msg, "non-fast forward")) {
-                               status = REF_STATUS_REJECT_NONFASTFORWARD;
-                               free(msg);
-                               msg = NULL;
-                       }
-               }
-
-               if (ref)
-                       ref = find_ref_by_name(ref, refname);
-               if (!ref)
-                       ref = find_ref_by_name(remote_refs, refname);
-               if (!ref) {
-                       warning("helper reported unexpected status of %s", refname);
-                       continue;
-               }
-
-               if (ref->status != REF_STATUS_NONE) {
-                       /*
-                        * Earlier, the ref was marked not to be pushed, so ignore the ref
-                        * status reported by the remote helper if the latter is 'no match'.
-                        */
-                       if (status == REF_STATUS_NONE)
-                               continue;
-               }
-
-               ref->status = status;
-               ref->remote_status = msg;
-       }
        strbuf_release(&buf);
+
+       push_update_refs_status(data, remote_refs);
        return 0;
 }
 
@@ -687,7 +724,6 @@ static int push_refs_with_export(struct transport *transport,
        struct ref *ref;
        struct child_process *helper, exporter;
        struct helper_data *data = transport->data;
-       char *export_marks = NULL, *import_marks = NULL;
        struct string_list revlist_args = STRING_LIST_INIT_NODUP;
        struct strbuf buf = STRBUF_INIT;
 
@@ -695,26 +731,6 @@ static int push_refs_with_export(struct transport *transport,
 
        write_constant(helper->in, "export\n");
 
-       recvline(data, &buf);
-       if (debug)
-               fprintf(stderr, "Debug: Got export_marks '%s'\n", buf.buf);
-       if (buf.len) {
-               struct strbuf arg = STRBUF_INIT;
-               strbuf_addstr(&arg, "--export-marks=");
-               strbuf_addbuf(&arg, &buf);
-               export_marks = strbuf_detach(&arg, NULL);
-       }
-
-       recvline(data, &buf);
-       if (debug)
-               fprintf(stderr, "Debug: Got import_marks '%s'\n", buf.buf);
-       if (buf.len) {
-               struct strbuf arg = STRBUF_INIT;
-               strbuf_addstr(&arg, "--import-marks=");
-               strbuf_addbuf(&arg, &buf);
-               import_marks = strbuf_detach(&arg, NULL);
-       }
-
        strbuf_reset(&buf);
 
        for (ref = remote_refs; ref; ref = ref->next) {
@@ -728,18 +744,23 @@ static int push_refs_with_export(struct transport *transport,
                        strbuf_addf(&buf, "^%s", private);
                        string_list_append(&revlist_args, strbuf_detach(&buf, NULL));
                }
+               free(private);
+
+               if (ref->deletion) {
+                       die("remote-helpers do not support ref deletion");
+               }
 
-               string_list_append(&revlist_args, ref->name);
+               if (ref->peer_ref)
+                       string_list_append(&revlist_args, ref->peer_ref->name);
 
        }
 
-       if (get_exporter(transport, &exporter,
-                        export_marks, import_marks, &revlist_args))
+       if (get_exporter(transport, &exporter, &revlist_args))
                die("Couldn't run fast-export");
 
-       data->no_disconnect_req = 1;
-       finish_command(&exporter);
-       disconnect_helper(transport);
+       if (finish_command(&exporter))
+               die("Error while running fast-export");
+       push_update_refs_status(data, remote_refs);
        return 0;
 }
 
index c9c8056f9de69bd378cd271d70363b5560f13e07..e1940615e28ebc3b16f80b8f647f216031f64ee8 100644 (file)
@@ -10,6 +10,7 @@
 #include "refs.h"
 #include "branch.h"
 #include "url.h"
+#include "submodule.h"
 
 /* rsync support */
 
@@ -431,7 +432,8 @@ static int fetch_refs_from_bundle(struct transport *transport,
                               int nr_heads, struct ref **to_fetch)
 {
        struct bundle_transport_data *data = transport->data;
-       return unbundle(&data->header, data->fd);
+       return unbundle(&data->header, data->fd,
+                       transport->progress ? BUNDLE_VERBOSE : 0);
 }
 
 static int close_bundle(struct transport *transport)
@@ -1041,6 +1043,14 @@ int transport_push(struct transport *transport,
                        flags & TRANSPORT_PUSH_MIRROR,
                        flags & TRANSPORT_PUSH_FORCE);
 
+               if ((flags & TRANSPORT_RECURSE_SUBMODULES_CHECK) && !is_bare_repository()) {
+                       struct ref *ref = remote_refs;
+                       for (; ref; ref = ref->next)
+                               if (!is_null_sha1(ref->new_sha1) &&
+                                   check_submodule_needs_pushing(ref->new_sha1,transport->remote->name))
+                                       die("There are unpushed submodules, aborting.");
+               }
+
                push_ret = transport->push_refs(transport, remote_refs, flags);
                err = push_had_errors(remote_refs);
                ret = push_ret | err;
index 161d724bba5c132950b45866286c558148560d01..059b3303e20f8335cea388dfccfc2740d3c3d43e 100644 (file)
@@ -101,6 +101,7 @@ struct transport {
 #define TRANSPORT_PUSH_MIRROR 8
 #define TRANSPORT_PUSH_PORCELAIN 16
 #define TRANSPORT_PUSH_SET_UPSTREAM 32
+#define TRANSPORT_RECURSE_SUBMODULES_CHECK 64
 
 #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
 
index 33f749e1e77484694e7b8f40a65755a7818c4abb..808bb55ba3f21d113e6358f6874e2e20cb33bbea 100644 (file)
@@ -309,6 +309,18 @@ static void free_extended_entry(struct tree_desc_x *t)
        }
 }
 
+static inline int prune_traversal(struct name_entry *e,
+                                 struct traverse_info *info,
+                                 struct strbuf *base,
+                                 int still_interesting)
+{
+       if (!info->pathspec || still_interesting == 2)
+               return 2;
+       if (still_interesting < 0)
+               return still_interesting;
+       return tree_entry_interesting(e, base, 0, info->pathspec);
+}
+
 int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
 {
        int ret = 0;
@@ -316,10 +328,18 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
        struct name_entry *entry = xmalloc(n*sizeof(*entry));
        int i;
        struct tree_desc_x *tx = xcalloc(n, sizeof(*tx));
+       struct strbuf base = STRBUF_INIT;
+       int interesting = 1;
 
        for (i = 0; i < n; i++)
                tx[i].d = t[i];
 
+       if (info->prev) {
+               strbuf_grow(&base, info->pathlen);
+               make_traverse_path(base.buf, info->prev, &info->name);
+               base.buf[info->pathlen-1] = '/';
+               strbuf_setlen(&base, info->pathlen);
+       }
        for (;;) {
                unsigned long mask, dirmask;
                const char *first = NULL;
@@ -376,16 +396,22 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
                        mask |= 1ul << i;
                        if (S_ISDIR(entry[i].mode))
                                dirmask |= 1ul << i;
+                       e = &entry[i];
                }
                if (!mask)
                        break;
-               ret = info->fn(n, mask, dirmask, entry, info);
-               if (ret < 0) {
-                       error = ret;
-                       if (!info->show_all_errors)
-                               break;
+               interesting = prune_traversal(e, info, &base, interesting);
+               if (interesting < 0)
+                       break;
+               if (interesting) {
+                       ret = info->fn(n, mask, dirmask, entry, info);
+                       if (ret < 0) {
+                               error = ret;
+                               if (!info->show_all_errors)
+                                       break;
+                       }
+                       mask &= ret;
                }
-               mask &= ret;
                ret = 0;
                for (i = 0; i < n; i++)
                        if (mask & (1ul << i))
@@ -395,6 +421,7 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
        for (i = 0; i < n; i++)
                free_extended_entry(tx + i);
        free(tx);
+       strbuf_release(&base);
        return error;
 }
 
index 39524b7dba6a1d0b63c4cd2b42db59a27a030b21..0089581e1dd55800302799a7381d4a7ad01bd79d 100644 (file)
@@ -44,6 +44,7 @@ struct traverse_info {
        struct traverse_info *prev;
        struct name_entry name;
        int pathlen;
+       struct pathspec *pathspec;
 
        unsigned long conflicts;
        traverse_callback_t fn;
index 07f83642443601d107e0a2425407b3250c022dcd..237aed8c766638c55d8d6b870c86ef2ad13a74d3 100644 (file)
@@ -159,7 +159,7 @@ static void display_error_msgs(struct unpack_trees_options *o)
                string_list_clear(rejects, 0);
        }
        if (something_displayed)
-               printf("Aborting\n");
+               fprintf(stderr, "Aborting\n");
 }
 
 /*
@@ -444,6 +444,7 @@ static int traverse_trees_recursive(int n, unsigned long dirmask,
 
        newinfo = *info;
        newinfo.prev = info;
+       newinfo.pathspec = info->pathspec;
        newinfo.name = *p;
        newinfo.pathlen += tree_entry_len(p->path, p->sha1) + 1;
        newinfo.conflicts |= df_conflicts;
@@ -593,7 +594,7 @@ static int unpack_nondirectories(int n, unsigned long mask,
 static int unpack_failed(struct unpack_trees_options *o, const char *message)
 {
        discard_index(&o->result);
-       if (!o->gently) {
+       if (!o->gently && !o->exiting_early) {
                if (message)
                        return error("%s", message);
                return -1;
@@ -1040,6 +1041,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
                info.fn = unpack_callback;
                info.data = o;
                info.show_all_errors = o->show_all_errors;
+               info.pathspec = o->pathspec;
 
                if (o->prefix) {
                        /*
@@ -1133,6 +1135,8 @@ return_failed:
                display_error_msgs(o);
        mark_all_ce_unused(o->src_index);
        ret = unpack_failed(o, NULL);
+       if (o->exiting_early)
+               ret = 0;
        goto done;
 }
 
@@ -1166,11 +1170,22 @@ static int verify_uptodate_1(struct cache_entry *ce,
 {
        struct stat st;
 
-       if (o->index_only || (!((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) && (o->reset || ce_uptodate(ce))))
+       if (o->index_only)
+               return 0;
+
+       /*
+        * CE_VALID and CE_SKIP_WORKTREE cheat, we better check again
+        * if this entry is truly up-to-date because this file may be
+        * overwritten.
+        */
+       if ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce))
+               ; /* keep checking */
+       else if (o->reset || ce_uptodate(ce))
                return 0;
 
        if (!lstat(ce->name, &st)) {
-               unsigned changed = ie_match_stat(o->src_index, ce, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
+               int flags = CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE;
+               unsigned changed = ie_match_stat(o->src_index, ce, &st, flags);
                if (!changed)
                        return 0;
                /*
index 64f02cb03ab242ac08ea0f1afbb24e71cf6664aa..5e432f576eb2304a63510a61a71182e11f777092 100644 (file)
@@ -46,11 +46,13 @@ struct unpack_trees_options {
                     debug_unpack,
                     skip_sparse_checkout,
                     gently,
+                    exiting_early,
                     show_all_errors,
                     dry_run;
        const char *prefix;
        int cache_bottom;
        struct dir_struct *dir;
+       struct pathspec *pathspec;
        merge_fn_t fn;
        const char *msgs[NB_UNPACK_TREES_ERROR_TYPES];
        /*
index ce5cbbea6be45a6dfa79bcc8c8244ae0b99c7ca3..8739bfacdfd4e01e16a614dd419388fa93a904b6 100644 (file)
@@ -10,6 +10,7 @@
 #include "revision.h"
 #include "list-objects.h"
 #include "run-command.h"
+#include "sigchain.h"
 
 static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=<n>] <dir>";
 
@@ -498,11 +499,97 @@ static int get_common_commits(void)
        }
 }
 
+static void check_non_tip(void)
+{
+       static const char *argv[] = {
+               "rev-list", "--stdin", NULL,
+       };
+       static struct child_process cmd;
+       struct object *o;
+       char namebuf[42]; /* ^ + SHA-1 + LF */
+       int i;
+
+       /* In the normal in-process case non-tip request can never happen */
+       if (!stateless_rpc)
+               goto error;
+
+       cmd.argv = argv;
+       cmd.git_cmd = 1;
+       cmd.no_stderr = 1;
+       cmd.in = -1;
+       cmd.out = -1;
+
+       if (start_command(&cmd))
+               goto error;
+
+       /*
+        * If rev-list --stdin encounters an unknown commit, it
+        * terminates, which will cause SIGPIPE in the write loop
+        * below.
+        */
+       sigchain_push(SIGPIPE, SIG_IGN);
+
+       namebuf[0] = '^';
+       namebuf[41] = '\n';
+       for (i = get_max_object_index(); 0 < i; ) {
+               o = get_indexed_object(--i);
+               if (!o)
+                       continue;
+               if (!(o->flags & OUR_REF))
+                       continue;
+               memcpy(namebuf + 1, sha1_to_hex(o->sha1), 40);
+               if (write_in_full(cmd.in, namebuf, 42) < 0)
+                       goto error;
+       }
+       namebuf[40] = '\n';
+       for (i = 0; i < want_obj.nr; i++) {
+               o = want_obj.objects[i].item;
+               if (o->flags & OUR_REF)
+                       continue;
+               memcpy(namebuf, sha1_to_hex(o->sha1), 40);
+               if (write_in_full(cmd.in, namebuf, 41) < 0)
+                       goto error;
+       }
+       close(cmd.in);
+
+       sigchain_pop(SIGPIPE);
+
+       /*
+        * The commits out of the rev-list are not ancestors of
+        * our ref.
+        */
+       i = read_in_full(cmd.out, namebuf, 1);
+       if (i)
+               goto error;
+       close(cmd.out);
+
+       /*
+        * rev-list may have died by encountering a bad commit
+        * in the history, in which case we do want to bail out
+        * even when it showed no commit.
+        */
+       if (finish_command(&cmd))
+               goto error;
+
+       /* All the non-tip ones are ancestors of what we advertised */
+       return;
+
+error:
+       /* Pick one of them (we know there at least is one) */
+       for (i = 0; i < want_obj.nr; i++) {
+               o = want_obj.objects[i].item;
+               if (!(o->flags & OUR_REF))
+                       die("git upload-pack: not our ref %s",
+                           sha1_to_hex(o->sha1));
+       }
+}
+
 static void receive_needs(void)
 {
        struct object_array shallows = OBJECT_ARRAY_INIT;
        static char line[1000];
        int len, depth = 0;
+       int has_non_tip = 0;
 
        shallow_nr = 0;
        if (debug_fd)
@@ -559,26 +646,30 @@ static void receive_needs(void)
                if (strstr(line+45, "include-tag"))
                        use_include_tag = 1;
 
-               /* We have sent all our refs already, and the other end
-                * should have chosen out of them; otherwise they are
-                * asking for nonsense.
-                *
-                * Hmph.  We may later want to allow "want" line that
-                * asks for something like "master~10" (symbolic)...
-                * would it make sense?  I don't know.
-                */
                o = lookup_object(sha1_buf);
-               if (!o || !(o->flags & OUR_REF))
+               if (!o)
                        die("git upload-pack: not our ref %s",
                            sha1_to_hex(sha1_buf));
                if (!(o->flags & WANTED)) {
                        o->flags |= WANTED;
+                       if (!(o->flags & OUR_REF))
+                               has_non_tip = 1;
                        add_object_array(o, NULL, &want_obj);
                }
        }
        if (debug_fd)
                write_str_in_full(debug_fd, "#E\n");
 
+       /*
+        * We have sent all our refs already, and the other end
+        * should have chosen out of them. When we are operating
+        * in the stateless RPC mode, however, their choice may
+        * have been based on the set of older refs advertised
+        * by another process that handled the initial request.
+        */
+       if (has_non_tip)
+               check_non_tip();
+
        if (!use_sideband && daemon_mode)
                no_progress = 1;
 
@@ -641,16 +732,17 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo
                " side-band-64k ofs-delta shallow no-progress"
                " include-tag multi_ack_detailed";
        struct object *o = parse_object(sha1);
+       const char *refname_nons = strip_namespace(refname);
 
        if (!o)
                die("git upload-pack: cannot find object %s:", sha1_to_hex(sha1));
 
        if (capabilities)
-               packet_write(1, "%s %s%c%s%s\n", sha1_to_hex(sha1), refname,
+               packet_write(1, "%s %s%c%s%s\n", sha1_to_hex(sha1), refname_nons,
                             0, capabilities,
                             stateless_rpc ? " no-done" : "");
        else
-               packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname);
+               packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname_nons);
        capabilities = NULL;
        if (!(o->flags & OUR_REF)) {
                o->flags |= OUR_REF;
@@ -659,7 +751,7 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo
        if (o->type == OBJ_TAG) {
                o = deref_tag(o, refname, 0);
                if (o)
-                       packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname);
+                       packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname_nons);
        }
        return 0;
 }
@@ -680,12 +772,12 @@ static void upload_pack(void)
 {
        if (advertise_refs || !stateless_rpc) {
                reset_timeout();
-               head_ref(send_ref, NULL);
-               for_each_ref(send_ref, NULL);
+               head_ref_namespaced(send_ref, NULL);
+               for_each_namespaced_ref(send_ref, NULL);
                packet_flush(1);
        } else {
-               head_ref(mark_our_ref, NULL);
-               for_each_ref(mark_our_ref, NULL);
+               head_ref_namespaced(mark_our_ref, NULL);
+               for_each_namespaced_ref(mark_our_ref, NULL);
        }
        if (advertise_refs)
                return;
diff --git a/usage.c b/usage.c
index b5e67e3d0d4a46a69d72371f4c6a0002e40a0bf1..a2a667800474e315a362e1d0623c17dafaad1022 100644 (file)
--- a/usage.c
+++ b/usage.c
@@ -4,6 +4,7 @@
  * Copyright (C) Linus Torvalds, 2005
  */
 #include "git-compat-util.h"
+#include "cache.h"
 
 void vreportf(const char *prefix, const char *err, va_list params)
 {
@@ -12,6 +13,18 @@ void vreportf(const char *prefix, const char *err, va_list params)
        fprintf(stderr, "%s%s\n", prefix, msg);
 }
 
+void vwritef(int fd, const char *prefix, const char *err, va_list params)
+{
+       char msg[4096];
+       int len = vsnprintf(msg, sizeof(msg), err, params);
+       if (len > sizeof(msg))
+               len = sizeof(msg);
+
+       write_in_full(fd, prefix, strlen(prefix));
+       write_in_full(fd, msg, len);
+       write_in_full(fd, "\n", 1);
+}
+
 static NORETURN void usage_builtin(const char *err, va_list params)
 {
        vreportf("usage: ", err, params);
@@ -46,6 +59,11 @@ void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list param
        die_routine = routine;
 }
 
+void set_error_routine(void (*routine)(const char *err, va_list params))
+{
+       error_routine = routine;
+}
+
 void NORETURN usagef(const char *err, ...)
 {
        va_list params;
index e55310cd02c8e7d1f0404b2fee9539e7340993d9..bf553ad91b55de8a762d56a6ffc6c86e959e878c 100644 (file)
@@ -270,7 +270,7 @@ struct userdiff_driver *userdiff_find_by_path(const char *path)
 
        if (!path)
                return NULL;
-       if (git_checkattr(path, 1, &check))
+       if (git_check_attr(path, 1, &check))
                return NULL;
 
        if (ATTR_TRUE(check.value))
@@ -281,3 +281,20 @@ struct userdiff_driver *userdiff_find_by_path(const char *path)
                return NULL;
        return userdiff_find_by_name(check.value);
 }
+
+struct userdiff_driver *userdiff_get_textconv(struct userdiff_driver *driver)
+{
+       if (!driver->textconv)
+               return NULL;
+
+       if (driver->textconv_want_cache && !driver->textconv_cache) {
+               struct notes_cache *c = xmalloc(sizeof(*c));
+               struct strbuf name = STRBUF_INIT;
+
+               strbuf_addf(&name, "textconv/%s", driver->name);
+               notes_cache_init(c, name.buf, driver->textconv);
+               driver->textconv_cache = c;
+       }
+
+       return driver;
+}
index 942d5949501027fb5d30e0d59629aab38fd2669a..4a7e78ffbcc6d552a39dcccd9008d6c11919e432 100644 (file)
@@ -23,4 +23,6 @@ int userdiff_config(const char *k, const char *v);
 struct userdiff_driver *userdiff_find_by_name(const char *name);
 struct userdiff_driver *userdiff_find_by_path(const char *path);
 
+struct userdiff_driver *userdiff_get_textconv(struct userdiff_driver *driver);
+
 #endif /* USERDIFF */
diff --git a/ws.c b/ws.c
index 9fb9b14760b100972a1adb5198bff9d6a9941808..b498d7599d5ac09c2688ee829b29aa36a866ac9c 100644 (file)
--- a/ws.c
+++ b/ws.c
@@ -88,7 +88,7 @@ unsigned whitespace_rule(const char *pathname)
        struct git_attr_check attr_whitespace_rule;
 
        setup_whitespace_attr_check(&attr_whitespace_rule);
-       if (!git_checkattr(pathname, 1, &attr_whitespace_rule)) {
+       if (!git_check_attr(pathname, 1, &attr_whitespace_rule)) {
                const char *value;
 
                value = attr_whitespace_rule.value;
index 9f4e0ba9c17120ca2903b30b95b6d8fbcc62cd9c..70fdb76ff2b5100c6d20f9f3b758e1ac8df314e1 100644 (file)
@@ -26,7 +26,9 @@ static char default_wt_status_colors[][COLOR_MAXLEN] = {
 
 static const char *color(int slot, struct wt_status *s)
 {
-       const char *c = s->use_color > 0 ? s->color_palette[slot] : "";
+       const char *c = "";
+       if (want_color(s->use_color))
+               c = s->color_palette[slot];
        if (slot == WT_STATUS_ONBRANCH && color_is_nil(c))
                c = s->color_palette[WT_STATUS_HEADER];
        return c;
@@ -394,7 +396,7 @@ static void wt_status_collect_changes_worktree(struct wt_status *s)
        if (s->ignore_submodule_arg) {
                DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
                handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
-    }
+       }
        rev.diffopt.format_callback = wt_status_collect_changed_cb;
        rev.diffopt.format_callback_data = s;
        init_pathspec(&rev.prune_data, s->pathspec);
@@ -642,7 +644,7 @@ static void wt_status_print_other(struct wt_status *s,
        int i;
        struct strbuf buf = STRBUF_INIT;
 
-       if (!s->untracked.nr)
+       if (!l->nr)
                return;
 
        wt_status_print_other_header(s, what, how);
@@ -681,7 +683,7 @@ static void wt_status_print_verbose(struct wt_status *s)
         * will have checked isatty on stdout).
         */
        if (s->fp != stdout)
-               DIFF_OPT_CLR(&rev.diffopt, COLOR_DIFF);
+               rev.diffopt.use_color = 0;
        run_diff_index(&rev, 1);
 }
 
index 711048ea36dda1d814b395efa8bc69ab142425ca..4beb10c678702a34b14914f8b292dd65b5c273cd 100644 (file)
@@ -33,6 +33,7 @@ extern "C" {
 #define XDF_IGNORE_WHITESPACE_CHANGE (1 << 3)
 #define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 4)
 #define XDF_PATIENCE_DIFF (1 << 5)
+#define XDF_HISTOGRAM_DIFF (1 << 6)
 #define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE | XDF_IGNORE_WHITESPACE_AT_EOL)
 
 #define XDL_PATCH_NORMAL '-'
@@ -105,7 +106,6 @@ typedef struct s_bdiffparam {
 #define xdl_realloc(ptr,x) realloc(ptr,x)
 
 void *xdl_mmfile_first(mmfile_t *mmf, long *size);
-void *xdl_mmfile_next(mmfile_t *mmf, long *size);
 long xdl_mmfile_size(mmfile_t *mmf);
 
 int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
index da67c04357dfe4d3283c589f5d47be9c5f2b7fcf..75a39227501715504cdd12ccc1b4854568a54ad7 100644 (file)
@@ -331,6 +331,9 @@ int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
        if (xpp->flags & XDF_PATIENCE_DIFF)
                return xdl_do_patience_diff(mf1, mf2, xpp, xe);
 
+       if (xpp->flags & XDF_HISTOGRAM_DIFF)
+               return xdl_do_histogram_diff(mf1, mf2, xpp, xe);
+
        if (xdl_prepare_env(mf1, mf2, xpp, xe) < 0) {
 
                return -1;
index ad033a8e6a79600b6a3ba0cc16244ede0e9437ea..7a92ea9c4d84a559ae1d0bd90ebe667828d8f9cb 100644 (file)
@@ -57,5 +57,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                  xdemitconf_t const *xecfg);
 int xdl_do_patience_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
                xdfenv_t *env);
+int xdl_do_histogram_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+               xdfenv_t *env);
 
 #endif /* #if !defined(XDIFFI_H) */
diff --git a/xdiff/xhistogram.c b/xdiff/xhistogram.c
new file mode 100644 (file)
index 0000000..18f6f99
--- /dev/null
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in JGit's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "xinclude.h"
+#include "xtypes.h"
+#include "xdiff.h"
+
+#define MAX_PTR        UINT_MAX
+#define MAX_CNT        UINT_MAX
+
+#define LINE_END(n) (line##n + count##n - 1)
+#define LINE_END_PTR(n) (*line##n + *count##n - 1)
+
+struct histindex {
+       struct record {
+               unsigned int ptr, cnt;
+               struct record *next;
+       } **records, /* an ocurrence */
+         **line_map; /* map of line to record chain */
+       chastore_t rcha;
+       unsigned int *next_ptrs;
+       unsigned int table_bits,
+                    records_size,
+                    line_map_size;
+
+       unsigned int max_chain_length,
+                    key_shift,
+                    ptr_shift;
+
+       unsigned int cnt,
+                    has_common;
+
+       xdfenv_t *env;
+       xpparam_t const *xpp;
+};
+
+struct region {
+       unsigned int begin1, end1;
+       unsigned int begin2, end2;
+};
+
+#define LINE_MAP(i, a) (i->line_map[(a) - i->ptr_shift])
+
+#define NEXT_PTR(index, ptr) \
+       (index->next_ptrs[(ptr) - index->ptr_shift])
+
+#define CNT(index, ptr) \
+       ((LINE_MAP(index, ptr))->cnt)
+
+#define REC(env, s, l) \
+       (env->xdf##s.recs[l - 1])
+
+static int cmp_recs(xpparam_t const *xpp,
+       xrecord_t *r1, xrecord_t *r2)
+{
+       return r1->ha == r2->ha &&
+               xdl_recmatch(r1->ptr, r1->size, r2->ptr, r2->size,
+                           xpp->flags);
+}
+
+#define CMP_ENV(xpp, env, s1, l1, s2, l2) \
+       (cmp_recs(xpp, REC(env, s1, l1), REC(env, s2, l2)))
+
+#define CMP(i, s1, l1, s2, l2) \
+       (cmp_recs(i->xpp, REC(i->env, s1, l1), REC(i->env, s2, l2)))
+
+#define TABLE_HASH(index, side, line) \
+       XDL_HASHLONG((REC(index->env, side, line))->ha, index->table_bits)
+
+static int scanA(struct histindex *index, int line1, int count1)
+{
+       unsigned int ptr, tbl_idx;
+       unsigned int chain_len;
+       struct record **rec_chain, *rec;
+
+       for (ptr = LINE_END(1); line1 <= ptr; ptr--) {
+               tbl_idx = TABLE_HASH(index, 1, ptr);
+               rec_chain = index->records + tbl_idx;
+               rec = *rec_chain;
+
+               chain_len = 0;
+               while (rec) {
+                       if (CMP(index, 1, rec->ptr, 1, ptr)) {
+                               /*
+                                * ptr is identical to another element. Insert
+                                * it onto the front of the existing element
+                                * chain.
+                                */
+                               NEXT_PTR(index, ptr) = rec->ptr;
+                               rec->ptr = ptr;
+                               /* cap rec->cnt at MAX_CNT */
+                               rec->cnt = XDL_MIN(MAX_CNT, rec->cnt + 1);
+                               LINE_MAP(index, ptr) = rec;
+                               goto continue_scan;
+                       }
+
+                       rec = rec->next;
+                       chain_len++;
+               }
+
+               if (chain_len == index->max_chain_length)
+                       return -1;
+
+               /*
+                * This is the first time we have ever seen this particular
+                * element in the sequence. Construct a new chain for it.
+                */
+               if (!(rec = xdl_cha_alloc(&index->rcha)))
+                       return -1;
+               rec->ptr = ptr;
+               rec->cnt = 1;
+               rec->next = *rec_chain;
+               *rec_chain = rec;
+               LINE_MAP(index, ptr) = rec;
+
+continue_scan:
+               ; /* no op */
+       }
+
+       return 0;
+}
+
+static int try_lcs(struct histindex *index, struct region *lcs, int b_ptr,
+       int line1, int count1, int line2, int count2)
+{
+       unsigned int b_next = b_ptr + 1;
+       struct record *rec = index->records[TABLE_HASH(index, 2, b_ptr)];
+       unsigned int as, ae, bs, be, np, rc;
+       int should_break;
+
+       for (; rec; rec = rec->next) {
+               if (rec->cnt > index->cnt) {
+                       if (!index->has_common)
+                               index->has_common = CMP(index, 1, rec->ptr, 2, b_ptr);
+                       continue;
+               }
+
+               as = rec->ptr;
+               if (!CMP(index, 1, as, 2, b_ptr))
+                       continue;
+
+               index->has_common = 1;
+               for (;;) {
+                       should_break = 0;
+                       np = NEXT_PTR(index, as);
+                       bs = b_ptr;
+                       ae = as;
+                       be = bs;
+                       rc = rec->cnt;
+
+                       while (line1 < as && line2 < bs
+                               && CMP(index, 1, as - 1, 2, bs - 1)) {
+                               as--;
+                               bs--;
+                               if (1 < rc)
+                                       rc = XDL_MIN(rc, CNT(index, as));
+                       }
+                       while (ae < LINE_END(1) && be < LINE_END(2)
+                               && CMP(index, 1, ae + 1, 2, be + 1)) {
+                               ae++;
+                               be++;
+                               if (1 < rc)
+                                       rc = XDL_MIN(rc, CNT(index, ae));
+                       }
+
+                       if (b_next <= be)
+                               b_next = be + 1;
+                       if (lcs->end1 - lcs->begin1 < ae - as || rc < index->cnt) {
+                               lcs->begin1 = as;
+                               lcs->begin2 = bs;
+                               lcs->end1 = ae;
+                               lcs->end2 = be;
+                               index->cnt = rc;
+                       }
+
+                       if (np == 0)
+                               break;
+
+                       while (np <= ae) {
+                               np = NEXT_PTR(index, np);
+                               if (np == 0) {
+                                       should_break = 1;
+                                       break;
+                               }
+                       }
+
+                       if (should_break)
+                               break;
+
+                       as = np;
+               }
+       }
+       return b_next;
+}
+
+static int find_lcs(struct histindex *index, struct region *lcs,
+       int line1, int count1, int line2, int count2) {
+       int b_ptr;
+
+       if (scanA(index, line1, count1))
+               return -1;
+
+       index->cnt = index->max_chain_length + 1;
+
+       for (b_ptr = line2; b_ptr <= LINE_END(2); )
+               b_ptr = try_lcs(index, lcs, b_ptr, line1, count1, line2, count2);
+
+       return index->has_common && index->max_chain_length < index->cnt;
+}
+
+static int fall_back_to_classic_diff(struct histindex *index,
+               int line1, int count1, int line2, int count2)
+{
+       xpparam_t xpp;
+       xpp.flags = index->xpp->flags & ~XDF_HISTOGRAM_DIFF;
+
+       return xdl_fall_back_diff(index->env, &xpp,
+                                 line1, count1, line2, count2);
+}
+
+static int histogram_diff(xpparam_t const *xpp, xdfenv_t *env,
+       int line1, int count1, int line2, int count2)
+{
+       struct histindex index;
+       struct region lcs;
+       int sz;
+       int result = -1;
+
+       if (count1 <= 0 && count2 <= 0)
+               return 0;
+
+       if (LINE_END(1) >= MAX_PTR)
+               return -1;
+
+       if (!count1) {
+               while(count2--)
+                       env->xdf2.rchg[line2++ - 1] = 1;
+               return 0;
+       } else if (!count2) {
+               while(count1--)
+                       env->xdf1.rchg[line1++ - 1] = 1;
+               return 0;
+       }
+
+       memset(&index, 0, sizeof(index));
+
+       index.env = env;
+       index.xpp = xpp;
+
+       index.records = NULL;
+       index.line_map = NULL;
+       /* in case of early xdl_cha_free() */
+       index.rcha.head = NULL;
+
+       index.table_bits = xdl_hashbits(count1);
+       sz = index.records_size = 1 << index.table_bits;
+       sz *= sizeof(struct record *);
+       if (!(index.records = (struct record **) xdl_malloc(sz)))
+               goto cleanup;
+       memset(index.records, 0, sz);
+
+       sz = index.line_map_size = count1;
+       sz *= sizeof(struct record *);
+       if (!(index.line_map = (struct record **) xdl_malloc(sz)))
+               goto cleanup;
+       memset(index.line_map, 0, sz);
+
+       sz = index.line_map_size;
+       sz *= sizeof(unsigned int);
+       if (!(index.next_ptrs = (unsigned int *) xdl_malloc(sz)))
+               goto cleanup;
+       memset(index.next_ptrs, 0, sz);
+
+       /* lines / 4 + 1 comes from xprepare.c:xdl_prepare_ctx() */
+       if (xdl_cha_init(&index.rcha, sizeof(struct record), count1 / 4 + 1) < 0)
+               goto cleanup;
+
+       index.ptr_shift = line1;
+       index.max_chain_length = 64;
+
+       memset(&lcs, 0, sizeof(lcs));
+       if (find_lcs(&index, &lcs, line1, count1, line2, count2))
+               result = fall_back_to_classic_diff(&index, line1, count1, line2, count2);
+       else {
+               if (lcs.begin1 == 0 && lcs.begin2 == 0) {
+                       while (count1--)
+                               env->xdf1.rchg[line1++ - 1] = 1;
+                       while (count2--)
+                               env->xdf2.rchg[line2++ - 1] = 1;
+                       result = 0;
+               } else {
+                       result = histogram_diff(xpp, env,
+                                               line1, lcs.begin1 - line1,
+                                               line2, lcs.begin2 - line2);
+                       if (result)
+                               goto cleanup;
+                       result = histogram_diff(xpp, env,
+                                               lcs.end1 + 1, LINE_END(1) - lcs.end1,
+                                               lcs.end2 + 1, LINE_END(2) - lcs.end2);
+                       if (result)
+                               goto cleanup;
+               }
+       }
+
+cleanup:
+       xdl_free(index.records);
+       xdl_free(index.line_map);
+       xdl_free(index.next_ptrs);
+       xdl_cha_free(&index.rcha);
+
+       return result;
+}
+
+int xdl_do_histogram_diff(mmfile_t *file1, mmfile_t *file2,
+       xpparam_t const *xpp, xdfenv_t *env)
+{
+       if (xdl_prepare_env(file1, file2, xpp, env) < 0)
+               return -1;
+
+       return histogram_diff(xpp, env,
+               env->xdf1.dstart + 1, env->xdf1.dend - env->xdf1.dstart + 1,
+               env->xdf2.dstart + 1, env->xdf2.dend - env->xdf2.dstart + 1);
+}
index e42c16a807609b967c6a807a99d4ecbe2e1e0af8..fdd7d0263f576a8dc1a8e791ef50f8dbe25c7ee5 100644 (file)
@@ -287,34 +287,11 @@ static int walk_common_sequence(struct hashmap *map, struct entry *first,
 static int fall_back_to_classic_diff(struct hashmap *map,
                int line1, int count1, int line2, int count2)
 {
-       /*
-        * This probably does not work outside Git, since
-        * we have a very simple mmfile structure.
-        *
-        * Note: ideally, we would reuse the prepared environment, but
-        * the libxdiff interface does not (yet) allow for diffing only
-        * ranges of lines instead of the whole files.
-        */
-       mmfile_t subfile1, subfile2;
        xpparam_t xpp;
-       xdfenv_t env;
-
-       subfile1.ptr = (char *)map->env->xdf1.recs[line1 - 1]->ptr;
-       subfile1.size = map->env->xdf1.recs[line1 + count1 - 2]->ptr +
-               map->env->xdf1.recs[line1 + count1 - 2]->size - subfile1.ptr;
-       subfile2.ptr = (char *)map->env->xdf2.recs[line2 - 1]->ptr;
-       subfile2.size = map->env->xdf2.recs[line2 + count2 - 2]->ptr +
-               map->env->xdf2.recs[line2 + count2 - 2]->size - subfile2.ptr;
        xpp.flags = map->xpp->flags & ~XDF_PATIENCE_DIFF;
-       if (xdl_do_diff(&subfile1, &subfile2, &xpp, &env) < 0)
-               return -1;
 
-       memcpy(map->env->xdf1.rchg + line1 - 1, env.xdf1.rchg, count1);
-       memcpy(map->env->xdf2.rchg + line2 - 1, env.xdf2.rchg, count2);
-
-       xdl_free_env(&env);
-
-       return 0;
+       return xdl_fall_back_diff(map->env, &xpp,
+                                 line1, count1, line2, count2);
 }
 
 /*
index 16890852350cb62bb9f9aec5e52eea8ba46f1192..e419f4f726019a5b0365c589285439fb3bfb8db2 100644 (file)
@@ -26,6 +26,8 @@
 #define XDL_KPDIS_RUN 4
 #define XDL_MAX_EQLIMIT 1024
 #define XDL_SIMSCAN_WINDOW 100
+#define XDL_GUESS_NLINES1 256
+#define XDL_GUESS_NLINES2 20
 
 
 typedef struct s_xdlclass {
@@ -34,6 +36,7 @@ typedef struct s_xdlclass {
        char const *line;
        long size;
        long idx;
+       long len1, len2;
 } xdlclass_t;
 
 typedef struct s_xdlclassifier {
@@ -41,6 +44,8 @@ typedef struct s_xdlclassifier {
        long hsize;
        xdlclass_t **rchash;
        chastore_t ncha;
+       xdlclass_t **rcrecs;
+       long alloc;
        long count;
        long flags;
 } xdlclassifier_t;
@@ -50,22 +55,20 @@ typedef struct s_xdlclassifier {
 
 static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags);
 static void xdl_free_classifier(xdlclassifier_t *cf);
-static int xdl_classify_record(xdlclassifier_t *cf, xrecord_t **rhash, unsigned int hbits,
-                              xrecord_t *rec);
-static int xdl_prepare_ctx(mmfile_t *mf, long narec, xpparam_t const *xpp,
+static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,
+                              unsigned int hbits, xrecord_t *rec);
+static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
                           xdlclassifier_t *cf, xdfile_t *xdf);
 static void xdl_free_ctx(xdfile_t *xdf);
 static int xdl_clean_mmatch(char const *dis, long i, long s, long e);
-static int xdl_cleanup_records(xdfile_t *xdf1, xdfile_t *xdf2);
+static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);
 static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2);
-static int xdl_optimize_ctxs(xdfile_t *xdf1, xdfile_t *xdf2);
+static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);
 
 
 
 
 static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) {
-       long i;
-
        cf->flags = flags;
 
        cf->hbits = xdl_hashbits((unsigned int) size);
@@ -80,8 +83,15 @@ static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) {
                xdl_cha_free(&cf->ncha);
                return -1;
        }
-       for (i = 0; i < cf->hsize; i++)
-               cf->rchash[i] = NULL;
+       memset(cf->rchash, 0, cf->hsize * sizeof(xdlclass_t *));
+
+       cf->alloc = size;
+       if (!(cf->rcrecs = (xdlclass_t **) xdl_malloc(cf->alloc * sizeof(xdlclass_t *)))) {
+
+               xdl_free(cf->rchash);
+               xdl_cha_free(&cf->ncha);
+               return -1;
+       }
 
        cf->count = 0;
 
@@ -91,16 +101,18 @@ static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) {
 
 static void xdl_free_classifier(xdlclassifier_t *cf) {
 
+       xdl_free(cf->rcrecs);
        xdl_free(cf->rchash);
        xdl_cha_free(&cf->ncha);
 }
 
 
-static int xdl_classify_record(xdlclassifier_t *cf, xrecord_t **rhash, unsigned int hbits,
-                              xrecord_t *rec) {
+static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,
+                              unsigned int hbits, xrecord_t *rec) {
        long hi;
        char const *line;
        xdlclass_t *rcrec;
+       xdlclass_t **rcrecs;
 
        line = rec->ptr;
        hi = (long) XDL_HASHLONG(rec->ha, cf->hbits);
@@ -116,13 +128,25 @@ static int xdl_classify_record(xdlclassifier_t *cf, xrecord_t **rhash, unsigned
                        return -1;
                }
                rcrec->idx = cf->count++;
+               if (cf->count > cf->alloc) {
+                       cf->alloc *= 2;
+                       if (!(rcrecs = (xdlclass_t **) xdl_realloc(cf->rcrecs, cf->alloc * sizeof(xdlclass_t *)))) {
+
+                               return -1;
+                       }
+                       cf->rcrecs = rcrecs;
+               }
+               cf->rcrecs[rcrec->idx] = rcrec;
                rcrec->line = line;
                rcrec->size = rec->size;
                rcrec->ha = rec->ha;
+               rcrec->len1 = rcrec->len2 = 0;
                rcrec->next = cf->rchash[hi];
                cf->rchash[hi] = rcrec;
        }
 
+       (pass == 1) ? rcrec->len1++ : rcrec->len2++;
+
        rec->ha = (unsigned long) rcrec->idx;
 
        hi = (long) XDL_HASHLONG(rec->ha, hbits);
@@ -133,10 +157,10 @@ static int xdl_classify_record(xdlclassifier_t *cf, xrecord_t **rhash, unsigned
 }
 
 
-static int xdl_prepare_ctx(mmfile_t *mf, long narec, xpparam_t const *xpp,
+static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
                           xdlclassifier_t *cf, xdfile_t *xdf) {
        unsigned int hbits;
-       long i, nrec, hsize, bsize;
+       long nrec, hsize, bsize;
        unsigned long hav;
        char const *blk, *cur, *top, *prev;
        xrecord_t *crec;
@@ -146,96 +170,59 @@ static int xdl_prepare_ctx(mmfile_t *mf, long narec, xpparam_t const *xpp,
        char *rchg;
        long *rindex;
 
-       if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0) {
-
-               return -1;
-       }
-       if (!(recs = (xrecord_t **) xdl_malloc(narec * sizeof(xrecord_t *)))) {
-
-               xdl_cha_free(&xdf->rcha);
-               return -1;
-       }
-
-       hbits = xdl_hashbits((unsigned int) narec);
-       hsize = 1 << hbits;
-       if (!(rhash = (xrecord_t **) xdl_malloc(hsize * sizeof(xrecord_t *)))) {
-
-               xdl_free(recs);
-               xdl_cha_free(&xdf->rcha);
-               return -1;
+       ha = NULL;
+       rindex = NULL;
+       rchg = NULL;
+       rhash = NULL;
+       recs = NULL;
+
+       if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0)
+               goto abort;
+       if (!(recs = (xrecord_t **) xdl_malloc(narec * sizeof(xrecord_t *))))
+               goto abort;
+
+       if (xpp->flags & XDF_HISTOGRAM_DIFF)
+               hbits = hsize = 0;
+       else {
+               hbits = xdl_hashbits((unsigned int) narec);
+               hsize = 1 << hbits;
+               if (!(rhash = (xrecord_t **) xdl_malloc(hsize * sizeof(xrecord_t *))))
+                       goto abort;
+               memset(rhash, 0, hsize * sizeof(xrecord_t *));
        }
-       for (i = 0; i < hsize; i++)
-               rhash[i] = NULL;
 
        nrec = 0;
        if ((cur = blk = xdl_mmfile_first(mf, &bsize)) != NULL) {
-               for (top = blk + bsize;;) {
-                       if (cur >= top) {
-                               if (!(cur = blk = xdl_mmfile_next(mf, &bsize)))
-                                       break;
-                               top = blk + bsize;
-                       }
+               for (top = blk + bsize; cur < top; ) {
                        prev = cur;
                        hav = xdl_hash_record(&cur, top, xpp->flags);
                        if (nrec >= narec) {
                                narec *= 2;
-                               if (!(rrecs = (xrecord_t **) xdl_realloc(recs, narec * sizeof(xrecord_t *)))) {
-
-                                       xdl_free(rhash);
-                                       xdl_free(recs);
-                                       xdl_cha_free(&xdf->rcha);
-                                       return -1;
-                               }
+                               if (!(rrecs = (xrecord_t **) xdl_realloc(recs, narec * sizeof(xrecord_t *))))
+                                       goto abort;
                                recs = rrecs;
                        }
-                       if (!(crec = xdl_cha_alloc(&xdf->rcha))) {
-
-                               xdl_free(rhash);
-                               xdl_free(recs);
-                               xdl_cha_free(&xdf->rcha);
-                               return -1;
-                       }
+                       if (!(crec = xdl_cha_alloc(&xdf->rcha)))
+                               goto abort;
                        crec->ptr = prev;
                        crec->size = (long) (cur - prev);
                        crec->ha = hav;
                        recs[nrec++] = crec;
 
-                       if (xdl_classify_record(cf, rhash, hbits, crec) < 0) {
-
-                               xdl_free(rhash);
-                               xdl_free(recs);
-                               xdl_cha_free(&xdf->rcha);
-                               return -1;
-                       }
+                       if (!(xpp->flags & XDF_HISTOGRAM_DIFF) &&
+                               xdl_classify_record(pass, cf, rhash, hbits, crec) < 0)
+                               goto abort;
                }
        }
 
-       if (!(rchg = (char *) xdl_malloc((nrec + 2) * sizeof(char)))) {
-
-               xdl_free(rhash);
-               xdl_free(recs);
-               xdl_cha_free(&xdf->rcha);
-               return -1;
-       }
+       if (!(rchg = (char *) xdl_malloc((nrec + 2) * sizeof(char))))
+               goto abort;
        memset(rchg, 0, (nrec + 2) * sizeof(char));
 
-       if (!(rindex = (long *) xdl_malloc((nrec + 1) * sizeof(long)))) {
-
-               xdl_free(rchg);
-               xdl_free(rhash);
-               xdl_free(recs);
-               xdl_cha_free(&xdf->rcha);
-               return -1;
-       }
-       if (!(ha = (unsigned long *) xdl_malloc((nrec + 1) * sizeof(unsigned long)))) {
-
-               xdl_free(rindex);
-               xdl_free(rchg);
-               xdl_free(rhash);
-               xdl_free(recs);
-               xdl_cha_free(&xdf->rcha);
-               return -1;
-       }
+       if (!(rindex = (long *) xdl_malloc((nrec + 1) * sizeof(long))))
+               goto abort;
+       if (!(ha = (unsigned long *) xdl_malloc((nrec + 1) * sizeof(unsigned long))))
+               goto abort;
 
        xdf->nrec = nrec;
        xdf->recs = recs;
@@ -249,6 +236,15 @@ static int xdl_prepare_ctx(mmfile_t *mf, long narec, xpparam_t const *xpp,
        xdf->dend = nrec - 1;
 
        return 0;
+
+abort:
+       xdl_free(ha);
+       xdl_free(rindex);
+       xdl_free(rchg);
+       xdl_free(rhash);
+       xdl_free(recs);
+       xdl_cha_free(&xdf->rcha);
+       return -1;
 }
 
 
@@ -265,39 +261,53 @@ static void xdl_free_ctx(xdfile_t *xdf) {
 
 int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
                    xdfenv_t *xe) {
-       long enl1, enl2;
+       long enl1, enl2, sample;
        xdlclassifier_t cf;
 
-       enl1 = xdl_guess_lines(mf1) + 1;
-       enl2 = xdl_guess_lines(mf2) + 1;
+       memset(&cf, 0, sizeof(cf));
+
+       /*
+        * For histogram diff, we can afford a smaller sample size and
+        * thus a poorer estimate of the number of lines, as the hash
+        * table (rhash) won't be filled up/grown. The number of lines
+        * (nrecs) will be updated correctly anyway by
+        * xdl_prepare_ctx().
+        */
+       sample = xpp->flags & XDF_HISTOGRAM_DIFF ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1;
+
+       enl1 = xdl_guess_lines(mf1, sample) + 1;
+       enl2 = xdl_guess_lines(mf2, sample) + 1;
 
-       if (xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0) {
+       if (!(xpp->flags & XDF_HISTOGRAM_DIFF) &&
+               xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0) {
 
                return -1;
        }
 
-       if (xdl_prepare_ctx(mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {
+       if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {
 
                xdl_free_classifier(&cf);
                return -1;
        }
-       if (xdl_prepare_ctx(mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {
+       if (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {
 
                xdl_free_ctx(&xe->xdf1);
                xdl_free_classifier(&cf);
                return -1;
        }
 
-       xdl_free_classifier(&cf);
-
        if (!(xpp->flags & XDF_PATIENCE_DIFF) &&
-                       xdl_optimize_ctxs(&xe->xdf1, &xe->xdf2) < 0) {
+                       !(xpp->flags & XDF_HISTOGRAM_DIFF) &&
+                       xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) {
 
                xdl_free_ctx(&xe->xdf2);
                xdl_free_ctx(&xe->xdf1);
                return -1;
        }
 
+       if (!(xpp->flags & XDF_HISTOGRAM_DIFF))
+               xdl_free_classifier(&cf);
+
        return 0;
 }
 
@@ -372,11 +382,10 @@ static int xdl_clean_mmatch(char const *dis, long i, long s, long e) {
  * matches on the other file. Also, lines that have multiple matches
  * might be potentially discarded if they happear in a run of discardable.
  */
-static int xdl_cleanup_records(xdfile_t *xdf1, xdfile_t *xdf2) {
-       long i, nm, rhi, nreff, mlim;
-       unsigned long hav;
+static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) {
+       long i, nm, nreff, mlim;
        xrecord_t **recs;
-       xrecord_t *rec;
+       xdlclass_t *rcrec;
        char *dis, *dis1, *dis2;
 
        if (!(dis = (char *) xdl_malloc(xdf1->nrec + xdf2->nrec + 2))) {
@@ -390,22 +399,16 @@ static int xdl_cleanup_records(xdfile_t *xdf1, xdfile_t *xdf2) {
        if ((mlim = xdl_bogosqrt(xdf1->nrec)) > XDL_MAX_EQLIMIT)
                mlim = XDL_MAX_EQLIMIT;
        for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) {
-               hav = (*recs)->ha;
-               rhi = (long) XDL_HASHLONG(hav, xdf2->hbits);
-               for (nm = 0, rec = xdf2->rhash[rhi]; rec; rec = rec->next)
-                       if (rec->ha == hav && ++nm == mlim)
-                               break;
+               rcrec = cf->rcrecs[(*recs)->ha];
+               nm = rcrec ? rcrec->len2 : 0;
                dis1[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1;
        }
 
        if ((mlim = xdl_bogosqrt(xdf2->nrec)) > XDL_MAX_EQLIMIT)
                mlim = XDL_MAX_EQLIMIT;
        for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) {
-               hav = (*recs)->ha;
-               rhi = (long) XDL_HASHLONG(hav, xdf1->hbits);
-               for (nm = 0, rec = xdf1->rhash[rhi]; rec; rec = rec->next)
-                       if (rec->ha == hav && ++nm == mlim)
-                               break;
+               rcrec = cf->rcrecs[(*recs)->ha];
+               nm = rcrec ? rcrec->len1 : 0;
                dis2[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1;
        }
 
@@ -468,10 +471,10 @@ static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) {
 }
 
 
-static int xdl_optimize_ctxs(xdfile_t *xdf1, xdfile_t *xdf2) {
+static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) {
 
        if (xdl_trim_ends(xdf1, xdf2) < 0 ||
-           xdl_cleanup_records(xdf1, xdf2) < 0) {
+           xdl_cleanup_records(cf, xdf1, xdf2) < 0) {
 
                return -1;
        }
index ab6503460f760356a393a3c7b3b8438213e1ee94..0de084e53f5144153373cc66cae7b523b4ae2812 100644 (file)
 
 
 
-#define XDL_GUESS_NLINES 256
-
-
-
 
 long xdl_bogosqrt(long n) {
        long i;
@@ -71,12 +67,6 @@ void *xdl_mmfile_first(mmfile_t *mmf, long *size)
 }
 
 
-void *xdl_mmfile_next(mmfile_t *mmf, long *size)
-{
-       return NULL;
-}
-
-
 long xdl_mmfile_size(mmfile_t *mmf)
 {
        return mmf->size;
@@ -159,18 +149,12 @@ void *xdl_cha_next(chastore_t *cha) {
 }
 
 
-long xdl_guess_lines(mmfile_t *mf) {
+long xdl_guess_lines(mmfile_t *mf, long sample) {
        long nl = 0, size, tsize = 0;
        char const *data, *cur, *top;
 
        if ((cur = data = xdl_mmfile_first(mf, &size)) != NULL) {
-               for (top = data + size; nl < XDL_GUESS_NLINES;) {
-                       if (cur >= top) {
-                               tsize += (long) (cur - data);
-                               if (!(cur = data = xdl_mmfile_next(mf, &size)))
-                                       break;
-                               top = data + size;
-                       }
+               for (top = data + size; nl < sample && cur < top; ) {
                        nl++;
                        if (!(cur = memchr(cur, '\n', top - cur)))
                                cur = top;
@@ -402,3 +386,34 @@ int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2,
 
        return 0;
 }
+
+int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,
+               int line1, int count1, int line2, int count2)
+{
+       /*
+        * This probably does not work outside Git, since
+        * we have a very simple mmfile structure.
+        *
+        * Note: ideally, we would reuse the prepared environment, but
+        * the libxdiff interface does not (yet) allow for diffing only
+        * ranges of lines instead of the whole files.
+        */
+       mmfile_t subfile1, subfile2;
+       xdfenv_t env;
+
+       subfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1]->ptr;
+       subfile1.size = diff_env->xdf1.recs[line1 + count1 - 2]->ptr +
+               diff_env->xdf1.recs[line1 + count1 - 2]->size - subfile1.ptr;
+       subfile2.ptr = (char *)diff_env->xdf2.recs[line2 - 1]->ptr;
+       subfile2.size = diff_env->xdf2.recs[line2 + count2 - 2]->ptr +
+               diff_env->xdf2.recs[line2 + count2 - 2]->size - subfile2.ptr;
+       if (xdl_do_diff(&subfile1, &subfile2, xpp, &env) < 0)
+               return -1;
+
+       memcpy(diff_env->xdf1.rchg + line1 - 1, env.xdf1.rchg, count1);
+       memcpy(diff_env->xdf2.rchg + line2 - 1, env.xdf2.rchg, count2);
+
+       xdl_free_env(&env);
+
+       return 0;
+}
index d5de8292e05e7c36c4b68857c1cf9855e3d2f70a..714719a89cba9170820bf7d54b9c569d42861aa4 100644 (file)
@@ -33,7 +33,7 @@ void xdl_cha_free(chastore_t *cha);
 void *xdl_cha_alloc(chastore_t *cha);
 void *xdl_cha_first(chastore_t *cha);
 void *xdl_cha_next(chastore_t *cha);
-long xdl_guess_lines(mmfile_t *mf);
+long xdl_guess_lines(mmfile_t *mf, long sample);
 int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags);
 unsigned long xdl_hash_record(char const **data, char const *top, long flags);
 unsigned int xdl_hashbits(unsigned int size);
@@ -41,6 +41,8 @@ int xdl_num_out(char *out, long val);
 long xdl_atol(char const *str, char const **next);
 int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2,
                      const char *func, long funclen, xdemitcb_t *ecb);
+int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,
+                      int line1, int count1, int line2, int count2);
 
 
 
diff --git a/zlib.c b/zlib.c
index c4d58da4e9050c6330ff145914cc379f0600f703..3c63d480c75e9939fb3a047f595b032e9509d681 100644 (file)
--- a/zlib.c
+++ b/zlib.c
  */
 #include "cache.h"
 
-void git_inflate_init(z_streamp strm)
+static const char *zerr_to_string(int status)
 {
-       const char *err;
+       switch (status) {
+       case Z_MEM_ERROR:
+               return "out of memory";
+       case Z_VERSION_ERROR:
+               return "wrong version";
+       case Z_NEED_DICT:
+               return "needs dictionary";
+       case Z_DATA_ERROR:
+               return "data stream error";
+       case Z_STREAM_ERROR:
+               return "stream consistency error";
+       default:
+               return "unknown error";
+       }
+}
 
-       switch (inflateInit(strm)) {
-       case Z_OK:
+/*
+ * avail_in and avail_out in zlib are counted in uInt, which typically
+ * limits the size of the buffer we can use to 4GB when interacting
+ * with zlib in a single call to inflate/deflate.
+ */
+/* #define ZLIB_BUF_MAX ((uInt)-1) */
+#define ZLIB_BUF_MAX ((uInt) 1024 * 1024 * 1024) /* 1GB */
+static inline uInt zlib_buf_cap(unsigned long len)
+{
+       return (ZLIB_BUF_MAX < len) ? ZLIB_BUF_MAX : len;
+}
+
+static void zlib_pre_call(git_zstream *s)
+{
+       s->z.next_in = s->next_in;
+       s->z.next_out = s->next_out;
+       s->z.total_in = s->total_in;
+       s->z.total_out = s->total_out;
+       s->z.avail_in = zlib_buf_cap(s->avail_in);
+       s->z.avail_out = zlib_buf_cap(s->avail_out);
+}
+
+static void zlib_post_call(git_zstream *s)
+{
+       unsigned long bytes_consumed;
+       unsigned long bytes_produced;
+
+       bytes_consumed = s->z.next_in - s->next_in;
+       bytes_produced = s->z.next_out - s->next_out;
+       if (s->z.total_out != s->total_out + bytes_produced)
+               die("BUG: total_out mismatch");
+       if (s->z.total_in != s->total_in + bytes_consumed)
+               die("BUG: total_in mismatch");
+
+       s->total_out = s->z.total_out;
+       s->total_in = s->z.total_in;
+       s->next_in = s->z.next_in;
+       s->next_out = s->z.next_out;
+       s->avail_in -= bytes_consumed;
+       s->avail_out -= bytes_produced;
+}
+
+void git_inflate_init(git_zstream *strm)
+{
+       int status;
+
+       zlib_pre_call(strm);
+       status = inflateInit(&strm->z);
+       zlib_post_call(strm);
+       if (status == Z_OK)
                return;
+       die("inflateInit: %s (%s)", zerr_to_string(status),
+           strm->z.msg ? strm->z.msg : "no message");
+}
 
-       case Z_MEM_ERROR:
-               err = "out of memory";
-               break;
-       case Z_VERSION_ERROR:
-               err = "wrong version";
+void git_inflate_init_gzip_only(git_zstream *strm)
+{
+       /*
+        * Use default 15 bits, +16 is to accept only gzip and to
+        * yield Z_DATA_ERROR when fed zlib format.
+        */
+       const int windowBits = 15 + 16;
+       int status;
+
+       zlib_pre_call(strm);
+       status = inflateInit2(&strm->z, windowBits);
+       zlib_post_call(strm);
+       if (status == Z_OK)
+               return;
+       die("inflateInit2: %s (%s)", zerr_to_string(status),
+           strm->z.msg ? strm->z.msg : "no message");
+}
+
+void git_inflate_end(git_zstream *strm)
+{
+       int status;
+
+       zlib_pre_call(strm);
+       status = inflateEnd(&strm->z);
+       zlib_post_call(strm);
+       if (status == Z_OK)
+               return;
+       error("inflateEnd: %s (%s)", zerr_to_string(status),
+             strm->z.msg ? strm->z.msg : "no message");
+}
+
+int git_inflate(git_zstream *strm, int flush)
+{
+       int status;
+
+       for (;;) {
+               zlib_pre_call(strm);
+               /* Never say Z_FINISH unless we are feeding everything */
+               status = inflate(&strm->z,
+                                (strm->z.avail_in != strm->avail_in)
+                                ? 0 : flush);
+               if (status == Z_MEM_ERROR)
+                       die("inflate: out of memory");
+               zlib_post_call(strm);
+
+               /*
+                * Let zlib work another round, while we can still
+                * make progress.
+                */
+               if ((strm->avail_out && !strm->z.avail_out) &&
+                   (status == Z_OK || status == Z_BUF_ERROR))
+                       continue;
                break;
+       }
+
+       switch (status) {
+       /* Z_BUF_ERROR: normal, needs more space in the output buffer */
+       case Z_BUF_ERROR:
+       case Z_OK:
+       case Z_STREAM_END:
+               return status;
        default:
-               err = "error";
+               break;
        }
-       die("inflateInit: %s (%s)", err, strm->msg ? strm->msg : "no message");
+       error("inflate: %s (%s)", zerr_to_string(status),
+             strm->z.msg ? strm->z.msg : "no message");
+       return status;
 }
 
-void git_inflate_end(z_streamp strm)
+#if defined(NO_DEFLATE_BOUND) || ZLIB_VERNUM < 0x1200
+#define deflateBound(c,s)  ((s) + (((s) + 7) >> 3) + (((s) + 63) >> 6) + 11)
+#endif
+
+unsigned long git_deflate_bound(git_zstream *strm, unsigned long size)
 {
-       if (inflateEnd(strm) != Z_OK)
-               error("inflateEnd: %s", strm->msg ? strm->msg : "failed");
+       return deflateBound(&strm->z, size);
 }
 
-int git_inflate(z_streamp strm, int flush)
+void git_deflate_init(git_zstream *strm, int level)
 {
-       int ret = inflate(strm, flush);
-       const char *err;
+       int status;
 
-       switch (ret) {
-       /* Out of memory is fatal. */
-       case Z_MEM_ERROR:
-               die("inflate: out of memory");
+       zlib_pre_call(strm);
+       status = deflateInit(&strm->z, level);
+       zlib_post_call(strm);
+       if (status == Z_OK)
+               return;
+       die("deflateInit: %s (%s)", zerr_to_string(status),
+           strm->z.msg ? strm->z.msg : "no message");
+}
 
-       /* Data corruption errors: we may want to recover from them (fsck) */
-       case Z_NEED_DICT:
-               err = "needs dictionary"; break;
-       case Z_DATA_ERROR:
-               err = "data stream error"; break;
-       case Z_STREAM_ERROR:
-               err = "stream consistency error"; break;
-       default:
-               err = "unknown error"; break;
+void git_deflate_init_gzip(git_zstream *strm, int level)
+{
+       /*
+        * Use default 15 bits, +16 is to generate gzip header/trailer
+        * instead of the zlib wrapper.
+        */
+       const int windowBits = 15 + 16;
+       int status;
 
+       zlib_pre_call(strm);
+       status = deflateInit2(&strm->z, level,
+                                 Z_DEFLATED, windowBits,
+                                 8, Z_DEFAULT_STRATEGY);
+       zlib_post_call(strm);
+       if (status == Z_OK)
+               return;
+       die("deflateInit2: %s (%s)", zerr_to_string(status),
+           strm->z.msg ? strm->z.msg : "no message");
+}
+
+void git_deflate_end(git_zstream *strm)
+{
+       int status;
+
+       zlib_pre_call(strm);
+       status = deflateEnd(&strm->z);
+       zlib_post_call(strm);
+       if (status == Z_OK)
+               return;
+       error("deflateEnd: %s (%s)", zerr_to_string(status),
+             strm->z.msg ? strm->z.msg : "no message");
+}
+
+int git_deflate_end_gently(git_zstream *strm)
+{
+       int status;
+
+       zlib_pre_call(strm);
+       status = deflateEnd(&strm->z);
+       zlib_post_call(strm);
+       return status;
+}
+
+int git_deflate(git_zstream *strm, int flush)
+{
+       int status;
+
+       for (;;) {
+               zlib_pre_call(strm);
+
+               /* Never say Z_FINISH unless we are feeding everything */
+               status = deflate(&strm->z,
+                                (strm->z.avail_in != strm->avail_in)
+                                ? 0 : flush);
+               if (status == Z_MEM_ERROR)
+                       die("deflate: out of memory");
+               zlib_post_call(strm);
+
+               /*
+                * Let zlib work another round, while we can still
+                * make progress.
+                */
+               if ((strm->avail_out && !strm->z.avail_out) &&
+                   (status == Z_OK || status == Z_BUF_ERROR))
+                       continue;
+               break;
+       }
+
+       switch (status) {
        /* Z_BUF_ERROR: normal, needs more space in the output buffer */
        case Z_BUF_ERROR:
        case Z_OK:
        case Z_STREAM_END:
-               return ret;
+               return status;
+       default:
+               break;
        }
-       error("inflate: %s (%s)", err, strm->msg ? strm->msg : "no message");
-       return ret;
+       error("deflate: %s (%s)", zerr_to_string(status),
+             strm->z.msg ? strm->z.msg : "no message");
+       return status;
 }