Merge branch 'mg/maint-submodule-normalize-path'
authorJunio C Hamano <gitster@pobox.com>
Wed, 11 Mar 2009 20:50:29 +0000 (13:50 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 11 Mar 2009 20:50:29 +0000 (13:50 -0700)
* mg/maint-submodule-normalize-path:
git submodule: Fix adding of submodules at paths with ./, .. and //
git submodule: Add test cases for git submodule add

372 files changed:
.gitignore
Documentation/CodingGuidelines
Documentation/Makefile
Documentation/RelNotes-1.5.2.2.txt
Documentation/RelNotes-1.6.0.2.txt
Documentation/RelNotes-1.6.1.1.txt
Documentation/RelNotes-1.6.1.2.txt
Documentation/RelNotes-1.6.2.1.txt [new file with mode: 0644]
Documentation/RelNotes-1.6.2.txt [new file with mode: 0644]
Documentation/RelNotes-1.6.3.txt [new file with mode: 0644]
Documentation/SubmittingPatches
Documentation/blame-options.txt
Documentation/cat-texi.perl
Documentation/config.txt
Documentation/diff-options.txt
Documentation/git-add.txt
Documentation/git-am.txt
Documentation/git-annotate.txt
Documentation/git-apply.txt
Documentation/git-archive.txt
Documentation/git-bisect.txt
Documentation/git-blame.txt
Documentation/git-bundle.txt
Documentation/git-checkout.txt
Documentation/git-cherry.txt
Documentation/git-config.txt
Documentation/git-describe.txt
Documentation/git-filter-branch.txt
Documentation/git-format-patch.txt
Documentation/git-gc.txt
Documentation/git-imap-send.txt
Documentation/git-ls-files.txt
Documentation/git-ls-tree.txt
Documentation/git-mergetool.txt
Documentation/git-push.txt
Documentation/git-rebase.txt
Documentation/git-reset.txt
Documentation/git-rev-parse.txt
Documentation/git-send-email.txt
Documentation/git-shortlog.txt
Documentation/git-show-branch.txt
Documentation/git-submodule.txt
Documentation/git-svn.txt
Documentation/git-tag.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/gitcore-tutorial.txt
Documentation/githooks.txt
Documentation/gitk.txt
Documentation/gittutorial-2.txt
Documentation/gittutorial.txt
Documentation/howto/rebase-from-internal-branch.txt
Documentation/howto/revert-a-faulty-merge.txt
Documentation/howto/setup-git-server-over-http.txt
Documentation/mailmap.txt [new file with mode: 0644]
Documentation/pretty-formats.txt
Documentation/pretty-options.txt
Documentation/rev-list-options.txt
Documentation/technical/api-parse-options.txt
Documentation/technical/api-run-command.txt
Documentation/technical/api-strbuf.txt
GIT-VERSION-GEN
INSTALL
Makefile
README
RelNotes
archive.c
branch.c
builtin-add.c
builtin-apply.c
builtin-archive.c
builtin-blame.c
builtin-branch.c
builtin-cat-file.c
builtin-checkout.c
builtin-clone.c
builtin-commit.c
builtin-config.c
builtin-count-objects.c
builtin-fetch--tool.c
builtin-fetch-pack.c
builtin-fetch.c
builtin-fmt-merge-msg.c
builtin-fsck.c
builtin-gc.c
builtin-help.c
builtin-init-db.c
builtin-log.c
builtin-ls-files.c
builtin-ls-tree.c
builtin-mailinfo.c
builtin-merge-recursive.c
builtin-merge.c
builtin-pack-objects.c
builtin-prune-packed.c
builtin-prune.c
builtin-receive-pack.c
builtin-remote.c
builtin-rerere.c
builtin-reset.c
builtin-rev-list.c
builtin-revert.c
builtin-shortlog.c
builtin-symbolic-ref.c
builtin-tag.c
builtin-update-index.c
builtin-upload-archive.c
builtin-verify-pack.c
bundle.c
cache.h
color.c
color.h
combine-diff.c
commit.c
commit.h
compat/memmem.c
compat/mingw.c
compat/mingw.h
config.c
config.mak.in
configure.ac
connect.c
contrib/completion/git-completion.bash
contrib/difftool/git-difftool [new file with mode: 0755]
contrib/difftool/git-difftool-helper [new file with mode: 0755]
contrib/difftool/git-difftool.txt [new file with mode: 0644]
contrib/emacs/Makefile
contrib/emacs/README [new file with mode: 0644]
contrib/emacs/git.el
contrib/emacs/vc-git.el [deleted file]
contrib/examples/git-svnimport.perl
contrib/examples/git-svnimport.txt
contrib/fast-import/git-p4
contrib/git-resurrect.sh [new file with mode: 0755]
contrib/hooks/post-receive-email
contrib/vim/README
ctype.c
daemon.c
date.c
diff-lib.c
diff-no-index.c
diff.c
diff.h
diffcore-break.c
diffcore-pickaxe.c
dir.c
dir.h
entry.c
exec_cmd.c
exec_cmd.h
fast-import.c
fsck.c
git-add--interactive.perl
git-am.sh
git-bisect.sh
git-compat-util.h
git-cvsserver.perl
git-filter-branch.sh
git-instaweb.sh
git-mergetool.sh
git-pull.sh
git-quiltimport.sh
git-rebase--interactive.sh
git-rebase.sh
git-repack.sh
git-send-email.perl
git-sh-setup.sh
git-submodule.sh
git-svn.perl
git-web--browse.sh
git.c
gitk-git/gitk
gitweb/README
gitweb/gitweb.perl
grep.c
grep.h
hash-object.c
http-push.c
http.c
imap-send.c
index-pack.c
lockfile.c
log-tree.c
mailmap.c
mailmap.h
merge-index.c
merge-recursive.c
merge-tree.c
mktag.c
mktree.c
pack-redundant.c
pack-write.c
pager.c
parse-options.c
parse-options.h
patch-id.c
path.c
pretty.c
read-cache.c
refs.c
refs.h
remote.c
rerere.c
rerere.h
revision.c
revision.h
run-command.c
run-command.h
setup.c
sha1_file.c
sha1_name.c
sigchain.c [new file with mode: 0644]
sigchain.h [new file with mode: 0644]
strbuf.c
string-list.c
string-list.h
symlinks.c
t/Makefile
t/README
t/lib-httpd.sh
t/lib-httpd/apache.conf
t/lib-rebase.sh [new file with mode: 0644]
t/t0005-signals.sh [new file with mode: 0755]
t/t0060-path-utils.sh
t/t0070-fundamental.sh [new file with mode: 0755]
t/t0100-previous.sh [new file with mode: 0755]
t/t1300-repo-config.sh
t/t1401-symbolic-ref.sh [new file with mode: 0755]
t/t1450-fsck.sh [new file with mode: 0755]
t/t1500-rev-parse.sh
t/t1501-worktree.sh
t/t1504-ceiling-dirs.sh
t/t1505-rev-parse-last.sh [new file with mode: 0755]
t/t2012-checkout-last.sh [new file with mode: 0755]
t/t2200-add-update.sh
t/t2300-cd-to-toplevel.sh
t/t3001-ls-files-others-exclude.sh
t/t3203-branch-output.sh [new file with mode: 0755]
t/t3400-rebase.sh
t/t3404-rebase-interactive.sh
t/t3406-rebase-message.sh
t/t3409-rebase-hook.sh
t/t3410-rebase-preserve-dropped-merges.sh
t/t3411-rebase-preserve-around-merges.sh
t/t3412-rebase-root.sh [new file with mode: 0755]
t/t3505-cherry-pick-empty.sh [new file with mode: 0755]
t/t4002-diff-basic.sh
t/t4011-diff-symlink.sh
t/t4012-diff-binary.sh
t/t4013-diff-various.sh
t/t4013/diff.diff_--dirstat_master~1_master~2 [new file with mode: 0644]
t/t4013/diff.log_--decorate_--all [new file with mode: 0644]
t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_
t/t4013/diff.log_--patch-with-stat_master
t/t4013/diff.log_--patch-with-stat_master_--_dir_
t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master
t/t4013/diff.log_--root_--patch-with-stat_--summary_master
t/t4013/diff.log_--root_--patch-with-stat_master
t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master
t/t4013/diff.log_--root_-p_master
t/t4013/diff.log_--root_master
t/t4013/diff.log_-p_master
t/t4013/diff.log_master
t/t4013/diff.rev-list_--children_HEAD [new file with mode: 0644]
t/t4013/diff.rev-list_--parents_HEAD [new file with mode: 0644]
t/t4013/diff.show_master
t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master
t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master
t/t4014-format-patch.sh
t/t4020-diff-external.sh
t/t4032-diff-inter-hunk-context.sh [new file with mode: 0755]
t/t4033-diff-patience.sh [new file with mode: 0755]
t/t4034-diff-words.sh [new file with mode: 0755]
t/t4106-apply-stdin.sh [new file with mode: 0755]
t/t4150-am.sh
t/t4202-log.sh
t/t4203-mailmap.sh [new file with mode: 0755]
t/t4203-patch-id.sh [new file with mode: 0755]
t/t4252-am-options.sh
t/t4252/am-test-5-1 [new file with mode: 0644]
t/t4252/am-test-5-2 [new file with mode: 0644]
t/t4252/am-test-6-1 [new file with mode: 0644]
t/t5000-tar-tree.sh
t/t5100-mailinfo.sh
t/t5100/empty [new file with mode: 0644]
t/t5100/info0001
t/t5100/info0012 [new file with mode: 0644]
t/t5100/info0013 [new file with mode: 0644]
t/t5100/msg0012 [new file with mode: 0644]
t/t5100/msg0013 [new file with mode: 0644]
t/t5100/patch0012 [new file with mode: 0644]
t/t5100/patch0013 [new file with mode: 0644]
t/t5100/rfc2047-info-0001 [new file with mode: 0644]
t/t5100/rfc2047-info-0002 [new file with mode: 0644]
t/t5100/rfc2047-info-0003 [new file with mode: 0644]
t/t5100/rfc2047-info-0004 [new file with mode: 0644]
t/t5100/rfc2047-info-0005 [new file with mode: 0644]
t/t5100/rfc2047-info-0006 [new file with mode: 0644]
t/t5100/rfc2047-info-0007 [new file with mode: 0644]
t/t5100/rfc2047-info-0008 [new file with mode: 0644]
t/t5100/rfc2047-info-0009 [new file with mode: 0644]
t/t5100/rfc2047-info-0010 [new file with mode: 0644]
t/t5100/rfc2047-info-0011 [new file with mode: 0644]
t/t5100/rfc2047-samples.mbox [new file with mode: 0644]
t/t5100/sample.mbox
t/t5300-pack-object.sh
t/t5302-pack-index.sh
t/t5304-prune.sh
t/t5400-send-pack.sh
t/t5403-post-checkout-hook.sh
t/t5505-remote.sh
t/t5516-fetch-push.sh
t/t5519-push-alternates.sh
t/t5540-http-push.sh
t/t5601-clone.sh
t/t5701-clone-local.sh
t/t5704-bundle.sh [new file with mode: 0755]
t/t5705-clone-2gb.sh [new file with mode: 0755]
t/t6006-rev-list-format.sh
t/t6030-bisect-porcelain.sh
t/t7003-filter-branch.sh
t/t7004-tag.sh
t/t7400-submodule-basic.sh
t/t7500-commit.sh
t/t7501-commit.sh
t/t7610-mergetool.sh
t/t7700-repack.sh
t/t7701-repack-unpack-unreachable.sh
t/t9001-send-email.sh
t/t9129-git-svn-i18n-commitencoding.sh
t/t9130-git-svn-authors-file.sh [new file with mode: 0755]
t/t9131-git-svn-empty-symlink.sh [new file with mode: 0755]
t/t9132-git-svn-broken-symlink.sh [new file with mode: 0755]
t/t9133-git-svn-nested-git-repo.sh [new file with mode: 0755]
t/t9134-git-svn-ignore-paths.sh [new file with mode: 0755]
t/t9135-git-svn-moved-branch-empty-file.sh [new file with mode: 0755]
t/t9135/svn.dump [new file with mode: 0644]
t/t9136-git-svn-recreated-branch-empty-file.sh [new file with mode: 0755]
t/t9136/svn.dump [new file with mode: 0644]
t/t9500-gitweb-standalone-no-errors.sh
t/test-lib.sh
t/valgrind/.gitignore [new file with mode: 0644]
t/valgrind/analyze.sh [new file with mode: 0755]
t/valgrind/default.supp [new file with mode: 0644]
t/valgrind/valgrind.sh [new file with mode: 0755]
templates/hooks--update.sample
templates/this--description
test-ctype.c [new file with mode: 0644]
test-path-utils.c
test-sigchain.c [new file with mode: 0644]
trace.c
transport.c
tree.c
unpack-file.c
unpack-trees.c
update-server-info.c
upload-pack.c
userdiff.c
userdiff.h
utf8.c
utf8.h
var.c
walker.c
wrapper.c
wt-status.c
xdiff-interface.c
xdiff/xdiff.h
xdiff/xdiffi.c
xdiff/xdiffi.h
xdiff/xemit.c
xdiff/xpatience.c [new file with mode: 0644]
xdiff/xprepare.c
index d9adce585af99e617a2906a89029a92045a79538..1c57d4c958bc5e8ff539c5f5ddb1c784d923271b 100644 (file)
@@ -144,6 +144,7 @@ git-core-*/?*
 gitk-wish
 gitweb/gitweb.cgi
 test-chmtime
+test-ctype
 test-date
 test-delta
 test-dump-cache-tree
@@ -152,6 +153,7 @@ test-match-trees
 test-parse-options
 test-path-utils
 test-sha1
+test-sigchain
 common-cmds.h
 *.tar.gz
 *.dsc
index f628c1f3b7b706f9d585b96041e5a4b12bc0f62c..0d7fa9cca9e5c3ec8a11cd2c878f5a7afe353abe 100644 (file)
@@ -21,8 +21,13 @@ code.  For git in general, three rough rules are:
 
 As for more concrete guidelines, just imitate the existing code
 (this is a good guideline, no matter which project you are
-contributing to).  But if you must have a list of rules,
-here they are.
+contributing to). It is always preferable to match the _local_
+convention. New code added to git suite is expected to match
+the overall style of existing code. Modifications to existing
+code is expected to match the style the surrounding code already
+uses (even if it doesn't match the overall style of existing code).
+
+But if you must have a list of rules, here they are.
 
 For shell scripts specifically (not exhaustive):
 
index c34c1cae2072fa27de32f553866426bbc01436d5..144ec32f12a7950746dd71e2ddde5b3f0cab57b0 100644 (file)
@@ -32,6 +32,7 @@ DOC_MAN7=$(patsubst %.txt,%.7,$(MAN7_TXT))
 prefix?=$(HOME)
 bindir?=$(prefix)/bin
 htmldir?=$(prefix)/share/doc/git-doc
+pdfdir?=$(prefix)/share/doc/git-doc
 mandir?=$(prefix)/share/man
 man1dir=$(mandir)/man1
 man5dir=$(mandir)/man5
@@ -50,6 +51,7 @@ infodir?=$(prefix)/share/info
 MAKEINFO=makeinfo
 INSTALL_INFO=install-info
 DOCBOOK2X_TEXI=docbook2x-texi
+DBLATEX=dblatex
 ifndef PERL_PATH
        PERL_PATH = /usr/bin/perl
 endif
@@ -87,6 +89,8 @@ man7: $(DOC_MAN7)
 
 info: git.info gitman.info
 
+pdf: user-manual.pdf
+
 install: install-man
 
 install-man: man
@@ -107,6 +111,10 @@ install-info: info
          echo "No directory found in $(DESTDIR)$(infodir)" >&2 ; \
        fi
 
+install-pdf: pdf
+       $(INSTALL) -d -m 755 $(DESTDIR)$(pdfdir)
+       $(INSTALL) -m 644 user-manual.pdf $(DESTDIR)$(pdfdir)
+
 install-html: html
        sh ./install-webdoc.sh $(DESTDIR)$(htmldir)
 
@@ -187,17 +195,23 @@ git.info: user-manual.texi
 
 user-manual.texi: user-manual.xml
        $(RM) $@+ $@
-       $(DOCBOOK2X_TEXI) user-manual.xml --to-stdout | $(PERL_PATH) fix-texi.perl >$@+
+       $(DOCBOOK2X_TEXI) user-manual.xml --encoding=UTF-8 --to-stdout | \
+               $(PERL_PATH) fix-texi.perl >$@+
+       mv $@+ $@
+
+user-manual.pdf: user-manual.xml
+       $(RM) $@+ $@
+       $(DBLATEX) -o $@+ -p /etc/asciidoc/dblatex/asciidoc-dblatex.xsl -s /etc/asciidoc/dblatex/asciidoc-dblatex.sty $<
        mv $@+ $@
 
 gitman.texi: $(MAN_XML) cat-texi.perl
        $(RM) $@+ $@
-       ($(foreach xml,$(MAN_XML),$(DOCBOOK2X_TEXI) --to-stdout $(xml);)) | \
-       $(PERL_PATH) cat-texi.perl $@ >$@+
+       ($(foreach xml,$(MAN_XML),$(DOCBOOK2X_TEXI) --encoding=UTF-8 \
+               --to-stdout $(xml);)) | $(PERL_PATH) cat-texi.perl $@ >$@+
        mv $@+ $@
 
 gitman.info: gitman.texi
-       $(MAKEINFO) --no-split $*.texi
+       $(MAKEINFO) --no-split --no-validate $*.texi
 
 $(patsubst %.txt,%.texi,$(MAN_TXT)): %.texi : %.xml
        $(RM) $@+ $@
index f6393f8a94f454f4f7b5cc5bb42881b6662cadff..7bfa3417506066c67546f601c6cd36cab8c08531 100644 (file)
@@ -45,7 +45,7 @@ Fixes since v1.5.2.1
     correctly when the branch name had slash in it.
 
   - The email address of the user specified with user.email
-    configuration was overriden by EMAIL environment variable.
+    configuration was overridden by EMAIL environment variable.
 
   - The tree parser did not warn about tree entries with
     nonsense file modes, and assumed they must be blobs.
index 7a9646fc4f3241dad0a7b5c699a4e1935d3100f4..51b32f5d94c050f6cb5eae0b94cedda187e00312 100644 (file)
@@ -7,7 +7,7 @@ Fixes since v1.6.0.1
 * Installation on platforms that needs .exe suffix to git-* programs were
   broken in 1.6.0.1.
 
-* Installation on filesystems without symbolic links support did nto
+* Installation on filesystems without symbolic links support did not
   work well.
 
 * In-tree documentations and test scripts now use "git foo" form to set a
index 88454c19734ffccb29864932f1d5f07a55cd2fe7..8c594ba02fe67fc234572456ea980f3e0fcc65e6 100644 (file)
@@ -41,11 +41,11 @@ Fixes since v1.6.1
   work tree upon delete/modify conflict.
 
 * "git merge -s recursive" didn't leave the index unmerged for entries with
-  rename/delete conflictd.
+  rename/delete conflicts.
 
 * "git merge -s recursive" clobbered untracked files in the work tree.
 
-* "git mv -k" with more than one errorneous paths misbehaved.
+* "git mv -k" with more than one erroneous paths misbehaved.
 
 * "git read-tree -m -u" hence branch switching incorrectly lost a
   subdirectory in rare cases.
index 230aa3d8e88e6f3a9281ab4a95ef3c9c61126550..be37cbb8583bf34f3a93864f16f453a1679fabdb 100644 (file)
@@ -4,8 +4,8 @@ GIT v1.6.1.2 Release Notes
 Fixes since v1.6.1.1
 --------------------
 
-* The logic for rename detectin in internal diff used by commands like
-  "git diff" and "git blame" have been optimized to avoid loading the same
+* The logic for rename detection in internal diff used by commands like
+  "git diff" and "git blame" has been optimized to avoid loading the same
   blob repeatedly.
 
 * We did not allow writing out a blob that is larger than 2GB for no good
diff --git a/Documentation/RelNotes-1.6.2.1.txt b/Documentation/RelNotes-1.6.2.1.txt
new file mode 100644 (file)
index 0000000..3a2d3bf
--- /dev/null
@@ -0,0 +1,6 @@
+GIT v1.6.2.1 Release Notes
+==========================
+
+Fixes since v1.6.2
+------------------
+
diff --git a/Documentation/RelNotes-1.6.2.txt b/Documentation/RelNotes-1.6.2.txt
new file mode 100644 (file)
index 0000000..ad060f4
--- /dev/null
@@ -0,0 +1,164 @@
+GIT v1.6.2 Release Notes
+========================
+
+With the next major release, "git push" into a branch that is
+currently checked out will be refused by default.  You can choose
+what should happen upon such a push by setting the configuration
+variable receive.denyCurrentBranch in the receiving repository.
+
+To ease the transition plan, the receiving repository of such a
+push running this release will issue a big warning when the
+configuration variable is missing.  Please refer to:
+
+  http://git.or.cz/gitwiki/GitFaq#non-bare
+  http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007
+
+for more details on the reason why this change is needed and the
+transition plan.
+
+For a similar reason, "git push $there :$killed" to delete the branch
+$killed in a remote repository $there, if $killed branch is the current
+branch pointed at by its HEAD, gets a large warning.  You can choose what
+should happen upon such a push by setting the configuration variable
+receive.denyDeleteCurrent in the receiving repository.
+
+
+Updates since v1.6.1
+--------------------
+
+(subsystems)
+
+* git-svn updates.
+
+* gitweb updates, including a new patch view and RSS/Atom feed
+  improvements.
+
+* (contrib/emacs) git.el now has commands for checking out a branch,
+  creating a branch, cherry-picking and reverting commits; vc-git.el
+  is not shipped with git anymore (it is part of official Emacs).
+
+(performance)
+
+* pack-objects autodetects the number of CPUs available and uses threaded
+  version.
+
+(usability, bells and whistles)
+
+* automatic typo correction works on aliases as well
+
+* @{-1} is a way to refer to the last branch you were on.  This is
+  accepted not only where an object name is expected, but anywhere
+  a branch name is expected and acts as if you typed the branch name.
+  E.g. "git branch --track mybranch @{-1}", "git merge @{-1}", and
+  "git rev-parse --symbolic-full-name @{-1}" would work as expected.
+
+* When refs/remotes/origin/HEAD points at a remote tracking branch that
+  has been pruned away, many git operations issued warning when they
+  internally enumerated the refs.  We now warn only when you say "origin"
+  to refer to that pruned branch.
+
+* The location of .mailmap file can be configured, and its file format was
+  enhanced to allow mapping an incorrect e-mail field as well.
+
+* "git add -p" learned 'g'oto action to jump directly to a hunk.
+
+* "git add -p" learned to find a hunk with given text with '/'.
+
+* "git add -p" optionally can be told to work with just the command letter
+  without Enter.
+
+* when "git am" stops upon a patch that does not apply, it shows the
+  title of the offending patch.
+
+* "git am --directory=<dir>" and "git am --reject" passes these options
+  to underlying "git apply".
+
+* "git am" learned --ignore-date option.
+
+* "git blame" aligns author names better when they are spelled in
+  non US-ASCII encoding.
+
+* "git clone" now makes its best effort when cloning from an empty
+  repository to set up configuration variables to refer to the remote
+  repository.
+
+* "git checkout -" is a shorthand for "git checkout @{-1}".
+
+* "git cherry" defaults to whatever the current branch is tracking (if
+  exists) when the <upstream> argument is not given.
+
+* "git cvsserver" can be told not to add extra "via git-CVS emulator" to
+  the commit log message it serves via gitcvs.commitmsgannotation
+  configuration.
+
+* "git cvsserver" learned to handle 'noop' command some CVS clients seem
+  to expect to work.
+
+* "git diff" learned a new option --inter-hunk-context to coalesce close
+  hunks together and show context between them.
+
+* The definition of what constitutes a word for "git diff --color-words"
+  can be customized via gitattributes, command line or a configuration.
+
+* "git diff" learned --patience to run "patience diff" algorithm.
+
+* "git filter-branch" learned --prune-empty option that discards commits
+  that do not change the contents.
+
+* "git fsck" now checks loose objects in alternate object stores, instead
+  of misreporting them as missing.
+
+* "git gc --prune" was resurrected to allow "git gc --no-prune" and
+  giving non-default expiration period e.g. "git gc --prune=now".
+
+* "git grep -w" and "git grep" for fixed strings have been optimized.
+
+* "git mergetool" learned -y(--no-prompt) option to disable prompting.
+
+* "git rebase -i" can transplant a history down to root to elsewhere
+  with --root option.
+
+* "git reset --merge" is a new mode that works similar to the way
+  "git checkout" switches branches, taking the local changes while
+  switching to another commit.
+
+* "git submodule update" learned --no-fetch option.
+
+* "git tag" learned --contains that works the same way as the same option
+  from "git branch".
+
+
+Fixes since v1.6.1
+------------------
+
+All of the fixes in v1.6.1.X maintenance series are included in this
+release, unless otherwise noted.
+
+Here are fixes that this release has, but have not been backported to
+v1.6.1.X series.
+
+* "git-add sub/file" when sub is a submodule incorrectly added the path to
+  the superproject.
+
+* "git bundle" did not exclude annotated tags even when a range given
+  from the command line wanted to.
+
+* "git filter-branch" unnecessarily refused to work when you had
+  checked out a different commit from what is recorded in the superproject
+  index in a submodule.
+
+* "git filter-branch" incorrectly tried to update a nonexistent work tree
+  at the end when it is run in a bare repository.
+
+* "git gc" did not work if your repository was created with an ancient git
+  and never had any pack files in it before.
+
+* "git mergetool" used to ignore autocrlf and other attributes
+  based content rewriting.
+
+* branch switching and merges had a silly bug that did not validate
+  the correct directory when making sure an existing subdirectory is
+  clean.
+
+* "git -p cmd" when cmd is not a built-in one left the display in funny state
+  when killed in the middle.
diff --git a/Documentation/RelNotes-1.6.3.txt b/Documentation/RelNotes-1.6.3.txt
new file mode 100644 (file)
index 0000000..ee1fddb
--- /dev/null
@@ -0,0 +1,104 @@
+GIT v1.6.3 Release Notes
+========================
+
+With the next major release, "git push" into a branch that is
+currently checked out will be refused by default.  You can choose
+what should happen upon such a push by setting the configuration
+variable receive.denyCurrentBranch in the receiving repository.
+
+To ease the transition plan, the receiving repository of such a
+push running this release will issue a big warning when the
+configuration variable is missing.  Please refer to:
+
+  http://git.or.cz/gitwiki/GitFaq#non-bare
+  http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007
+
+for more details on the reason why this change is needed and the
+transition plan.
+
+For a similar reason, "git push $there :$killed" to delete the branch
+$killed in a remote repository $there, if $killed branch is the current
+branch pointed at by its HEAD, gets a large warning.  You can choose what
+should happen upon such a push by setting the configuration variable
+receive.denyDeleteCurrent in the receiving repository.
+
+
+Updates since v1.6.2
+--------------------
+
+(subsystems)
+
+(performance)
+
+(usability, bells and whistles)
+
+* "--pretty=<style>" option to the log family of commands can now be
+  spelled as "--format=<style>".  In addition, --format=%formatstring
+  is a short-hand for --pretty=tformat:%formatstring.
+
+* "--oneline" is a synonym for "--pretty=oneline --abbrev=commit".
+
+* If you realize that you botched the patch when you are editing hunks
+  with the 'edit' action in git-add -i/-p, you can abort the editor to
+  tell git not to apply it.
+
+* git-archive learned --output=<file> option.
+
+* git-bisect shows not just the number of remaining commits whose goodness
+  is unknown, but also shows the estimated number of remaining rounds.
+
+* git-branch -r shows HEAD symref that points at a remote branch in
+  interest of each tracked remote repository.
+
+* git-config learned -e option to open an editor to edit the config file
+  directly.
+
+* git-format-patch can be told to use attachment with a new configuration,
+  format.attach.
+
+* git-imap-send learned to work around Thunderbird's inability to easily
+  disable format=flowed with a new configuration, imap.preformattedHTML.
+
+* git-rebase can be told to rebase the series even if your branch is a
+  descendant of the commit you are rebasing onto with --force-rebase
+  option.
+
+* git-send-email learned --confirm option to review the Cc: list before
+  sending the messages out.
+
+(developers)
+
+* Test scripts can be run under valgrind.
+
+
+Fixes since v1.6.2
+------------------
+
+All of the fixes in v1.6.2.X maintenance series are included in this
+release, unless otherwise noted.
+
+Here are fixes that this release has, but have not been backported to
+v1.6.2.X series.
+
+* .gitignore learned to handle backslash as a quoting mechanism for
+  comment introduction character "#" (backport by merging dd482ee if
+  needed).
+
+* timestamp output in --date=relative mode used to display timestamps that
+  are long time ago in the default mode; it now uses "N years M months
+  ago", and "N years ago" (backport by picking 10edf37 if needed).
+
+* git-add -i/-p now works with non-ASCII pathnames (backport by picking
+  8851f48 if needed).
+
+* "git hash-object -w" did not read from the configuration file from the
+  correct .git directory (backport by merging 272459a if needed).
+
+* git-send-email learned to correctly handle multiple Cc: addresses
+  (backport by merging afe756c if needed).
+
+---
+exec >/var/tmp/1
+O=v1.6.2-77-g8cc3fe4
+echo O=$(git describe master)
+git shortlog --no-merges $O..master ^maint
index ba07c8c57107a8438d5b3ce1a49761dba7182d25..8d818a21600f386abb02dee902d842a716767027 100644 (file)
@@ -376,9 +376,36 @@ Thunderbird
 
 (A Large Angry SCM)
 
+By default, Thunderbird will both wrap emails as well as flag them as
+being 'format=flowed', both of which will make the resulting email unusable
+by git.
+
 Here are some hints on how to successfully submit patches inline using
 Thunderbird.
 
+There are two different approaches.  One approach is to configure
+Thunderbird to not mangle patches.  The second approach is to use
+an external editor to keep Thunderbird from mangling the patches.
+
+Approach #1 (configuration):
+
+This recipe is current as of Thunderbird 2.0.0.19.  Three steps:
+  1.  Configure your mail server composition as plain text
+      Edit...Account Settings...Composition & Addressing,
+        uncheck 'Compose Messages in HTML'.
+  2.  Configure your general composition window to not wrap
+      Edit..Preferences..Composition, wrap plain text messages at 0
+  3.  Disable the use of format=flowed
+      Edit..Preferences..Advanced..Config Editor.  Search for:
+        mailnews.send_plaintext_flowed
+      toggle it to make sure it is set to 'false'.
+
+After that is done, you should be able to compose email as you
+otherwise would (cut + paste, git-format-patch | git-imap-send, etc),
+and the patches should not be mangled.
+
+Approach #2 (external editor):
+
 This recipe appears to work with the current [*1*] Thunderbird from Suse.
 
 The following Thunderbird extensions are needed:
@@ -464,6 +491,12 @@ message, complete the addressing and subject fields, and press send.
 Gmail
 -----
 
+GMail does not appear to have any way to turn off line wrapping in the web
+interface, so this will mangle any emails that you send.  You can however
+use any IMAP email client to connect to the google imap server, and forward
+the emails through that.  Just make sure to disable line wrapping in that
+email client.  Alternatively, use "git send-email" instead.
+
 Submitting properly formatted patches via Gmail is simple now that
 IMAP support is available. First, edit your ~/.gitconfig to specify your
 account settings:
@@ -476,6 +509,9 @@ account settings:
        port = 993
        sslverify = false
 
+You might need to instead use: folder = "[Google Mail]/Drafts" if you get an error
+that the "Folder doesn't exist".
+
 Next, ensure that your Gmail settings are correct. In "Settings" the
 "Use Unicode (UTF-8) encoding for outgoing messages" should be checked.
 
@@ -486,3 +522,4 @@ command to send the patch emails to your Gmail Drafts folder.
 
 Go to your Gmail account, open the Drafts folder, find the patch email, fill
 in the To: and CC: fields and send away!
+
index 7f28432254a81e4c39ce8ff204b4d6618050c5c6..63fc197fbe768e39fde653a24f77e6d8f03af114 100644 (file)
@@ -70,11 +70,19 @@ of lines before or after the line given by <start>.
        tree copy has the contents of the named file (specify
        `-` to make the command read from the standard input).
 
+--date <format>::
+       The value is one of the following alternatives:
+       {relative,local,default,iso,rfc,short}. If --date is not
+       provided, the value of the blame.date config variable is
+       used. If the blame.date config variable is also not set, the
+       iso format is used. For more information, See the discussion
+       of the --date option at linkgit:git-log[1].
+
 -M|<num>|::
        Detect moving lines in the file as well.  When a commit
        moves a block of lines in a file (e.g. the original file
        has A and then B, and the commit changes it to B and
-       then A), traditional 'blame' algorithm typically blames
+       then A), the traditional 'blame' algorithm typically blames
        the lines that were moved up (i.e. B) to the parent and
        assigns blame to the lines that were moved down (i.e. A)
        to the child commit.  With this option, both groups of lines
@@ -90,8 +98,8 @@ commit.
        files that were modified in the same commit.  This is
        useful when you reorganize your program and move code
        around across files.  When this option is given twice,
-       the command looks for copies from all other files in the
-       parent for the commit that creates the file in addition.
+       the command additionally looks for copies from all other
+       files in the parent for the commit that creates the file.
 +
 <num> is optional but it is the lower bound on the number of
 alphanumeric characters that git must detect as moving
index dbc133cd3c1f19dd507014477e68b8ada78eab5e..828ec62554fe927eb16a03f835448e6db0c303a1 100755 (executable)
 
 printf '\input texinfo
 @setfilename gitman.info
-@documentencoding us-ascii
-@node Top,,%s
+@documentencoding UTF-8
+@dircategory Development
+@direntry
+* Git Man Pages: (gitman).  Manual pages for Git revision control system
+@end direntry
+@node Top,,, (dir)
 @top Git Manual Pages
 @documentlanguage en
 @menu
index 2ed868c81a09af9176bd805f819c0b312556891a..56bd781a163d70ce23f1b6932ce3b1c70510d3b5 100644 (file)
@@ -556,8 +556,8 @@ color.interactive::
 
 color.interactive.<slot>::
        Use customized color for 'git-add --interactive'
-       output. `<slot>` may be `prompt`, `header`, or `help`, for
-       three distinct types of normal output from interactive
+       output. `<slot>` may be `prompt`, `header`, `help` or `error`, for
+       four distinct types of normal output from interactive
        programs.  The values of these variables may be specified as
        in color.branch.<slot>.
 
@@ -639,6 +639,12 @@ diff.suppressBlankEmpty::
        A boolean to inhibit the standard behavior of printing a space
        before each empty output line. Defaults to false.
 
+diff.wordRegex::
+       A POSIX Extended Regular Expression used to determine what is a "word"
+       when performing word-by-word difference calculations.  Character
+       sequences that match the regular expression are "words", all other
+       characters are *ignorable* whitespace.
+
 fetch.unpackLimit::
        If the number of objects fetched over the git native
        transfer is below this
@@ -671,6 +677,16 @@ format.pretty::
        See linkgit:git-log[1], linkgit:git-show[1],
        linkgit:git-whatchanged[1].
 
+format.thread::
+       The default threading style for 'git-format-patch'.  Can be
+       either a boolean value, `shallow` or `deep`.  'Shallow'
+       threading makes every mail a reply to the head of the series,
+       where the head is chosen from the cover letter, the
+       `\--in-reply-to`, and the first patch mail, in this order.
+       'Deep' threading makes every mail a reply to the previous one.
+       A true boolean value is the same as `shallow`, and a false
+       value disables threading.
+
 gc.aggressiveWindow::
        The window size parameter used in the delta compression
        algorithm used by 'git-gc --aggressive'.  This defaults
@@ -725,6 +741,10 @@ gc.rerereunresolved::
        kept for this many days when 'git-rerere gc' is run.
        The default is 15 days.  See linkgit:git-rerere[1].
 
+gitcvs.commitmsgannotation::
+       Append this string to each commit message. Set to empty string
+       to disable this feature. Defaults to "via git-CVS emulator".
+
 gitcvs.enabled::
        Whether the CVS server interface is enabled for this repository.
        See linkgit:git-cvsserver[1].
@@ -990,6 +1010,13 @@ instaweb.port::
        The port number to bind the gitweb httpd to. See
        linkgit:git-instaweb[1].
 
+interactive.singlekey::
+       In interactive programs, allow the user to provide one-letter
+       input with a single key (i.e., without hitting enter).
+       Currently this is used only by the `\--patch` mode of
+       linkgit:git-add[1].  Note that this setting is silently
+       ignored if portable keystroke input is not available.
+
 log.date::
        Set default date-time mode for the log command. Setting log.date
        value is similar to using 'git-log'\'s --date option. The value is one of the
@@ -1002,6 +1029,14 @@ log.showroot::
        Tools like linkgit:git-log[1] or linkgit:git-whatchanged[1], which
        normally hide the root commit will now show it. True by default.
 
+mailmap.file::
+       The location of an augmenting mailmap file. The default
+       mailmap, located in the root of the repository, is loaded
+       first, then the mailmap file pointed to by this variable.
+       The location of the mailmap file may be in a repository
+       subdirectory, or somewhere outside of the repository itself.
+       See linkgit:git-shortlog[1] and linkgit:git-blame[1].
+
 man.viewer::
        Specify the programs that may be used to display help in the
        'man' format. See linkgit:git-help[1].
@@ -1046,6 +1081,16 @@ mergetool.keepBackup::
        is set to `false` then this file is not preserved.  Defaults to
        `true` (i.e. keep the backup files).
 
+mergetool.keepTemporaries::
+       When invoking a custom merge tool, git uses a set of temporary
+       files to pass to the tool. If the tool returns an error and this
+       variable is set to `true`, then these temporary files will be
+       preserved, otherwise they will be removed after the tool has
+       exited. Defaults to `false`.
+
+mergetool.prompt::
+       Prompt before each invocation of the merge resolution program.
+
 pack.window::
        The size of the window used by linkgit:git-pack-objects[1] when no
        window size is given on the command line. Defaults to 10.
@@ -1125,6 +1170,10 @@ pull.octopus::
 pull.twohead::
        The default merge strategy to use when pulling a single branch.
 
+rebase.stat::
+       Whether to show a diffstat of what changed upstream since the last
+       rebase. False by default.
+
 receive.fsckObjects::
        If it is set to true, git-receive-pack will check all received
        objects. It will abort in the case of a malformed object or a
index b432d2518aba3d1be9d86fb1613bbe2f5fa9da4d..813a7b11b99d51d3014f854770384ed4fc247c9f 100644 (file)
@@ -36,6 +36,9 @@ endif::git-format-patch[]
 --patch-with-raw::
        Synonym for "-p --raw".
 
+--patience::
+       Generate a diff using the "patience diff" algorithm.
+
 --stat[=width[,name-width]]::
        Generate a diffstat.  You can override the default
        output width for 80-column terminal by "--stat=width".
@@ -91,8 +94,22 @@ endif::git-format-patch[]
        Turn off colored diff, even when the configuration file
        gives the default to color output.
 
---color-words::
-       Show colored word diff, i.e. color words which have changed.
+--color-words[=<regex>]::
+       Show colored word diff, i.e., color words which have changed.
+       By default, words are separated by whitespace.
++
+When a <regex> is specified, every non-overlapping match of the
+<regex> is considered a word.  Anything between these matches is
+considered whitespace and ignored(!) for the purposes of finding
+differences.  You may want to append `|[^[:space:]]` to your regular
+expression to make sure that it matches all non-whitespace characters.
+A match that contains a newline is silently truncated(!) at the
+newline.
++
+The regex can also be set via a diff driver or configuration option, see
+linkgit:gitattributes[1] or linkgit:git-config[1].  Giving it explicitly
+overrides any diff driver or configuration setting.  Diff drivers
+override configuration settings.
 
 --no-renames::
        Turn off rename detection, even when the configuration
@@ -116,7 +133,7 @@ endif::git-format-patch[]
 --abbrev[=<n>]::
        Instead of showing the full 40-byte hexadecimal object
        name in diff-raw format output and diff-tree header
-       lines, show only handful hexdigits prefix.  This is
+       lines, show only a partial prefix.  This is
        independent of --full-index option above, which controls
        the diff-patch output format.  Non default number of
        digits can be specified with --abbrev=<n>.
@@ -205,6 +222,10 @@ endif::git-format-patch[]
        differences even if one line has whitespace where the other
        line has none.
 
+--inter-hunk-context=<lines>::
+       Show the context between diff hunks, up to the specified number
+       of lines, thereby fusing hunks that are close to each other.
+
 --exit-code::
        Make the program exit with codes similar to diff(1).
        That is, it exits with 1 if there were differences and
index e4c711bbd2d1a95d501978673bc52f7a2957fba7..ce71838b9e17f3b68dcd19648da3a88dfe4f35bd 100644 (file)
@@ -263,13 +263,6 @@ diff::
   This lets you review what will be committed (i.e. between
   HEAD and index).
 
-Bugs
-----
-The interactive mode does not work with files whose names contain
-characters that need C-quoting.  `core.quotepath` configuration can be
-used to work this limitation around to some degree, but backslash,
-double-quote and control characters will still have problems.
-
 SEE ALSO
 --------
 linkgit:git-status[1]
index b9c6fac7483dbefba0afb60a76ac0362aa390a6d..1e71dd536b1881b61a007af09f1dedea37fe984e 100644 (file)
@@ -10,8 +10,10 @@ SYNOPSIS
 --------
 [verse]
 'git am' [--signoff] [--keep] [--utf8 | --no-utf8]
-        [--3way] [--interactive]
-         [--whitespace=<option>] [-C<n>] [-p<n>]
+        [--3way] [--interactive] [--committer-date-is-author-date]
+        [--ignore-date]
+        [--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>]
+        [--reject]
         [<mbox> | <Maildir>...]
 'git am' (--skip | --resolved | --abort)
 
@@ -25,8 +27,8 @@ OPTIONS
 -------
 <mbox>|<Maildir>...::
        The list of mailbox files to read patches from. If you do not
-       supply this argument, reads from the standard input. If you supply
-       directories, they'll be treated as Maildirs.
+       supply this argument, the command reads from the standard input.
+       If you supply directories, they will be treated as Maildirs.
 
 -s::
 --signoff::
@@ -46,7 +48,7 @@ OPTIONS
        preferred encoding if it is not UTF-8).
 +
 This was optional in prior versions of git, but now it is the
-default.   You could use `--no-utf8` to override this.
+default.   You can use `--no-utf8` to override this.
 
 --no-utf8::
        Pass `-n` flag to 'git-mailinfo' (see
@@ -55,17 +57,15 @@ default.   You could use `--no-utf8` to override this.
 -3::
 --3way::
        When the patch does not apply cleanly, fall back on
-       3-way merge, if the patch records the identity of blobs
-       it is supposed to apply to, and we have those blobs
+       3-way merge if the patch records the identity of blobs
+       it is supposed to apply to and we have those blobs
        available locally.
 
 --whitespace=<option>::
-       This flag is passed to the 'git-apply' (see linkgit:git-apply[1])
-       program that applies
-       the patch.
-
 -C<n>::
 -p<n>::
+--directory=<dir>::
+--reject::
        These flags are passed to the 'git-apply' (see linkgit:git-apply[1])
        program that applies
        the patch.
@@ -74,6 +74,20 @@ default.   You could use `--no-utf8` to override this.
 --interactive::
        Run interactively.
 
+--committer-date-is-author-date::
+       By default the command records the date from the e-mail
+       message as the commit author date, and uses the time of
+       commit creation as the committer date. This allows the
+       user to lie about the committer date by using the same
+       timestamp as the author date.
+
+--ignore-date::
+       By default the command records the date from the e-mail
+       message as the commit author date, and uses the time of
+       commit creation as the committer date. This allows the
+       user to lie about author timestamp by using the same
+       timestamp as the committer date.
+
 --skip::
        Skip the current patch.  This is only meaningful when
        restarting an aborted patch.
@@ -107,18 +121,18 @@ the commit, after stripping common prefix "[PATCH <anything>]".
 It is supposed to describe what the commit is about concisely as
 a one line text.
 
-The body of the message (iow, after a blank line that terminates
-RFC2822 headers) can begin with "Subject: " and "From: " lines
-that are different from those of the mail header, to override
-the values of these fields.
+The body of the message (the rest of the message after the blank line
+that terminates the RFC2822 headers) can begin with "Subject: " and
+"From: " lines that are different from those of the mail header,
+to override the values of these fields.
 
 The commit message is formed by the title taken from the
 "Subject: ", a blank line and the body of the message up to
-where the patch begins.  Excess whitespaces at the end of the
+where the patch begins.  Excess whitespace characters at the end of the
 lines are automatically stripped.
 
 The patch is expected to be inline, directly following the
-message.  Any line that is of form:
+message.  Any line that is of the form:
 
 * three-dashes and end-of-line, or
 * a line that begins with "diff -", or
@@ -127,18 +141,18 @@ message.  Any line that is of form:
 is taken as the beginning of a patch, and the commit log message
 is terminated before the first occurrence of such a line.
 
-When initially invoking it, you give it names of the mailboxes
-to crunch.  Upon seeing the first patch that does not apply, it
-aborts in the middle,.  You can recover from this in one of two ways:
+When initially invoking it, you give it the names of the mailboxes
+to process.  Upon seeing the first patch that does not apply, it
+aborts in the middle.  You can recover from this in one of two ways:
 
-. skip the current patch by re-running the command with '--skip'
+. skip the current patch by re-running the command with the '--skip'
   option.
 
 . hand resolve the conflict in the working directory, and update
-  the index file to bring it in a state that the patch should
-  have produced.  Then run the command with '--resolved' option.
+  the index file to bring it into a state that the patch should
+  have produced.  Then run the command with the '--resolved' option.
 
-The command refuses to process new mailboxes while `.git/rebase-apply`
+The command refuses to process new mailboxes while the `.git/rebase-apply`
 directory exists, so if you decide to start over from scratch,
 run `rm -f -r .git/rebase-apply` before running the command with mailbox
 names.
index 0aba022ba6838442721a0584f3eaa293a3a90f74..0590eec0566cf9f318ddd1ac27d10bb989b2c52f 100644 (file)
@@ -3,7 +3,7 @@ git-annotate(1)
 
 NAME
 ----
-git-annotate - Annotate file lines with commit info
+git-annotate - Annotate file lines with commit information
 
 SYNOPSIS
 --------
@@ -12,11 +12,11 @@ SYNOPSIS
 DESCRIPTION
 -----------
 Annotates each line in the given file with information from the commit
-which introduced the line. Optionally annotate from a given revision.
+which introduced the line. Optionally annotates from a given revision.
 
 The only difference between this command and linkgit:git-blame[1] is that
 they use slightly different output formats, and this command exists only
-for backward compatibility to support existing scripts, and provide more
+for backward compatibility to support existing scripts, and provide more
 familiar command name for people coming from other SCM systems.
 
 OPTIONS
index 32f2b85a105e684c7c04f8825b8b74c511271cef..9e5baa277731adf14e47fda8c6b13a076e0873db 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git apply' [--stat] [--numstat] [--summary] [--check] [--index]
-         [--apply] [--no-add] [--build-fake-ancestor <file>] [-R | --reverse]
+         [--apply] [--no-add] [--build-fake-ancestor=<file>] [-R | --reverse]
          [--allow-binary-replacement | --binary] [--reject] [-z]
          [-pNUM] [-CNUM] [--inaccurate-eof] [--recount] [--cached]
          [--whitespace=<nowarn|warn|fix|error|error-all>]
@@ -25,7 +25,7 @@ and a work tree.
 OPTIONS
 -------
 <patch>...::
-       The files to read patch from.  '-' can be used to read
+       The files to read the patch from.  '-' can be used to read
        from the standard input.
 
 --stat::
@@ -33,8 +33,8 @@ OPTIONS
        input.  Turns off "apply".
 
 --numstat::
-       Similar to \--stat, but shows number of added and
-       deleted lines in decimal notation and pathname without
+       Similar to \--stat, but shows the number of added and
+       deleted lines in decimal notation and the pathname without
        abbreviation, to make it more machine friendly.  For
        binary files, outputs two `-` instead of saying
        `0 0`.  Turns off "apply".
@@ -60,15 +60,15 @@ OPTIONS
        causes the index file to be updated.
 
 --cached::
-       Apply a patch without touching the working tree. Instead, take the
-       cached data, apply the patch, and store the result in the index,
+       Apply a patch without touching the working tree. Instead take the
+       cached data, apply the patch, and store the result in the index
        without using the working tree. This implies '--index'.
 
---build-fake-ancestor <file>::
+--build-fake-ancestor=<file>::
        Newer 'git-diff' output has embedded 'index information'
        for each blob to help identify the original version that
        the patch applies to.  When this flag is given, and if
-       the original versions of the blobs is available locally,
+       the original versions of the blobs are available locally,
        builds a temporary index containing those blobs.
 +
 When a pure mode change is encountered (which has no index information),
@@ -109,13 +109,13 @@ the information is read from the current index instead.
        applying a diff generated with --unified=0. To bypass these
        checks use '--unidiff-zero'.
 +
-Note, for the reasons stated above usage of context-free patches are
+Note, for the reasons stated above usage of context-free patches is
 discouraged.
 
 --apply::
        If you use any of the options marked "Turns off
        'apply'" above, 'git-apply' reads and outputs the
-       information you asked without actually applying the
+       requested information without actually applying the
        patch.  Give this flag after those flags to also apply
        the patch.
 
@@ -124,7 +124,7 @@ discouraged.
        patch.  This can be used to extract the common part between
        two files by first running 'diff' on them and applying
        the result with this option, which would apply the
-       deletion part but not addition part.
+       deletion part but not the addition part.
 
 --allow-binary-replacement::
 --binary::
@@ -162,7 +162,7 @@ By default, the command outputs warning messages but applies the patch.
 When `git-apply` is used for statistics and not applying a
 patch, it defaults to `nowarn`.
 +
-You can use different `<action>` to control this
+You can use different `<action>` values to control this
 behavior:
 +
 * `nowarn` turns off the trailing whitespace warning.
@@ -170,7 +170,7 @@ behavior:
   patch as-is (default).
 * `fix` outputs warnings for a few such errors, and applies the
   patch after fixing them (`strip` is a synonym --- the tool
-  used to consider only trailing whitespaces as errors, and the
+  used to consider only trailing whitespace characters as errors, and the
   fix involved 'stripping' them, but modern gits do more).
 * `error` outputs warnings for a few such errors, and refuses
   to apply the patch.
@@ -195,7 +195,7 @@ behavior:
        adjusting the hunk headers appropriately).
 
 --directory=<root>::
-       Prepend <root> to all filenames.  If a "-p" argument was passed, too,
+       Prepend <root> to all filenames.  If a "-p" argument was also passed,
        it is applied before prepending the new root.
 +
 For example, a patch that talks about updating `a/git-gui.sh` to `b/git-gui.sh`
@@ -221,7 +221,7 @@ ignored, i.e., they are not required to be up-to-date or clean and they
 are not updated.
 
 If --index is not specified, then the submodule commits in the patch
-are ignored and only the absence of presence of the corresponding
+are ignored and only the absence or presence of the corresponding
 subdirectory is checked and (if possible) updated.
 
 Author
index 41cbf9c0819872a322321455b8a5cb805efcc26b..0eeefe00603814fcabc7d537d5c04e5801591c56 100644 (file)
@@ -10,6 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git archive' --format=<fmt> [--list] [--prefix=<prefix>/] [<extra>]
+             [--output=<file>]
              [--remote=<repo> [--exec=<git-upload-archive>]] <tree-ish>
              [path...]
 
@@ -47,6 +48,9 @@ OPTIONS
 --prefix=<prefix>/::
        Prepend <prefix>/ to each filename in the archive.
 
+--output=<file>::
+       Write the archive to <file> instead of stdout.
+
 <extra>::
        This can be any options that the archiver backend understand.
        See next section.
@@ -88,6 +92,18 @@ tar.umask::
        archiving user's umask will be used instead.  See umask(2) for
        details.
 
+ATTRIBUTES
+----------
+
+export-ignore::
+       Files and directories with the attribute export-ignore won't be
+       added to archive files.  See linkgit:gitattributes[5] for details.
+
+export-subst::
+       If the attribute export-subst is set for a file then git will
+       expand several placeholders when adding this file to an archive.
+       See linkgit:gitattributes[5] for details.
+
 EXAMPLES
 --------
 git archive --format=tar --prefix=junk/ HEAD | (cd /var/tmp/ && tar xf -)::
@@ -110,6 +126,11 @@ git archive --format=zip --prefix=git-docs/ HEAD:Documentation/ > git-1.4.0-docs
        Put everything in the current head's Documentation/ directory
        into 'git-1.4.0-docs.zip', with the prefix 'git-docs/'.
 
+
+SEE ALSO
+--------
+linkgit:gitattributes[5]
+
 Author
 ------
 Written by Franck Bui-Huu and Rene Scharfe.
index 147ea381977a459f1fa624408cc3af3e9b9f3bd5..e65c1cae8b3da4efae7571628c1586cbb56151ae 100644 (file)
@@ -212,7 +212,7 @@ If you have a script that can tell if the current source code is good
 or bad, you can automatically bisect using:
 
 ------------
-$ git bisect run my_script
+$ git bisect run my_script arguments
 ------------
 
 Note that the "run" script (`my_script` in the above example) should
@@ -252,6 +252,13 @@ $ git bisect start HEAD v1.2 --      # HEAD is bad, v1.2 is good
 $ git bisect run make                # "make" builds the app
 ------------
 
+* Automatically bisect a test failure between origin and HEAD:
++
+------------
+$ git bisect start HEAD origin --    # HEAD is bad, origin is good
+$ git bisect run make test           # "make test" builds and tests
+------------
+
 * Automatically bisect a broken test suite:
 +
 ------------
@@ -291,6 +298,15 @@ It's safer if both "test.sh" and "check_test_case.sh" scripts are
 outside the repo to prevent interactions between the bisect, make and
 test processes and the scripts.
 
+* Automatically bisect a broken test suite:
++
+------------
+$ git bisect start HEAD HEAD~10 --   # culprit is among the last 10
+$ git bisect run sh -c "make || exit 125; ~/check_test_case.sh"
+------------
++
+Does the same as the previous example, but on a single line.
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
index cc934e55c38902e1bac0964e134640c910b9de96..4ef54d660280c921913a61afe117968d51960ec3 100644 (file)
@@ -184,6 +184,12 @@ there is ever added information (like the commit encoding or extended
 commit commentary), a blame viewer won't ever care.
 
 
+MAPPING AUTHORS
+---------------
+
+include::mailmap.txt[]
+
+
 SEE ALSO
 --------
 linkgit:git-annotate[1]
index 1b66ab743c64d980a43a028d57ca2f6505d97845..57590b148011c44485d28d93d31c6a75a681bfb0 100644 (file)
@@ -84,7 +84,7 @@ defining the basis.  More than one reference may be packaged, and more
 than one basis can be specified.  The objects packaged are those not
 contained in the union of the given bases.  Each basis can be
 specified explicitly (e.g., ^master~10), or implicitly (e.g.,
-master~10..master, master --since=10.days.ago).
+master~10..master, --since=10.days.ago master).
 
 It is very important that the basis used be held by the destination.
 It is okay to err on the side of conservatism, causing the bundle file
@@ -94,75 +94,111 @@ when unpacking at the destination.
 EXAMPLE
 -------
 
-Assume two repositories exist as R1 on machine A, and R2 on machine B.
+Assume you want to transfer the history from a repository R1 on machine A
+to another repository R2 on machine B.
 For whatever reason, direct connection between A and B is not allowed,
 but we can move data from A to B via some mechanism (CD, email, etc).
 We want to update R2 with developments made on branch master in R1.
 
-To create the bundle you have to specify the basis. You have some options:
+To bootstrap the process, you can first create a bundle that doesn't have
+any basis. You can use a tag to remember up to what commit you sent out
+in order to make it easy to later update the other repository with
+incremental bundle,
 
-- Without basis.
-+
-This is useful when sending the whole history.
+----------------
+machineA$ cd R1
+machineA$ git bundle create file.bundle master
+machineA$ git tag -f lastR2bundle master
+----------------
 
-------------
-$ git bundle create mybundle master
-------------
+Then you sneakernet file.bundle to the target machine B. Because you don't
+have to have any object to extract objects from such a bundle, not only
+you can fetch/pull from a bundle, you can clone from it as if it was a
+remote repository.
 
-- Using temporally tags.
-+
-We set a tag in R1 (lastR2bundle) after the previous such transport,
-and move it afterwards to help build the bundle.
+----------------
+machineB$ git clone /home/me/tmp/file.bundle R2
+----------------
 
-------------
-$ git bundle create mybundle master ^lastR2bundle
-$ git tag -f lastR2bundle master
-------------
+This will define a remote called "origin" in the resulting repository that
+lets you fetch and pull from the bundle. $GIT_DIR/config file in R2 may
+have an entry like this:
 
-- Using a tag present in both repositories
+------------------------
+[remote "origin"]
+    url = /home/me/tmp/file.bundle
+    fetch = refs/heads/*:refs/remotes/origin/*
+------------------------
+
+You can fetch/pull to update the resulting mine.git repository after
+replacing the bundle you store at /home/me/tmp/file.bundle with incremental
+updates from here on.
+
+After working more in the original repository, you can create an
+incremental bundle to update the other:
+
+----------------
+machineA$ cd R1
+machineA$ git bundle create file.bundle lastR2bundle..master
+machineA$ git tag -f lastR2bundle master
+----------------
+
+and sneakernet it to the other machine to replace /home/me/tmp/file.bundle,
+and pull from it.
+
+----------------
+machineB$ cd R2
+machineB$ git pull
+----------------
 
-------------
-$ git bundle create mybundle master ^v1.0.0
-------------
+If you know up to what commit the intended recipient repository should
+have the necessary objects for, you can use that knowledge to specify the
+basis, giving a cut-off point to limit the revisions and objects that go
+in the resulting bundle. The previous example used lastR2bundle tag
+for this purpose, but you can use other options you would give to
+the linkgit:git-log[1] command. Here are more examples:
 
-- A basis based on time.
+You can use a tag that is present in both.
 
-------------
-$ git bundle create mybundle master --since=10.days.ago
-------------
+----------------
+$ git bundle create mybundle v1.0.0..master
+----------------
 
-- With a limit on the number of commits
+You can use a basis based on time.
 
-------------
-$ git bundle create mybundle master -n 10
-------------
+----------------
+$ git bundle create mybundle --since=10.days master
+----------------
 
-Then you move mybundle from A to B, and in R2 on B:
+Or you can use the number of commits.
 
-------------
+----------------
+$ git bundle create mybundle -10 master
+----------------
+
+You can run `git-bundle verify` to see if you can extract from a bundle
+that was created with a basis.
+
+----------------
 $ git bundle verify mybundle
-$ git fetch mybundle master:localRef
-------------
+----------------
 
-With something like this in the config in R2:
+This will list what commits you must have in order to extract from the
+bundle and will error out if you don't have them.
 
-------------------------
-[remote "bundle"]
-    url = /home/me/tmp/file.bdl
-    fetch = refs/heads/*:refs/remotes/origin/*
-------------------------
+A bundle from a recipient repository's point of view is just like a
+regular repository it fetches/pulls from. You can for example map
+refs, like this example, when fetching:
 
-You can first sneakernet the bundle file to ~/tmp/file.bdl and
-then these commands on machine B:
+----------------
+$ git fetch mybundle master:localRef
+----------------
 
-------------
-$ git ls-remote bundle
-$ git fetch bundle
-$ git pull bundle
-------------
+Or see what refs it offers.
 
-would treat it as if it is talking with a remote side over the
-network.
+----------------
+$ git ls-remote mybundle
+----------------
 
 Author
 ------
index 9cd51514dbfd987db4423e254e8bab7284352525..125d8f3c32470cd0917bade45ed7e9fcd7f4c376 100644 (file)
@@ -8,7 +8,7 @@ git-checkout - Checkout a branch or paths to the working tree
 SYNOPSIS
 --------
 [verse]
-'git checkout' [-q] [-f] [--track | --no-track] [-b <new_branch> [-l]] [-m] [<branch>]
+'git checkout' [-q] [-f] [-t | --track | --no-track] [-b <new_branch> [-l]] [-m] [<branch>]
 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
 
 DESCRIPTION
@@ -21,15 +21,15 @@ specified, <new_branch>.  Using -b will cause <new_branch> to
 be created; in this case you can use the --track or --no-track
 options, which will be passed to `git branch`.
 
-As a convenience, --track will default to create a branch whose
+As a convenience, --track will default to creating a branch whose
 name is constructed from the specified branch name by stripping
 the first namespace level.
 
 When <paths> are given, this command does *not* switch
 branches.  It updates the named paths in the working tree from
 the index file, or from a named <tree-ish> (most often a commit).  In
-this case, the `-b` options is meaningless and giving
-either of them results in an error.  <tree-ish> argument can be
+this case, the `-b` and `--track` options are meaningless and giving
+either of them results in an error. The <tree-ish> argument can be
 used to specify a specific tree-ish (i.e. commit, tag or tree)
 to update the index for the given paths before updating the
 working tree.
@@ -75,14 +75,13 @@ entries; instead, unmerged entries are ignored.
        <repository> <refspec>" explicitly. This behavior is the default
        when the start point is a remote branch. Set the
        branch.autosetupmerge configuration variable to `false` if you want
-       'git-checkout' and 'git-branch' to always behave as if '--no-track' were
+       'git checkout' and 'git branch' to always behave as if '--no-track' were
        given. Set it to `always` if you want this behavior when the
-       start-point is either a local or remote branch.
+       start point is either a local or remote branch.
 +
-If no '-b' option was given, the name of the new branch will be
-derived from the remote branch, by attempting to guess the name
-of the branch on remote system.  If "remotes/" or "refs/remotes/"
-are prefixed, it is stripped away, and then the part up to the
+If no '-b' option is given, the name of the new branch will be
+derived from the remote branch.  If "remotes/" or "refs/remotes/"
+is prefixed it is stripped away, and then the part up to the
 next slash (which would be the nickname of the remote) is removed.
 This would tell us to use "hack" as the local branch when branching
 off of "origin/hack" (or "remotes/origin/hack", or even
@@ -133,6 +132,10 @@ the conflicted merge in the specified paths.
 +
 When this parameter names a non-branch (but still a valid commit object),
 your HEAD becomes 'detached'.
++
+As a special case, the "`@\{-N\}`" syntax for the N-th last branch
+checks out the branch (instead of detaching).  You may also specify
+"`-`" which is synonymous with "`@\{-1\}`".
 
 
 Detached HEAD
@@ -148,12 +151,12 @@ $ git checkout v2.6.18
 ------------
 
 Earlier versions of git did not allow this and asked you to
-create a temporary branch using `-b` option, but starting from
+create a temporary branch using the `-b` option, but starting from
 version 1.5.0, the above command 'detaches' your HEAD from the
-current branch and directly point at the commit named by the tag
-(`v2.6.18` in the above example).
+current branch and directly points at the commit named by the tag
+(`v2.6.18` in the example above).
 
-You can use usual git commands while in this state.  You can use
+You can use all git commands while in this state.  You can use
 `git reset --hard $othercommit` to further move around, for
 example.  You can make changes and create a new commit on top of
 a detached HEAD.  You can even create a merge by using `git
@@ -187,7 +190,7 @@ $ git checkout hello.c            <3>
 ------------
 +
 <1> switch branch
-<2> take out a file out of other commit
+<2> take a file out of another commit
 <3> restore hello.c from HEAD of current branch
 +
 If you have an unfortunate branch that is named `hello.c`, this
@@ -198,7 +201,7 @@ You should instead write:
 $ git checkout -- hello.c
 ------------
 
-. After working in a wrong branch, switching to the correct
+. After working in the wrong branch, switching to the correct
 branch would be done using:
 +
 ------------
@@ -206,7 +209,7 @@ $ git checkout mytopic
 ------------
 +
 However, your "wrong" branch and correct "mytopic" branch may
-differ in files that you have locally modified, in which case,
+differ in files that you have modified locally, in which case
 the above checkout would fail like this:
 +
 ------------
index 74d14c4e7fc88e702e4781b22eb8ed5ce0480c6f..7deefdae8f995d843971f6beff24af8325b99f1f 100644 (file)
@@ -7,7 +7,7 @@ git-cherry - Find commits not merged upstream
 
 SYNOPSIS
 --------
-'git cherry' [-v] <upstream> [<head>] [<limit>]
+'git cherry' [-v] [<upstream> [<head> [<limit>]]]
 
 DESCRIPTION
 -----------
@@ -51,6 +51,7 @@ OPTIONS
 
 <upstream>::
        Upstream branch to compare against.
+       Defaults to the first tracked remote branch, if available.
 
 <head>::
        Working branch; defaults to HEAD.
index 19a8917b83f4ff111e9eec0767c3261a2c39d995..82ce89eae849e5a1c697a3c7f659a80521600348 100644 (file)
@@ -22,6 +22,7 @@ SYNOPSIS
 'git config' [<file-option>] [-z|--null] -l | --list
 'git config' [<file-option>] --get-color name [default]
 'git config' [<file-option>] --get-colorbool name [stdout-is-tty]
+'git config' [<file-option>] -e | --edit
 
 DESCRIPTION
 -----------
@@ -130,6 +131,10 @@ See also <<FILES>>.
        in the config file will cause the value to be multiplied
        by 1024, 1048576, or 1073741824 prior to output.
 
+--bool-or-int::
+       'git-config' will ensure that the output matches the format of
+       either --bool or --int, as described above.
+
 -z::
 --null::
        For all options that output values and/or keys, always
@@ -157,6 +162,11 @@ See also <<FILES>>.
        output.  The optional `default` parameter is used instead, if
        there is no color configured for `name`.
 
+-e::
+--edit::
+       Opens an editor to modify the specified config file; either
+       '--system', '--global', or repository (default).
+
 [[FILES]]
 FILES
 -----
index a30c5ac96618f5010388edd66815ec97971a92e0..b231dbb947791bb4fc5cde552e8c736b3558ca0a 100644 (file)
@@ -87,7 +87,7 @@ With something like git.git current tree, I get:
        v1.0.4-14-g2414721
 
 i.e. the current head of my "parent" branch is based on v1.0.4,
-but since it has a handful commits on top of that,
+but since it has a few commits on top of that,
 describe has added the number of additional commits ("14") and
 an abbreviated object name for the commit itself ("2414721")
 at the end.
index fed6de6a7fa0e720994f7094be2f464818af289a..7ffe03f4279a8ca8110806260f33a58f849351e4 100644 (file)
@@ -122,6 +122,10 @@ You can use the 'map' convenience function in this filter, and other
 convenience functions, too.  For example, calling 'skip_commit "$@"'
 will leave out the current commit (but not its changes! If you want
 that, use 'git-rebase' instead).
++
+You can also use the 'git_commit_non_empty_tree "$@"' instead of
+'git commit-tree "$@"' if you don't wish to keep commits with a single parent
+and that makes no change to the tree.
 
 --tag-name-filter <command>::
        This is the filter for rewriting tag names. When passed,
@@ -151,6 +155,16 @@ to other tags will be rewritten to point to the underlying commit.
        The result will contain that directory (and only that) as its
        project root.
 
+--prune-empty::
+       Some kind of filters will generate empty commits, that left the tree
+       untouched.  This switch allow git-filter-branch to ignore such
+       commits.  Though, this switch only applies for commits that have one
+       and only one parent, it will hence keep merges points. Also, this
+       option is not compatible with the use of '--commit-filter'. Though you
+       just need to use the function 'git_commit_non_empty_tree "$@"' instead
+       of the 'git commit-tree "$@"' idiom in your commit filter to make that
+       happen.
+
 --original <namespace>::
        Use this option to set the namespace where the original commits
        will be stored. The default value is 'refs/original'.
@@ -198,6 +212,11 @@ git filter-branch --index-filter 'git rm --cached filename' HEAD
 
 Now, you will get the rewritten history saved in HEAD.
 
+As with using `rm filename`, `git rm --cached filename` will fail
+if the file is absent from the tree of a commit.  If it is not important
+whether the file is already absent from the tree, you can use
+`git rm --cached --ignore-unmatch filename` instead.
+
 To rewrite the repository to look as if `foodir/` had been its project
 root, and discard all other history:
 
@@ -320,6 +339,47 @@ git filter-branch --index-filter \
 ---------------------------------------------------------------
 
 
+
+Checklist for Shrinking a Repository
+------------------------------------
+
+git-filter-branch is often used to get rid of a subset of files,
+usually with some combination of `\--index-filter` and
+`\--subdirectory-filter`.  People expect the resulting repository to
+be smaller than the original, but you need a few more steps to
+actually make it smaller, because git tries hard not to lose your
+objects until you tell it to.  First make sure that:
+
+* You really removed all variants of a filename, if a blob was moved
+  over its lifetime.  `git log \--name-only \--follow \--all \--
+  filename` can help you find renames.
+
+* You really filtered all refs: use `\--tag-name-filter cat \--
+  \--all` when calling git-filter-branch.
+
+Then there are two ways to get a smaller repository.  A safer way is
+to clone, that keeps your original intact.
+
+* Clone it with `git clone +++file:///path/to/repo+++`.  The clone
+  will not have the removed objects.  See linkgit:git-clone[1].  (Note
+  that cloning with a plain path just hardlinks everything!)
+
+If you really don't want to clone it, for whatever reasons, check the
+following points instead (in this order).  This is a very destructive
+approach, so *make a backup* or go back to cloning it.  You have been
+warned.
+
+* Remove the original refs backed up by git-filter-branch: say `git
+  for-each-ref \--format="%(refname)" refs/original/ | xargs -n 1 git
+  update-ref -d`.
+
+* Expire all reflogs with `git reflog expire \--expire=now \--all`.
+
+* Garbage collect all unreferenced objects with `git gc \--prune=now`
+  (or if your git-gc is not new enough to support arguments to
+  `\--prune`, use `git repack -ad; git prune` instead).
+
+
 Author
 ------
 Written by Petr "Pasky" Baudis <pasky@suse.cz>,
index 11a7d772618f48b961d41fb5fe1cb269d7ae7cd1..c14e3ee395f465c761bcdcd71e49807286f08742 100644 (file)
@@ -10,7 +10,8 @@ SYNOPSIS
 --------
 [verse]
 'git format-patch' [-k] [-o <dir> | --stdout] [--thread]
-                  [--attach[=<boundary>] | --inline[=<boundary>]]
+                  [--attach[=<boundary>] | --inline[=<boundary>] |
+                    [--no-attach]]
                   [-s | --signoff] [<common diff options>]
                   [-n | --numbered | -N | --no-numbered]
                   [--start-number <n>] [--numbered-files]
@@ -117,15 +118,27 @@ include::diff-options.txt[]
        which is the commit message and the patch itself in the
        second part, with "Content-Disposition: attachment".
 
+--no-attach::
+       Disable the creation of an attachment, overriding the
+       configuration setting.
+
 --inline[=<boundary>]::
        Create multipart/mixed attachment, the first part of
        which is the commit message and the patch itself in the
        second part, with "Content-Disposition: inline".
 
---thread::
+--thread[=<style>]::
        Add In-Reply-To and References headers to make the second and
        subsequent mails appear as replies to the first.  Also generates
        the Message-Id header to reference.
++
+The optional <style> argument can be either `shallow` or `deep`.
+'Shallow' threading makes every mail a reply to the head of the
+series, where the head is chosen from the cover letter, the
+`\--in-reply-to`, and the first patch mail, in this order.  'Deep'
+threading makes every mail a reply to the previous one.  If not
+specified, defaults to the 'format.thread' configuration, or `shallow`
+if that is not set.
 
 --in-reply-to=Message-Id::
        Make the first mail (or all the mails with --no-thread) appear as a
@@ -174,7 +187,8 @@ CONFIGURATION
 -------------
 You can specify extra mail header lines to be added to each message
 in the repository configuration, new defaults for the subject prefix
-and file suffix, and number patches when outputting more than one.
+and file suffix, control attachements, and number patches when outputting
+more than one.
 
 ------------
 [format]
@@ -183,6 +197,7 @@ and file suffix, and number patches when outputting more than one.
        suffix = .txt
        numbered = auto
        cc = <email>
+       attach [ = mime-boundary-string ]
 ------------
 
 
index 7086eea74a38b036130f61db362bae209a065e47..b292e9843aa9da86cd44bd07d3ce35053be32177 100644 (file)
@@ -8,7 +8,7 @@ git-gc - Cleanup unnecessary files and optimize the local repository
 
 SYNOPSIS
 --------
-'git gc' [--aggressive] [--auto] [--quiet]
+'git gc' [--aggressive] [--auto] [--quiet] [--prune=<date> | --no-prune]
 
 DESCRIPTION
 -----------
@@ -59,6 +59,14 @@ are consolidated into a single pack by using the `-A` option of
 'git-repack'. Setting `gc.autopacklimit` to 0 disables
 automatic consolidation of packs.
 
+--prune=<date>::
+       Prune loose objects older than date (default is 2 weeks ago,
+       overrideable by the config variable `gc.pruneExpire`).  This
+       option is on by default.
+
+--no-prune::
+       Do not prune any loose objects.
+
 --quiet::
        Suppress all progress reports.
 
index bd49a0aee8881f983077d74bd8d0fbe5764dfea5..024084b8b717d680c274737a14f609d299b6daa8 100644 (file)
@@ -64,6 +64,13 @@ imap.sslverify::
        used by the SSL/TLS connection. Default is `true`. Ignored when
        imap.tunnel is set.
 
+imap.preformattedHTML::
+       A boolean to enable/disable the use of html encoding when sending
+       a patch.  An html encoded patch will be bracketed with <pre>
+       and have a content type of text/html.  Ironically, enabling this
+       option causes Thunderbird to send the patch as a plain/text,
+       format=fixed email.  Default is `false`.
+
 Examples
 ~~~~~~~~
 
@@ -98,6 +105,20 @@ Using direct mode with SSL:
 ..........................
 
 
+CAUTION
+-------
+It is still your responsibility to make sure that the email message
+sent by your email program meets the standards of your project.
+Many projects do not like patches to be attached.  Some mail
+agents will transform patches (e.g. wrap lines, send them as
+format=flowed) in ways that make them fail.  You will get angry
+flames ridiculing you if you don't check this.
+
+Thunderbird in particular is known to be problematic.  Thunderbird
+users may wish to visit this web page for more information:
+  http://kb.mozillazine.org/Plain_text_e-mail_-_Thunderbird#Completely_plain_email
+
+
 BUGS
 ----
 Doesn't handle lines starting with "From " in the message body.
index 9f85d60b5fb6d6ae1b4d8c2e65a6131cbe21450b..057a021eb50899ed5fe1ee5a7b6c59e7fc27becf 100644 (file)
@@ -126,7 +126,7 @@ OPTIONS
 
 --abbrev[=<n>]::
        Instead of showing the full 40-byte hexadecimal object
-       lines, show only handful hexdigits prefix.
+       lines, show only a partial prefix.
        Non default number of digits can be specified with --abbrev=<n>.
 
 \--::
index db6ebccd6dc51d9f1463710f29a8f821f13fd412..f68e5c5c1abd15350e679eebf976f0ac8516123b 100644 (file)
@@ -61,7 +61,7 @@ OPTIONS
 
 --abbrev[=<n>]::
        Instead of showing the full 40-byte hexadecimal object
-       lines, show only handful hexdigits prefix.
+       lines, show only a partial prefix.
        Non default number of digits can be specified with --abbrev=<n>.
 
 --full-name::
index 602e7c6d3b497aa4f891915305d507ae88910a54..5d3c6328726ba1af829026dd97840fc43335cfe0 100644 (file)
@@ -7,7 +7,7 @@ git-mergetool - Run merge conflict resolution tools to resolve merge conflicts
 
 SYNOPSIS
 --------
-'git mergetool' [--tool=<tool>] [<file>]...
+'git mergetool' [--tool=<tool>] [-y|--no-prompt|--prompt] [<file>]...
 
 DESCRIPTION
 -----------
@@ -22,7 +22,8 @@ with merge conflicts.
 
 OPTIONS
 -------
--t or --tool=<tool>::
+-t <tool>::
+--tool=<tool>::
        Use the merge resolution program specified by <tool>.
        Valid merge tools are:
        kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge, and opendiff
@@ -60,6 +61,16 @@ variable `mergetool.<tool>.trustExitCode` can be set to `true`.
 Otherwise, 'git-mergetool' will prompt the user to indicate the
 success of the resolution after the custom tool has exited.
 
+-y::
+--no-prompt::
+       Don't prompt before each invocation of the merge resolution
+       program.
+
+--prompt::
+       Prompt before each invocation of the merge resolution program.
+       This is the default behaviour; the option is provided to
+       override any configuration settings.
+
 Author
 ------
 Written by Theodore Y Ts'o <tytso@mit.edu>
index ac6421178c1a62ca62fb335d9939ec36368c7e82..4e7e5a719a4b0447159213466d46aa2360b7408e 100644 (file)
@@ -48,17 +48,19 @@ push. Arbitrary expressions cannot be used here, an actual ref must
 be named. If `:`<dst> is omitted, the same ref as <src> will be
 updated.
 +
-The object referenced by <src> is used to fast forward the ref <dst>
-on the remote side. If the optional leading plus `{plus}` is used, the
-remote ref is updated even if it does not result in a fast forward
-update.
+The object referenced by <src> is used to update the <dst> reference
+on the remote side, but by default this is only allowed if the
+update can fast forward <dst>.  By having the optional leading `{plus}`,
+you can tell git to update the <dst> ref even when the update is not a
+fast forward.  This does *not* attempt to merge <src> into <dst>.  See
+EXAMPLES below for details.
 +
 `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
 +
 Pushing an empty <src> allows you to delete the <dst> ref from
 the remote repository.
 +
-The special refspec `:` (or `+:` to allow non-fast forward updates)
+The special refspec `:` (or `{plus}:` to allow non-fast forward updates)
 directs git to push "matching" branches: for every branch that exists on
 the local side, the remote side is updated if a branch of the same name
 already exists on the remote side.  This is the default operation mode
@@ -218,6 +220,30 @@ 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::
+       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
+       following situation, where a fast forward is not possible:
++
+----
+           o---o---o---A---B  origin/master
+                    \
+                     X---Y---Z  dev
+----
++
+The above command would change the origin repository to
++
+----
+                     A---B  (unnamed branch)
+                    /
+           o---o---o---X---Y---Z  master
+----
++
+Commits A and B would no longer belong to a branch with a symbolic name,
+and so would be unreachable.  As such, these commits would be removed by
+a `git gc` command on the origin repository.
+
 
 Author
 ------
index c8ad86a56fc6bff70cb6e7c74cc8ef10ef2da73e..57bd333f0b3660b12382365ee2b6d8e6c2418f02 100644 (file)
@@ -8,10 +8,11 @@ git-rebase - Forward-port local commits to the updated upstream head
 SYNOPSIS
 --------
 [verse]
-'git rebase' [-i | --interactive] [-v | --verbose] [-m | --merge]
-       [-s <strategy> | --strategy=<strategy>] [--no-verify]
-       [-C<n>] [ --whitespace=<option>] [-p | --preserve-merges]
-       [--onto <newbase>] <upstream> [<branch>]
+'git rebase' [-i | --interactive] [options] [--onto <newbase>]
+       <upstream> [<branch>]
+'git rebase' [-i | --interactive] [options] --onto <newbase>
+       --root [<branch>]
+
 'git rebase' --continue | --skip | --abort
 
 DESCRIPTION
@@ -22,7 +23,8 @@ it remains on the current branch.
 
 All changes made by commits in the current branch but that are not
 in <upstream> are saved to a temporary area.  This is the same set
-of commits that would be shown by `git log <upstream>..HEAD`.
+of commits that would be shown by `git log <upstream>..HEAD` (or
+`git log HEAD`, if --root is specified).
 
 The current branch is reset to <upstream>, or <newbase> if the
 --onto option was supplied.  This has the exact same effect as
@@ -190,6 +192,13 @@ Alternatively, you can undo the 'git-rebase' with
 
     git rebase --abort
 
+CONFIGURATION
+-------------
+
+rebase.stat::
+       Whether to show a diffstat of what changed upstream since the last
+       rebase. False by default.
+
 OPTIONS
 -------
 <newbase>::
@@ -230,7 +239,15 @@ OPTIONS
 
 -v::
 --verbose::
-       Display a diffstat of what changed upstream since the last rebase.
+       Be verbose. Implies --stat.
+
+--stat::
+       Show a diffstat of what changed upstream since the last rebase. The
+       diffstat is also controlled by the configuration option rebase.stat.
+
+-n::
+--no-stat::
+       Do not show a diffstat as part of the rebase process.
 
 --no-verify::
        This option bypasses the pre-rebase hook.  See also linkgit:githooks[5].
@@ -241,9 +258,10 @@ OPTIONS
        context exist they all must match.  By default no context is
        ever ignored.
 
---whitespace=<nowarn|warn|error|error-all|strip>::
+--whitespace=<option>::
        This flag is passed to the 'git-apply' program
        (see linkgit:git-apply[1]) that applies the patch.
+       Incompatible with the --interactive option.
 
 -i::
 --interactive::
@@ -255,6 +273,15 @@ OPTIONS
 --preserve-merges::
        Instead of ignoring merges, try to recreate them.
 
+--root::
+       Rebase all commits reachable from <branch>, instead of
+       limiting them with an <upstream>.  This allows you to rebase
+       the root commit(s) on a branch.  Must be used with --onto, and
+       will skip changes already contained in <newbase> (instead of
+       <upstream>).  When used together with --preserve-merges, 'all'
+       root commits will be rewritten to have <newbase> as parent
+       instead.
+
 include::merge-strategies.txt[]
 
 NOTES
index 2049f3d97b67adc8fa93ca462875db6e522e1923..abb25d1c00c97144b1f3709e408fe9cad613e623 100644 (file)
@@ -8,7 +8,7 @@ git-reset - Reset current HEAD to the specified state
 SYNOPSIS
 --------
 [verse]
-'git reset' [--mixed | --soft | --hard] [-q] [<commit>]
+'git reset' [--mixed | --soft | --hard | --merge] [-q] [<commit>]
 'git reset' [-q] [<commit>] [--] <paths>...
 
 DESCRIPTION
@@ -45,6 +45,11 @@ OPTIONS
        switched to. Any changes to tracked files in the working tree
        since <commit> are lost.
 
+--merge::
+       Resets the index to match the tree recorded by the named commit,
+       and updates the files that are different between the named commit
+       and the current commit in the working tree.
+
 -q::
        Be quiet, only report errors.
 
@@ -152,6 +157,28 @@ tip of the current branch in ORIG_HEAD, so resetting hard to it
 brings your index file and the working tree back to that state,
 and resets the tip of the branch to that commit.
 
+Undo a merge or pull inside a dirty work tree::
++
+------------
+$ git pull                         <1>
+Auto-merging nitfol
+Merge made by recursive.
+ nitfol                |   20 +++++----
+ ...
+$ git reset --merge ORIG_HEAD      <2>
+------------
++
+<1> Even if you may have local modifications in your
+working tree, you can safely say "git pull" when you know
+that the change in the other branch does not overlap with
+them.
+<2> After inspecting the result of the merge, you may find
+that the change in the other branch is unsatisfactory.  Running
+"git reset --hard ORIG_HEAD" will let you go back to where you
+were, but it will discard your local changes, which you do not
+want.  "git reset --merge" keeps your local changes.
+
+
 Interrupted workflow::
 +
 Suppose you are interrupted by an urgent fix request while you
index 2921da320d2b84df4d15ec2745e6d94093dd6907..3ccef2f2b3295c6d5e14b8f3a9d354d7b57598da 100644 (file)
@@ -212,6 +212,9 @@ when you run 'git-merge'.
   reflog of the current branch. For example, if you are on the
   branch 'blabla', then '@\{1\}' means the same as 'blabla@\{1\}'.
 
+* The special construct '@\{-<n>\}' means the <n>th branch checked out
+  before the current one.
+
 * A suffix '{caret}' to a revision parameter means the first parent of
   that commit object.  '{caret}<n>' means the <n>th parent (i.e.
   'rev{caret}'
index 66bf3b2fcdccda9cb29d66756b3c20e5a1545d46..14dfb501eb2e34523cd3c1e5e11149a07bac3f46 100644 (file)
@@ -177,14 +177,25 @@ Automating
 
 --suppress-cc::
        Specify an additional category of recipients to suppress the
-       auto-cc of.  'self' will avoid including the sender, 'author' will
-       avoid including the patch author, 'cc' will avoid including anyone
-       mentioned in Cc lines in the patch, 'sob' will avoid including
-       anyone mentioned in Signed-off-by lines, and 'cccmd' will avoid
-       running the --cc-cmd.  'all' will suppress all auto cc values.
-       Default is the value of 'sendemail.suppresscc' configuration value;
-       if that is unspecified, default to 'self' if --suppress-from is
-       specified, as well as 'sob' if --no-signed-off-cc is specified.
+       auto-cc of:
++
+--
+- 'author' will avoid including the patch author
+- 'self' will avoid including the sender
+- 'cc' will avoid including anyone mentioned in Cc lines in the patch header
+  except for self (use 'self' for that).
+- 'ccbody' will avoid including anyone mentioned in Cc lines in the
+  patch body (commit message) except for self (use 'self' for that).
+- 'sob' will avoid including anyone mentioned in Signed-off-by lines except
+   for self (use 'self' for that).
+- 'cccmd' will avoid running the --cc-cmd.
+- 'body' is equivalent to 'sob' + 'ccbody'
+- 'all' will suppress all auto cc values.
+--
++
+Default is the value of 'sendemail.suppresscc' configuration value; if
+that is unspecified, default to 'self' if --suppress-from is
+specified, as well as 'body' if --no-signed-off-cc is specified.
 
 --[no-]suppress-from::
        If this is set, do not add the From: address to the cc: list.
@@ -201,6 +212,22 @@ Automating
 Administering
 ~~~~~~~~~~~~~
 
+--confirm::
+       Confirm just before sending:
++
+--
+- 'always' will always confirm before sending
+- 'never' will never confirm before sending
+- 'cc' will confirm before sending when send-email has automatically
+  added addresses from the patch to the Cc list
+- 'compose' will confirm before sending the first message when using --compose.
+- 'auto' is equivalent to 'cc' + 'compose'
+--
++
+Default is the value of 'sendemail.confirm' configuration value; if that
+is unspecified, default to 'auto' unless any of the suppress options
+have been specified, in which case default to 'compose'.
+
 --dry-run::
        Do everything except actually send the emails.
 
@@ -244,6 +271,11 @@ sendemail.multiedit::
        summary when '--compose' is used). If false, files will be edited one
        after the other, spawning a new editor each time.
 
+sendemail.confirm::
+       Sets the default for whether to confirm before sending. Must be
+       one of 'always', 'never', 'cc', 'compose', or 'auto'. See '--confirm'
+       in the previous section for the meaning of these values.
+
 
 Author
 ------
index 8f7c0e226df8f58712e6a0d78ecec58c96aa2453..42463a955dd3f5dad25f3a45f0eec62d8712f731 100644 (file)
@@ -45,45 +45,16 @@ OPTIONS
        and subsequent lines are indented by `indent2` spaces. `width`,
        `indent1`, and `indent2` default to 76, 6 and 9 respectively.
 
-FILES
------
-
-If a file `.mailmap` exists at the toplevel of the repository,
-it is used to map an author email address to a canonical real name. This
-can be used to coalesce together commits by the same person where their
-name was spelled differently (whether with the same email address or
-not).
-
-Each line in the file consists, in this order, of the canonical real name
-of an author, whitespace, and an email address (enclosed by '<' and '>')
-to map to the name. Use hash '#' for comments, either on their own line,
-or after the email address.
-
-A canonical name may appear in more than one line, associated with
-different email addresses, but it doesn't make sense for a given address
-to appear more than once (if that happens, a later line overrides the
-earlier ones).
-
-So, for example, if your history contains commits by two authors, Jane
-and Joe, whose names appear in the repository under several forms:
-
-------------
-Joe Developer <joe@example.com>
-Joe R. Developer <joe@example.com>
-Jane Doe <jane@example.com>
-Jane Doe <jane@laptop.(none)>
-Jane D. <jane@desktop.(none)>
-------------
-
-Then, supposing Joe wants his middle name initial used, and Jane prefers
-her family name fully spelled out, a proper `.mailmap` file would look like:
-
-------------
-# Note how we don't need an entry for <jane@laptop.(none)>, because the
-# real name of that author is correct already, and coalesced directly.
-Jane Doe <jane@desktop.(none)>
-Joe R. Developer <joe@random.com>
-------------
+
+MAPPING AUTHORS
+---------------
+
+The `.mailmap` feature is used to coalesce together commits by the same
+person in the shortlog, where their name and/or email address was
+spelled differently.
+
+include::mailmap.txt[]
+
 
 Author
 ------
index 8277577a6f896f019793382735a8c0f4a6d5e1c3..7e9ff3762b111d8740a99b676ea516435018f3a8 100644 (file)
@@ -99,12 +99,12 @@ OPTIONS
        will show the revisions given by "git rev-list {caret}master
        topic1 topic2"
 
+-g::
 --reflog[=<n>[,<base>]] [<ref>]::
        Shows <n> most recent ref-log entries for the given
        ref.  If <base> is given, <n> entries going back from
        that entry.  <base> can be specified as count or date.
-       `-g` can be used as a short-hand for this option.  When
-       no explicit <ref> parameter is given, it defaults to the
+       When no explicit <ref> parameter is given, it defaults to the
        current branch (or `HEAD` if it is detached).
 
 Note that --more, --list, --independent and --merge-base options
index 2f207fbbda04c176cedd5a88c88f74e2ac97cf2e..3b8df4467377d73d613f76875c725cbf8544ee77 100644 (file)
@@ -12,7 +12,7 @@ SYNOPSIS
 'git submodule' [--quiet] add [-b branch] [--] <repository> <path>
 'git submodule' [--quiet] status [--cached] [--] [<path>...]
 'git submodule' [--quiet] init [--] [<path>...]
-'git submodule' [--quiet] update [--init] [--] [<path>...]
+'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--] [<path>...]
 'git submodule' [--quiet] summary [--summary-limit <n>] [commit] [--] [<path>...]
 'git submodule' [--quiet] foreach <command>
 'git submodule' [--quiet] sync [--] [<path>...]
@@ -172,6 +172,11 @@ OPTIONS
        (the default). This limit only applies to modified submodules. The
        size is always limited to 1 for added/deleted/typechanged submodules.
 
+-N::
+--no-fetch::
+       This option is only valid for the update command.
+       Don't fetch new objects from the remote site.
+
 <path>...::
        Paths to submodule(s). When specified this will restrict the command
        to only operate on the submodules found at the specified paths.
index 8d0c421b80b5ff110d33583cb1bc5a3b417cad90..cda3389331edd615cab316574e4dedc5cc30bd31 100644 (file)
@@ -92,6 +92,30 @@ COMMANDS
        .git/config file may be specified as an optional command-line
        argument.
 
+--localtime;;
+       Store Git commit times in the local timezone instead of UTC.  This
+       makes 'git-log' (even without --date=local) show the same times
+       that `svn log` would in the local timezone.
+
+This doesn't interfere with interoperating with the Subversion
+repository you cloned from, but if you wish for your local Git
+repository to be able to interoperate with someone else's local Git
+repository, either don't use this option or you should both use it in
+the same local timezone.
+
+--ignore-paths=<regex>;;
+       This allows one to specify Perl regular expression that will
+       cause skipping of all matching paths from checkout from SVN.
+       Examples:
+
+       --ignore-paths="^doc" - skip "doc*" directory for every fetch.
+
+       --ignore-paths="^[^/]+/(?:branches|tags)" - skip "branches"
+           and "tags" of first level directories.
+
+       Regular expression is not persistent, you should specify
+       it every time when fetching.
+
 'clone'::
        Runs 'init' and 'fetch'.  It will automatically create a
        directory based on the basename of the URL passed to it;
@@ -145,6 +169,10 @@ and have no uncommitted changes.
        reused if a user is later given access to an alternate transport
        method (e.g. `svn+ssh://` or `https://`) for commit.
 
+config key: svn-remote.<name>.commiturl
+
+config key: svn.commiturl (overwrites all svn-remote.<name>.commiturl options)
+
        Using this option for any other purpose (don't ask)
        is very strongly discouraged.
 --
@@ -475,6 +503,14 @@ svn-remote.<name>.rewriteRoot::
        the repository with a public http:// or svn:// URL in the
        metadata so users of it will see the public URL.
 
+svn.brokenSymlinkWorkaround::
+This disables potentially expensive checks to workaround broken symlinks
+checked into SVN by broken clients.  Set this option to "false" if you
+track a SVN repository with many empty blobs that are not symlinks.
+This option may be changed while "git-svn" is running and take effect on
+the next revision fetched.  If unset, git-svn assumes this option to be
+"true".
+
 --
 
 Since the noMetadata, rewriteRoot, useSvnsyncProps and useSvmProps
index e44f54302500172257fd9ea394f45707779cbac8..533d18bbd5634bf6197960f546a20772386c824a 100644 (file)
@@ -12,7 +12,7 @@ SYNOPSIS
 'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]
        <name> [<commit> | <object>]
 'git tag' -d <name>...
-'git tag' [-n[<num>]] -l [<pattern>]
+'git tag' [-n[<num>]] -l [--contains <commit>] [<pattern>]
 'git tag' -v <name>...
 
 DESCRIPTION
@@ -68,6 +68,9 @@ OPTIONS
        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.
 
+--contains <commit>::
+       Only list tags which contain the specified commit.
+
 -m <msg>::
        Use the given tag message (instead of prompting).
        If multiple `-m` options are given, their values are
index 17dc8b20192f2558775250436152d0e8acfde41e..9a26bde73e695effce9741558994e7799e81489a 100644 (file)
@@ -43,9 +43,17 @@ unreleased) version of git, that is available from 'master'
 branch of the `git.git` repository.
 Documentation for older releases are available here:
 
-* link:v1.6.1/git.html[documentation for release 1.6.1]
+* link:v1.6.2/git.html[documentation for release 1.6.2]
 
 * release notes for
+  link:RelNotes-1.6.2.txt[1.6.2].
+
+* link:v1.6.1.3/git.html[documentation for release 1.6.1.3]
+
+* release notes for
+  link:RelNotes-1.6.1.3.txt[1.6.1.3],
+  link:RelNotes-1.6.1.2.txt[1.6.1.2],
+  link:RelNotes-1.6.1.1.txt[1.6.1.1],
   link:RelNotes-1.6.1.txt[1.6.1].
 
 * link:v1.6.0.6/git.html[documentation for release 1.6.0.6]
index 8af22eccac85492416d36259baa60594c9b8382f..55668e345f9dd43f2f9850463503cda50f656641 100644 (file)
@@ -18,10 +18,10 @@ A `gitattributes` file is a simple text file that gives
 
 Each line in `gitattributes` file is of form:
 
-       glob    attr1 attr2 ...
+       pattern attr1 attr2 ...
 
-That is, a glob pattern followed by an attributes list,
-separated by whitespaces.  When the glob pattern matches the
+That is, a pattern followed by an attributes list,
+separated by whitespaces.  When the pattern matches the
 path in question, the attributes listed on the line are given to
 the path.
 
@@ -48,13 +48,14 @@ Set to a value::
 
 Unspecified::
 
-       No glob pattern matches the path, and nothing says if
+       No pattern matches the path, and nothing says if
        the path has or does not have the attribute, the
        attribute for the path is said to be Unspecified.
 
-When more than one glob pattern matches the path, a later line
+When more than one pattern matches the path, a later line
 overrides an earlier line.  This overriding is done per
-attribute.
+attribute.  The rules how the pattern matches paths are the
+same as in `.gitignore` files; see linkgit:gitignore[5].
 
 When deciding what attributes are assigned to a path, git
 consults `$GIT_DIR/info/attributes` file (which has the highest
@@ -317,6 +318,8 @@ patterns are available:
 
 - `bibtex` suitable for files with BibTeX coded references.
 
+- `cpp` suitable for source code in the C and C++ languages.
+
 - `html` suitable for HTML/XHTML documents.
 
 - `java` suitable for source code in the Java language.
@@ -334,6 +337,25 @@ patterns are available:
 - `tex` suitable for source code for LaTeX documents.
 
 
+Customizing word diff
+^^^^^^^^^^^^^^^^^^^^^
+
+You can customize the rules that `git diff --color-words` uses to
+split words in a line, by specifying an appropriate regular expression
+in the "diff.*.wordRegex" configuration variable.  For example, in TeX
+a backslash followed by a sequence of letters forms a command, but
+several such commands can be run together without intervening
+whitespace.  To separate them, use a regular expression such as
+
+------------------------
+[diff "tex"]
+       wordRegex = "\\\\[a-zA-Z]+|[{}]|\\\\.|[^\\{}[:space:]]+"
+------------------------
+
+A built-in pattern is provided for all languages listed in the
+previous section.
+
+
 Performing text diffs of binary files
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
index e4dd5518c81ac98ef3da9cbb1cea4a9e1fe6e62c..7ba5e589d7e824c526482c9707a5c26ac730cc9e 100644 (file)
@@ -1243,10 +1243,10 @@ $ git ls-files --stage
 ------------
 
 In our example of only two files, we did not have unchanged
-files so only 'example' resulted in collapsing, but in real-life
-large projects, only small number of files change in one commit,
-and this 'collapsing' tends to trivially merge most of the paths
-fairly quickly, leaving only a handful the real changes in non-zero
+files so only 'example' resulted in collapsing.  But in real-life
+large projects, when only a small number of files change in one commit,
+this 'collapsing' tends to trivially merge most of the paths
+fairly quickly, leaving only a handful of real changes in non-zero
 stages.
 
 To look at only non-zero stages, use `\--unmerged` flag:
index 28a8abcf52b0c4180a132d4093b6b8a59c63c6fd..1fd512bca2e12f5eb0abac0edb0dfb2a86949cec 100644 (file)
@@ -15,7 +15,7 @@ DESCRIPTION
 
 Hooks are little scripts you can place in `$GIT_DIR/hooks`
 directory to trigger action at certain points.  When
-'git-init' is run, a handful example hooks are copied in the
+'git-init' is run, a handful of example hooks are copied into the
 `hooks` directory of the new repository, but by default they are
 all disabled.  To enable a hook, rename it by removing its `.sample`
 suffix.
index 4673a75a98da1f778c1323d8e5a4f7faff388589..cf465cb47e1f3fbb7452568591874cf865a16c2c 100644 (file)
@@ -47,7 +47,8 @@ frequently used options.
 
        After an attempt to merge stops with conflicts, show the commits on
        the history between two branches (i.e. the HEAD and the MERGE_HEAD)
-       that modify the conflicted files.
+       that modify the conflicted files and do not exist on all the heads
+       being merged.
 
 --argscmd=<command>::
        Command to be run each time gitk has to determine the list of
@@ -73,7 +74,7 @@ frequently used options.
 <path>...::
 
        Limit commits to the ones touching files in the given paths. Note, to
-       avoid ambiguity wrt. revision names use "--" to separate the paths
+       avoid ambiguity with respect to revision names use "--" to separate the paths
        from any preceding options.
 
 Examples
index a057b50b2bbfe9740cbd3dc5f112f0d8c3d7d60b..dc8fc3a18a5e613da910c6bd5a1dda9c85c3f5b1 100644 (file)
@@ -32,12 +32,12 @@ Initialized empty Git repository in .git/
 $ echo 'hello world' > file.txt
 $ git add .
 $ git commit -a -m "initial commit"
-[master (root-commit)] created 54196cc: "initial commit"
+[master (root-commit) 54196cc] initial commit
  1 files changed, 1 insertions(+), 0 deletions(-)
  create mode 100644 file.txt
 $ echo 'hello world!' >file.txt
 $ git commit -a -m "add emphasis"
-[master] created c4d59f3: "add emphasis"
+[master c4d59f3] add emphasis
  1 files changed, 1 insertions(+), 1 deletions(-)
 ------------------------------------------------
 
index 458fafdb2cc7e54c5e2f49ca5854e6ec9fe581ee..c5d5596d895755d69c8060bc38a800d7c70eda26 100644 (file)
@@ -308,9 +308,7 @@ alice$ git pull /home/bob/myrepo master
 
 This merges the changes from Bob's "master" branch into Alice's
 current branch.  If Alice has made her own changes in the meantime,
-then she may need to manually fix any conflicts.  (Note that the
-"master" argument in the above command is actually unnecessary, as it
-is the default.)
+then she may need to manually fix any conflicts.
 
 The "pull" command thus performs two operations: it fetches changes
 from a remote branch, then merges them into the current branch.
index d214d4bf9d0e539c6bf58ba24dcd12aabbaea1d8..74a1c0c4ba3a03ba02cbbabcf0ee6cff22e4b099 100644 (file)
@@ -27,7 +27,7 @@ the kind of task StGIT is designed to do.
 I just have done a simpler one, this time using only the core
 GIT tools.
 
-I had a handful commits that were ahead of master in pu, and I
+I had a handful of commits that were ahead of master in pu, and I
 wanted to add some documentation bypassing my usual habit of
 placing new things in pu first.  At the beginning, the commit
 ancestry graph looked like this:
index 39b1da440a6f80a298ee50af0f1068f45a1565c1..3b4a390005b07c86ee320ee8ca1cf57e46458cb6 100644 (file)
@@ -39,7 +39,7 @@ Such a "revert" of a merge can be made with:
 
     $ git revert -m 1 M
 
-After the develpers of the side branch fixes their mistakes, the history
+After the developers of the side branch fix their mistakes, the history
 may look like this:
 
  ---o---o---o---M---x---x---W---x
@@ -116,7 +116,7 @@ If you reverted the revert in such a case as in the previous example:
               /                 \         /
        ---A---B                   A'--B'--C'
 
-where Y is the revert of W, A' and B'are rerolled A and B, and there may
+where Y is the revert of W, A' and B' are rerolled A and B, and there may
 also be a further fix-up C' on the side branch.  "diff Y^..Y" is similar
 to "diff -R W^..W" (which in turn means it is similar to "diff M^..M"),
 and "diff A'^..C'" by definition would be similar but different from that,
index 40327486084ac02874faff70fd100b619af83214..622ee5c8dd7c384794a21baa6093d85a47f89a54 100644 (file)
@@ -143,7 +143,7 @@ Then, add something like this to your httpd.conf
        Require valid-user
     </Location>
 
-    Debian automatically reads all files under /etc/apach2/conf.d.
+    Debian automatically reads all files under /etc/apache2/conf.d.
 
 The password file can be somewhere else, but it has to be readable by
 Apache and preferably not readable by the world.
diff --git a/Documentation/mailmap.txt b/Documentation/mailmap.txt
new file mode 100644 (file)
index 0000000..e25b154
--- /dev/null
@@ -0,0 +1,75 @@
+If the file `.mailmap` exists at the toplevel of the repository, or at
+the location pointed to by the mailmap.file configuration option, it
+is used to map author and committer names and email addresses to
+canonical real names and email addresses.
+
+In the simple form, each line in the file consists of the canonical
+real name of an author, whitespace, and an email address used in the
+commit (enclosed by '<' and '>') to map to the name. Thus, looks like
+this
+--
+       Proper Name <commit@email.xx>
+--
+
+The more complex forms are
+--
+       <proper@email.xx> <commit@email.xx>
+--
+which allows mailmap to replace only the email part of a commit, and
+--
+       Proper Name <proper@email.xx> <commit@email.xx>
+--
+which allows mailmap to replace both the name and the email of a
+commit matching the specified commit email address, and
+--
+       Proper Name <proper@email.xx> Commit Name <commit@email.xx>
+--
+which allows mailmap to replace both the name and the email of a
+commit matching both the specified commit name and email address.
+
+Example 1: Your history contains commits by two authors, Jane
+and Joe, whose names appear in the repository under several forms:
+
+------------
+Joe Developer <joe@example.com>
+Joe R. Developer <joe@example.com>
+Jane Doe <jane@example.com>
+Jane Doe <jane@laptop.(none)>
+Jane D. <jane@desktop.(none)>
+------------
+
+Now suppose that Joe wants his middle name initial used, and Jane
+prefers her family name fully spelled out. A proper `.mailmap` file
+would look like:
+
+------------
+Jane Doe         <jane@desktop.(none)>
+Joe R. Developer <joe@example.com>
+------------
+
+Note how we don't need an entry for <jane@laptop.(none)>, because the
+real name of that author is correct already.
+
+Example 2: Your repository contains commits from the following
+authors:
+
+------------
+nick1 <bugs@company.xx>
+nick2 <bugs@company.xx>
+nick2 <nick2@company.xx>
+santa <me@company.xx>
+claus <me@company.xx>
+CTO <cto@coompany.xx>
+------------
+
+Then, you might want a `.mailmap` file looking like:
+------------
+<cto@company.xx>                       <cto@coompany.xx>
+Some Dude <some@dude.xx>         nick1 <bugs@company.xx>
+Other Author <other@author.xx>   nick2 <bugs@company.xx>
+Other Author <other@author.xx>         <nick2@company.xx>
+Santa Claus <santa.claus@northpole.xx> <me@company.xx>
+------------
+
+Use hash '#' for comments that are either on their own line, or after
+the email address.
\ No newline at end of file
index 0a8a948e6ffba9d15b5db03399f74ed0139394ea..5c6e678aa3d9e75570d5a5578e38b0a45bae720e 100644 (file)
@@ -101,16 +101,18 @@ The placeholders are:
 - '%P': parent hashes
 - '%p': abbreviated parent hashes
 - '%an': author name
-- '%aN': author name (respecting .mailmap)
+- '%aN': author name (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
 - '%ae': author email
+- '%aE': author email (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
 - '%ad': author date (format respects --date= option)
 - '%aD': author date, RFC2822 style
 - '%ar': author date, relative
 - '%at': author date, UNIX timestamp
 - '%ai': author date, ISO 8601 format
 - '%cn': committer name
-- '%cN': committer name (respecting .mailmap)
+- '%cN': committer name (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
 - '%ce': committer email
+- '%cE': committer email (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
 - '%cd': committer date
 - '%cD': committer date, RFC2822 style
 - '%cr': committer date, relative
@@ -124,6 +126,7 @@ The placeholders are:
 - '%Cgreen': switch color to green
 - '%Cblue': switch color to blue
 - '%Creset': reset color
+- '%C(...)': color specification, as described in color.branch.* config option
 - '%m': left, right or boundary mark
 - '%n': newline
 - '%x00': print a byte from a hex code
@@ -149,3 +152,12 @@ $ git log -2 --pretty=tformat:%h 4da45bef \
 4da45be
 7134973
 ---------------------
++
+In addition, any unrecognized string that has a `%` in it is interpreted
+as if it has `tformat:` in front of it.  For example, these two are
+equivalent:
++
+---------------------
+$ git log -2 --pretty=tformat:%h 4da45bef
+$ git log -2 --pretty=%h 4da45bef
+---------------------
index 6d66c74cc11e6622892061f8328d04dfe38f87bf..bff94991b68aaca5a81eae4e6681f3431aa6b9ac 100644 (file)
@@ -1,4 +1,5 @@
 --pretty[='<format>']::
+--format[='<format>']::
 
        Pretty-print the contents of the commit logs in a given format,
        where '<format>' can be one of 'oneline', 'short', 'medium',
@@ -10,13 +11,17 @@ configuration (see linkgit:git-config[1]).
 
 --abbrev-commit::
        Instead of showing the full 40-byte hexadecimal commit object
-       name, show only handful hexdigits prefix.  Non default number of
+       name, show only a partial prefix.  Non default number of
        digits can be specified with "--abbrev=<n>" (which also modifies
        diff output, if it is displayed).
 +
 This should make "--pretty=oneline" a whole lot more readable for
 people using 80-column terminals.
 
+--oneline::
+       This is a shorthand for "--pretty=oneline --abbrev-commit"
+       used together.
+
 --encoding[=<encoding>]::
        The commit objects record the encoding used for the log message
        in their encoding header; this option can be used to tell the
index b9f6e4d1b7564480fac9c4e355fdc4936f6fa3db..7dd237c2f66d81aebf59add2787aa822c94d9b92 100644 (file)
@@ -13,7 +13,7 @@ include::pretty-options.txt[]
 
        Synonym for `--date=relative`.
 
---date={relative,local,default,iso,rfc,short}::
+--date={relative,local,default,iso,rfc,short,raw}::
 
        Only takes effect for dates shown in human-readable format, such
        as when using "--pretty". `log.date` config variable sets a default
@@ -31,6 +31,8 @@ format, often found in E-mail messages.
 +
 `--date=short` shows only date but not time, in `YYYY-MM-DD` format.
 +
+`--date=raw` shows the date in the internal raw git format `%s %z` format.
++
 `--date=default` shows timestamps in the original timezone
 (either committer's or author's).
 
@@ -566,11 +568,11 @@ This outputs all the commit objects between the included and excluded
 commits, ordered by their distance to the included and excluded
 commits. The farthest from them is displayed first. (This is the only
 one displayed by `--bisect`.)
-
++
 This is useful because it makes it easy to choose a good commit to
 test when you want to avoid to test some of them for some reason (they
 may not compile for example).
-
++
 This option can be used along with `--bisect-vars`, in this case,
 after all the sorted commit objects, there will be the same text as if
 `--bisect-vars` had been used alone.
index 539863b1f920f8f34ad9272907cbacbd35a7fcbd..e66ca9f70c705841c864196a3a651e04d8156e01 100644 (file)
@@ -66,6 +66,12 @@ Steps to parse options
 non-option arguments in `argv[]`.
 `argc` is updated appropriately because of the assignment.
 +
+You can also pass NULL instead of a usage array as fourth parameter of
+parse_options(), to avoid displaying a help screen with usage info and
+option list.  This should only be done if necessary, e.g. to implement
+a limited parser for only a subset of the options that needs to be run
+before the full parser, which in turn shows the full help message.
++
 Flags are the bitwise-or of:
 
 `PARSE_OPT_KEEP_DASHDASH`::
@@ -77,6 +83,28 @@ Flags are the bitwise-or of:
        Using this flag, processing is stopped at the first non-option
        argument.
 
+`PARSE_OPT_KEEP_ARGV0`::
+       Keep the first argument, which contains the program name.  It's
+       removed from argv[] by default.
+
+`PARSE_OPT_KEEP_UNKNOWN`::
+       Keep unknown arguments instead of erroring out.  This doesn't
+       work for all combinations of arguments as users might expect
+       it to do.  E.g. if the first argument in `--unknown --known`
+       takes a value (which we can't know), the second one is
+       mistakenly interpreted as a known option.  Similarly, if
+       `PARSE_OPT_STOP_AT_NON_OPTION` is set, the second argument in
+       `--unknown value` will be mistakenly interpreted as a
+       non-option, not as a value belonging to the unknown option,
+       the parser early.  That's why parse_options() errors out if
+       both options are set.
+
+`PARSE_OPT_NO_INTERNAL_HELP`::
+       By default, parse_options() handles `-h`, `--help` and
+       `--help-all` internally, by showing a help screen.  This option
+       turns it off and allows one to add custom handlers for these
+       options, or to just leave them unknown.
+
 Data Structure
 --------------
 
index 82e9e831b6a97dfaa62c02d30447415ef65e4fa3..2efe7a40be641bc2532c139637fe02e534ea1152 100644 (file)
@@ -52,6 +52,21 @@ Functions
        Wait for the completion of an asynchronous function that was
        started with start_async().
 
+`run_hook`::
+
+       Run a hook.
+       The first argument is a pathname to an index file, or NULL
+       if the hook uses the default index file or no index is needed.
+       The second argument is the name of the hook.
+       The further arguments correspond to the hook arguments.
+       The last argument has to be NULL to terminate the arguments list.
+       If the hook does not exist or is not executable, the return
+       value will be zero.
+       If it is executable, the hook will be executed and the exit
+       status of the hook is returned.
+       On execution, .stdout_to_stderr and .no_stdin will be set.
+       (See below.)
+
 
 Data structures
 ---------------
index 985800e43a9b91256c35df60f67c36994142b94c..7438149249364ca8837811771b072b20990b3a5d 100644 (file)
@@ -133,8 +133,10 @@ Functions
 
 * Adding data to the buffer
 
-NOTE: All of these functions in this section will grow the buffer as
-      necessary.
+NOTE: All of the functions in this section will grow the buffer as necessary.
+If they fail for some reason other than memory shortage and the buffer hadn't
+been allocated before (i.e. the `struct strbuf` was set to `STRBUF_INIT`),
+then they will free() it.
 
 `strbuf_addch`::
 
@@ -220,7 +222,7 @@ which can be used by the programmer of the callback as she sees fit.
 
        Read a given size of data from a FILE* pointer to the buffer.
 +
-NOTE: The buffer is rewinded if the read fails. If -1 is returned,
+NOTE: The buffer is rewound if the read fails. If -1 is returned,
 `errno` must be consulted, like you would do for `read(3)`.
 `strbuf_read()`, `strbuf_read_file()` and `strbuf_getline()` has the
 same behaviour as well.
@@ -235,6 +237,11 @@ same behaviour as well.
        Read the contents of a file, specified by its path. The third argument
        can be used to give a hint about the file size, to avoid reallocs.
 
+`strbuf_readlink`::
+
+       Read the target of a symbolic link, specified by its path.  The third
+       argument can be used to give a hint about the size, to avoid reallocs.
+
 `strbuf_getline`::
 
        Read a line from a FILE* pointer. The second argument specifies the line
index 550a0ae3710dd1d4a831185c1cffadd65a9f2c47..97fc1e0519936111beb295cbb345aa206cdd8893 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.6.1.3.GIT
+DEF_VER=v1.6.2.GIT
 
 LF='
 '
diff --git a/INSTALL b/INSTALL
index d1deb0b3c7a9aba961d40fe541490562fee486ac..ae7f7508f8e8cffeb930c820e068ba70dabff7bd 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -101,6 +101,9 @@ Issues of note:
    Building and installing the info file additionally requires
    makeinfo and docbook2X.  Version 0.8.3 is known to work.
 
+   Building and installing the pdf file additionally requires
+   dblatex.  Version 0.2.7 with asciidoc >= 8.2.7 is known to work.
+
    The documentation is written for AsciiDoc 7, but "make
    ASCIIDOC8=YesPlease doc" will let you format with AsciiDoc 8.
 
index 01242889eb888d35e28249a78d54584ebca4bb24..1087884be691ed2e9a59dd6289b809496538bf8e 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -23,6 +23,9 @@ all::
 # 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.
 #
+# Define EXPATDIR=/foo/bar if your expat header and library files are in
+# /foo/bar/include and /foo/bar/lib directories.
+#
 # Define NO_D_INO_IN_DIRENT if you don't have d_ino in your struct dirent.
 #
 # Define NO_D_TYPE_IN_DIRENT if your platform defines DT_UNKNOWN but lacks
@@ -179,28 +182,32 @@ STRIP ?= strip
 # Among the variables below, these:
 #   gitexecdir
 #   template_dir
+#   mandir
+#   infodir
 #   htmldir
 #   ETC_GITCONFIG (but not sysconfdir)
-# can be specified as a relative path ../some/where/else (which must begin
-# with ../); this is interpreted as relative to $(bindir) and "git" at
+# can be specified as a relative path some/where/else;
+# this is interpreted as relative to $(prefix) and "git" at
 # runtime figures out where they are based on the path to the executable.
 # This can help installing the suite in a relocatable way.
 
 prefix = $(HOME)
-bindir = $(prefix)/bin
-mandir = $(prefix)/share/man
-infodir = $(prefix)/share/info
-gitexecdir = $(prefix)/libexec/git-core
+bindir_relative = bin
+bindir = $(prefix)/$(bindir_relative)
+mandir = share/man
+infodir = share/info
+gitexecdir = libexec/git-core
 sharedir = $(prefix)/share
-template_dir = $(sharedir)/git-core/templates
-htmldir=$(sharedir)/doc/git-doc
+template_dir = share/git-core/templates
+htmldir = share/doc/git-doc
 ifeq ($(prefix),/usr)
 sysconfdir = /etc
+ETC_GITCONFIG = $(sysconfdir)/gitconfig
 else
 sysconfdir = $(prefix)/etc
+ETC_GITCONFIG = etc/gitconfig
 endif
 lib = lib
-ETC_GITCONFIG = $(sysconfdir)/gitconfig
 # DESTDIR=
 
 # default configuration for gitweb
@@ -221,7 +228,7 @@ GITWEB_FAVICON = git-favicon.png
 GITWEB_SITE_HEADER =
 GITWEB_SITE_FOOTER =
 
-export prefix bindir sharedir htmldir sysconfdir
+export prefix bindir sharedir sysconfdir
 
 CC = gcc
 AR = ar
@@ -310,8 +317,8 @@ PROGRAMS += git-var$X
 # builtin-$C.o but is linked in as part of some other command.
 BUILT_INS += $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
 
-BUILT_INS += git-cherry-pick$X
 BUILT_INS += git-cherry$X
+BUILT_INS += git-cherry-pick$X
 BUILT_INS += git-format-patch$X
 BUILT_INS += git-fsck-objects$X
 BUILT_INS += git-get-tar-commit-id$X
@@ -350,8 +357,8 @@ LIB_H += builtin.h
 LIB_H += cache.h
 LIB_H += cache-tree.h
 LIB_H += commit.h
-LIB_H += compat/mingw.h
 LIB_H += compat/cygwin.h
+LIB_H += compat/mingw.h
 LIB_H += csum-file.h
 LIB_H += decorate.h
 LIB_H += delta.h
@@ -376,7 +383,6 @@ LIB_H += pack-refs.h
 LIB_H += pack-revindex.h
 LIB_H += parse-options.h
 LIB_H += patch-ids.h
-LIB_H += string-list.h
 LIB_H += pkt-line.h
 LIB_H += progress.h
 LIB_H += quote.h
@@ -388,7 +394,9 @@ LIB_H += revision.h
 LIB_H += run-command.h
 LIB_H += sha1-lookup.h
 LIB_H += sideband.h
+LIB_H += sigchain.h
 LIB_H += strbuf.h
+LIB_H += string-list.h
 LIB_H += tag.h
 LIB_H += transport.h
 LIB_H += tree.h
@@ -427,8 +435,8 @@ LIB_OBJS += diffcore-order.o
 LIB_OBJS += diffcore-pickaxe.o
 LIB_OBJS += diffcore-rename.o
 LIB_OBJS += diff-delta.o
-LIB_OBJS += diff-no-index.o
 LIB_OBJS += diff-lib.o
+LIB_OBJS += diff-no-index.o
 LIB_OBJS += diff.o
 LIB_OBJS += dir.o
 LIB_OBJS += editor.o
@@ -460,9 +468,9 @@ LIB_OBJS += pager.o
 LIB_OBJS += parse-options.o
 LIB_OBJS += patch-delta.o
 LIB_OBJS += patch-ids.o
-LIB_OBJS += string-list.o
 LIB_OBJS += path.o
 LIB_OBJS += pkt-line.o
+LIB_OBJS += preload-index.o
 LIB_OBJS += pretty.o
 LIB_OBJS += progress.o
 LIB_OBJS += quote.o
@@ -476,12 +484,14 @@ LIB_OBJS += revision.o
 LIB_OBJS += run-command.o
 LIB_OBJS += server-info.o
 LIB_OBJS += setup.o
-LIB_OBJS += sha1_file.o
 LIB_OBJS += sha1-lookup.o
+LIB_OBJS += sha1_file.o
 LIB_OBJS += sha1_name.o
 LIB_OBJS += shallow.o
 LIB_OBJS += sideband.o
+LIB_OBJS += sigchain.o
 LIB_OBJS += strbuf.o
+LIB_OBJS += string-list.o
 LIB_OBJS += symlinks.o
 LIB_OBJS += tag.o
 LIB_OBJS += trace.o
@@ -490,8 +500,8 @@ LIB_OBJS += tree-diff.o
 LIB_OBJS += tree.o
 LIB_OBJS += tree-walk.o
 LIB_OBJS += unpack-trees.o
-LIB_OBJS += userdiff.o
 LIB_OBJS += usage.o
+LIB_OBJS += userdiff.o
 LIB_OBJS += utf8.o
 LIB_OBJS += walker.o
 LIB_OBJS += wrapper.o
@@ -499,7 +509,6 @@ LIB_OBJS += write_or_die.o
 LIB_OBJS += ws.o
 LIB_OBJS += wt-status.o
 LIB_OBJS += xdiff-interface.o
-LIB_OBJS += preload-index.o
 
 BUILTIN_OBJS += builtin-add.o
 BUILTIN_OBJS += builtin-annotate.o
@@ -640,10 +649,12 @@ endif
 ifeq ($(uname_S),Darwin)
        NEEDS_SSL_WITH_CRYPTO = YesPlease
        NEEDS_LIBICONV = YesPlease
-       ifneq ($(shell expr "$(uname_R)" : '9\.'),2)
+       ifeq ($(shell expr "$(uname_R)" : '[15678]\.'),2)
                OLD_ICONV = UnfortunatelyYes
        endif
-       NO_STRLCPY = YesPlease
+       ifeq ($(shell expr "$(uname_R)" : '[15]\.'),2)
+               NO_STRLCPY = YesPlease
+       endif
        NO_MEMMEM = YesPlease
        THREADED_DELTA_SEARCH = YesPlease
 endif
@@ -785,6 +796,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
        SNPRINTF_RETURNS_BOGUS = YesPlease
        NO_SVN_TESTS = YesPlease
        NO_PERL_MAKEMAKER = YesPlease
+       RUNTIME_PREFIX = YesPlease
        NO_POSIX_ONLY_PROGRAMS = YesPlease
        NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
        COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/regex -Icompat/fnmatch
@@ -793,9 +805,6 @@ ifneq (,$(findstring MINGW,$(uname_S)))
        COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/regex/regex.o compat/winansi.o
        EXTLIBS += -lws2_32
        X = .exe
-       gitexecdir = ../libexec/git-core
-       template_dir = ../share/git-core/templates/
-       ETC_GITCONFIG = ../etc/gitconfig
 endif
 ifneq (,$(findstring arm,$(uname_M)))
        ARM_SHA1 = YesPlease
@@ -817,6 +826,7 @@ ifeq ($(uname_S),Darwin)
                        BASIC_LDFLAGS += -L/opt/local/lib
                endif
        endif
+       PTHREAD_LIBS =
 endif
 
 ifndef CC_LD_DYNPATH
@@ -849,7 +859,12 @@ else
                endif
        endif
        ifndef NO_EXPAT
-               EXPAT_LIBEXPAT = -lexpat
+               ifdef EXPATDIR
+                       BASIC_CFLAGS += -I$(EXPATDIR)/include
+                       EXPAT_LIBEXPAT = -L$(EXPATDIR)/$(lib) $(CC_LD_DYNPATH)$(EXPATDIR)/$(lib) -lexpat
+               else
+                       EXPAT_LIBEXPAT = -lexpat
+               endif
        endif
 endif
 
@@ -1027,6 +1042,9 @@ ifdef INTERNAL_QSORT
        COMPAT_CFLAGS += -DINTERNAL_QSORT
        COMPAT_OBJS += compat/qsort.o
 endif
+ifdef RUNTIME_PREFIX
+       COMPAT_CFLAGS += -DRUNTIME_PREFIX
+endif
 
 ifdef NO_PTHREADS
        THREADED_DELTA_SEARCH =
@@ -1086,6 +1104,7 @@ ETC_GITCONFIG_SQ = $(subst ','\'',$(ETC_GITCONFIG))
 
 DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
 bindir_SQ = $(subst ','\'',$(bindir))
+bindir_relative_SQ = $(subst ','\'',$(bindir_relative))
 mandir_SQ = $(subst ','\'',$(mandir))
 infodir_SQ = $(subst ','\'',$(infodir))
 gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
@@ -1251,7 +1270,12 @@ git.o git.spec \
        $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
 
 exec_cmd.o: exec_cmd.c GIT-CFLAGS
-       $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' $<
+       $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) \
+               '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \
+               '-DBINDIR="$(bindir_relative_SQ)"' \
+               '-DPREFIX="$(prefix_SQ)"' \
+               $<
+
 builtin-init-db.o: builtin-init-db.c GIT-CFLAGS
        $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $<
 
@@ -1287,7 +1311,7 @@ $(LIB_FILE): $(LIB_OBJS)
        $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIB_OBJS)
 
 XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \
-       xdiff/xmerge.o
+       xdiff/xmerge.o xdiff/xpatience.o
 $(XDIFF_OBJS): xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \
        xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h
 
@@ -1307,6 +1331,9 @@ html:
 info:
        $(MAKE) -C Documentation info
 
+pdf:
+       $(MAKE) -C Documentation pdf
+
 TAGS:
        $(RM) TAGS
        $(FIND) . -name '*.[hcS]' -print | xargs etags -a
@@ -1353,7 +1380,17 @@ endif
 
 ### Testing rules
 
-TEST_PROGRAMS = test-chmtime$X test-dump-cache-tree$X test-genrandom$X test-date$X test-delta$X test-sha1$X test-match-trees$X test-parse-options$X test-path-utils$X
+TEST_PROGRAMS += test-chmtime$X
+TEST_PROGRAMS += test-ctype$X
+TEST_PROGRAMS += test-date$X
+TEST_PROGRAMS += test-delta$X
+TEST_PROGRAMS += test-dump-cache-tree$X
+TEST_PROGRAMS += test-genrandom$X
+TEST_PROGRAMS += test-match-trees$X
+TEST_PROGRAMS += test-parse-options$X
+TEST_PROGRAMS += test-path-utils$X
+TEST_PROGRAMS += test-sha1$X
+TEST_PROGRAMS += test-sigchain$X
 
 all:: $(TEST_PROGRAMS)
 
@@ -1366,6 +1403,8 @@ export NO_SVN_TESTS
 test: all
        $(MAKE) -C t/ all
 
+test-ctype$X: ctype.o
+
 test-date$X: date.o ctype.o
 
 test-delta$X: diff-delta.o patch-delta.o
@@ -1397,17 +1436,17 @@ remove-dashes:
 
 ### Installation rules
 
-ifeq ($(firstword $(subst /, ,$(template_dir))),..)
-template_instdir = $(bindir)/$(template_dir)
-else
+ifneq ($(filter /%,$(firstword $(template_dir))),)
 template_instdir = $(template_dir)
+else
+template_instdir = $(prefix)/$(template_dir)
 endif
 export template_instdir
 
-ifeq ($(firstword $(subst /, ,$(gitexecdir))),..)
-gitexec_instdir = $(bindir)/$(gitexecdir)
-else
+ifneq ($(filter /%,$(firstword $(gitexecdir))),)
 gitexec_instdir = $(gitexecdir)
+else
+gitexec_instdir = $(prefix)/$(gitexecdir)
 endif
 gitexec_instdir_SQ = $(subst ','\'',$(gitexec_instdir))
 export gitexec_instdir
@@ -1429,12 +1468,14 @@ endif
        bindir=$$(cd '$(DESTDIR_SQ)$(bindir_SQ)' && pwd) && \
        execdir=$$(cd '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' && pwd) && \
        { $(RM) "$$execdir/git-add$X" && \
-               ln git-add$X "$$execdir/git-add$X" 2>/dev/null || \
-               cp git-add$X "$$execdir/git-add$X"; } && \
-       { $(foreach p,$(filter-out git-add$X,$(BUILT_INS)), $(RM) "$$execdir/$p" && \
-               ln "$$execdir/git-add$X" "$$execdir/$p" 2>/dev/null || \
-               ln -s "git-add$X" "$$execdir/$p" 2>/dev/null || \
-               cp "$$execdir/git-add$X" "$$execdir/$p" || exit;) } && \
+               ln "$$bindir/git$X" "$$execdir/git-add$X" 2>/dev/null || \
+               cp "$$bindir/git$X" "$$execdir/git-add$X"; } && \
+       { for p in $(filter-out git-add$X,$(BUILT_INS)); do \
+               $(RM) "$$execdir/$$p" && \
+               ln "$$execdir/git-add$X" "$$execdir/$$p" 2>/dev/null || \
+               ln -s "git-add$X" "$$execdir/$$p" 2>/dev/null || \
+               cp "$$execdir/git-add$X" "$$execdir/$$p" || exit; \
+         done } && \
        ./check_bindir "z$$bindir" "z$$execdir" "$$bindir/git-add$X"
 
 install-doc:
@@ -1449,6 +1490,9 @@ install-html:
 install-info:
        $(MAKE) -C Documentation install-info
 
+install-pdf:
+       $(MAKE) -C Documentation install-pdf
+
 quick-install-doc:
        $(MAKE) -C Documentation quick-install
 
@@ -1596,3 +1640,27 @@ check-docs::
 check-builtins::
        ./check-builtins.sh
 
+### Test suite coverage testing
+#
+.PHONY: coverage coverage-clean coverage-build coverage-report
+
+coverage:
+       $(MAKE) coverage-build
+       $(MAKE) coverage-report
+
+coverage-clean:
+       rm -f *.gcda *.gcno
+
+COVERAGE_CFLAGS = $(CFLAGS) -O0 -ftest-coverage -fprofile-arcs
+COVERAGE_LDFLAGS = $(CFLAGS)  -O0 -lgcov
+
+coverage-build: coverage-clean
+       $(MAKE) CFLAGS="$(COVERAGE_CFLAGS)" LDFLAGS="$(COVERAGE_LDFLAGS)" all
+       $(MAKE) CFLAGS="$(COVERAGE_CFLAGS)" LDFLAGS="$(COVERAGE_LDFLAGS)" \
+               -j1 test
+
+coverage-report:
+       gcov -b *.c
+       grep '^function.*called 0 ' *.c.gcov \
+               | sed -e 's/\([^:]*\)\.gcov: *function \([^ ]*\) called.*/\1: \2/' \
+               | tee coverage-untested-functions
diff --git a/README b/README
index 5fa41b7a18942a68bacb7b488984bdf98f6dfd1a..c932ab310510c84c3e69107b458a228db37cb1f6 100644 (file)
--- a/README
+++ b/README
@@ -24,10 +24,18 @@ It was originally written by Linus Torvalds with help of a group of
 hackers around the net. It is currently maintained by Junio C Hamano.
 
 Please read the file INSTALL for installation instructions.
+
 See Documentation/gittutorial.txt to get started, then see
-Documentation/everyday.txt for a useful minimum set of commands,
-and "man git-commandname" for documentation of each command.
-CVS users may also want to read Documentation/cvs-migration.txt.
+Documentation/everyday.txt for a useful minimum set of commands, and
+Documentation/git-commandname.txt for documentation of each command.
+If git has been correctly installed, then the tutorial can also be
+read with "man gittutorial" or "git help tutorial", and the
+documentation of each command with "man git-commandname" or "git help
+commandname".
+
+CVS users may also want to read Documentation/gitcvs-migration.txt
+("man gitcvs-migration" or "git help cvs-migration" if git is
+installed).
 
 Many Git online resources are accessible from http://git.or.cz/
 including full documentation and Git related tools.
index 56936add354167e7dac8eb4f8b053572c92c47f8..dd8bc4bb4ab75e1148cb9b7aaf8ff7384ebb19ec 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes-1.6.1.4.txt
\ No newline at end of file
+Documentation/RelNotes-1.6.3.txt
\ No newline at end of file
index 9ac455d889b72deba8c949da1d9efe2be3a50244..96b62d4309a3bd4198bb035f266a1326fed61d10 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -132,7 +132,7 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,
                err = write_entry(args, sha1, path.buf, path.len, mode, NULL, 0);
                if (err)
                        return err;
-               return READ_TREE_RECURSIVE;
+               return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);
        }
 
        buffer = sha1_file_to_archive(path_without_prefix, sha1, mode,
@@ -253,6 +253,7 @@ static int parse_archive_args(int argc, const char **argv,
        const char *base = NULL;
        const char *remote = NULL;
        const char *exec = NULL;
+       const char *output = NULL;
        int compression_level = -1;
        int verbose = 0;
        int i;
@@ -262,6 +263,8 @@ static int parse_archive_args(int argc, const char **argv,
                OPT_STRING(0, "format", &format, "fmt", "archive format"),
                OPT_STRING(0, "prefix", &base, "prefix",
                        "prepend prefix to each pathname in the archive"),
+               OPT_STRING(0, "output", &output, "file",
+                       "write the archive to this file"),
                OPT__VERBOSE(&verbose),
                OPT__COMPR('0', &compression_level, "store only", 0),
                OPT__COMPR('1', &compression_level, "compress faster", 1),
@@ -290,6 +293,8 @@ static int parse_archive_args(int argc, const char **argv,
                die("Unexpected option --remote");
        if (exec)
                die("Option --exec can only be used together with --remote");
+       if (output)
+               die("Unexpected option --output");
 
        if (!base)
                base = "";
index b1ac837f3d30c826ddbe29492b906bf2d0de0a1a..1f00e44deb28afe74ae4f0b85b23039476032fe5 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -103,14 +103,22 @@ void create_branch(const char *head,
        struct ref_lock *lock;
        struct commit *commit;
        unsigned char sha1[20];
-       char *real_ref, ref[PATH_MAX], msg[PATH_MAX + 20];
+       char *real_ref, msg[PATH_MAX + 20];
+       struct strbuf ref = STRBUF_INIT;
        int forcing = 0;
+       int len;
 
-       snprintf(ref, sizeof ref, "refs/heads/%s", name);
-       if (check_ref_format(ref))
+       len = strlen(name);
+       if (interpret_nth_last_branch(name, &ref) != len) {
+               strbuf_reset(&ref);
+               strbuf_add(&ref, name, len);
+       }
+       strbuf_splice(&ref, 0, 0, "refs/heads/", 11);
+
+       if (check_ref_format(ref.buf))
                die("'%s' is not a valid branch name.", name);
 
-       if (resolve_ref(ref, sha1, 1, NULL)) {
+       if (resolve_ref(ref.buf, sha1, 1, NULL)) {
                if (!force)
                        die("A branch named '%s' already exists.", name);
                else if (!is_bare_repository() && !strcmp(head, name))
@@ -142,7 +150,7 @@ void create_branch(const char *head,
                die("Not a valid branch point: '%s'.", start_name);
        hashcpy(sha1, commit->object.sha1);
 
-       lock = lock_any_ref_for_update(ref, NULL, 0);
+       lock = lock_any_ref_for_update(ref.buf, NULL, 0);
        if (!lock)
                die("Failed to lock ref for update: %s.", strerror(errno));
 
@@ -162,6 +170,7 @@ void create_branch(const char *head,
        if (write_ref_sha1(lock, sha1, msg) < 0)
                die("Failed to write ref: %s.", strerror(errno));
 
+       strbuf_release(&ref);
        free(real_ref);
 }
 
index ac98c8354d84a7f556c22c53fbe9007832ac4346..08443f2f1ecf7d9edd21cec11fa74548c3326df5 100644 (file)
@@ -15,7 +15,7 @@ static const char * const builtin_add_usage[] = {
        "git add [options] [--] <filepattern>...",
        NULL
 };
-static int patch_interactive = 0, add_interactive = 0;
+static int patch_interactive, add_interactive;
 static int take_worktree_changes;
 
 static void fill_pathspec_matches(const char **pathspec, char *seen, int specs)
index 58d998577e6d4148ddead9e57bdb0999ff026f73..f312798af38553e0badeda9732736a62460eae05 100644 (file)
@@ -14,6 +14,7 @@
 #include "builtin.h"
 #include "string-list.h"
 #include "dir.h"
+#include "parse-options.h"
 
 /*
  *  --check turns on checking that the working tree matches the
@@ -45,9 +46,11 @@ static int apply_verbosely;
 static int no_add;
 static const char *fake_ancestor;
 static int line_termination = '\n';
-static unsigned long p_context = ULONG_MAX;
-static const char apply_usage[] =
-"git apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [--reject] [--verbose] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|fix|error|error-all>] <patch>...";
+static unsigned int p_context = UINT_MAX;
+static const char * const apply_usage[] = {
+       "git apply [options] [<patch>...]",
+       NULL
+};
 
 static enum ws_error_action {
        nowarn_ws_error,
@@ -61,6 +64,8 @@ static int applied_after_fixing_ws;
 static const char *patch_input_file;
 static const char *root;
 static int root_len;
+static int read_stdin = 1;
+static int options;
 
 static void parse_whitespace_option(const char *option)
 {
@@ -3138,151 +3143,160 @@ static int git_apply_config(const char *var, const char *value, void *cb)
        return git_default_config(var, value, cb);
 }
 
+static int option_parse_exclude(const struct option *opt,
+                               const char *arg, int unset)
+{
+       add_name_limit(arg, 1);
+       return 0;
+}
+
+static int option_parse_include(const struct option *opt,
+                               const char *arg, int unset)
+{
+       add_name_limit(arg, 0);
+       has_include = 1;
+       return 0;
+}
+
+static int option_parse_p(const struct option *opt,
+                         const char *arg, int unset)
+{
+       p_value = atoi(arg);
+       p_value_known = 1;
+       return 0;
+}
+
+static int option_parse_z(const struct option *opt,
+                         const char *arg, int unset)
+{
+       if (unset)
+               line_termination = '\n';
+       else
+               line_termination = 0;
+       return 0;
+}
+
+static int option_parse_whitespace(const struct option *opt,
+                                  const char *arg, int unset)
+{
+       const char **whitespace_option = opt->value;
+
+       *whitespace_option = arg;
+       parse_whitespace_option(arg);
+       return 0;
+}
+
+static int option_parse_directory(const struct option *opt,
+                                 const char *arg, int unset)
+{
+       root_len = strlen(arg);
+       if (root_len && arg[root_len - 1] != '/') {
+               char *new_root;
+               root = new_root = xmalloc(root_len + 2);
+               strcpy(new_root, arg);
+               strcpy(new_root + root_len++, "/");
+       } else
+               root = arg;
+       return 0;
+}
 
 int cmd_apply(int argc, const char **argv, const char *unused_prefix)
 {
        int i;
-       int read_stdin = 1;
-       int options = 0;
        int errs = 0;
        int is_not_gitdir;
+       int binary;
+       int force_apply = 0;
 
        const char *whitespace_option = NULL;
 
+       struct option builtin_apply_options[] = {
+               { OPTION_CALLBACK, 0, "exclude", NULL, "path",
+                       "don´t apply changes matching the given path",
+                       0, option_parse_exclude },
+               { OPTION_CALLBACK, 0, "include", NULL, "path",
+                       "apply changes matching the given path",
+                       0, option_parse_include },
+               { OPTION_CALLBACK, 'p', NULL, NULL, "num",
+                       "remove <num> leading slashes from traditional diff paths",
+                       0, option_parse_p },
+               OPT_BOOLEAN(0, "no-add", &no_add,
+                       "ignore additions made by the patch"),
+               OPT_BOOLEAN(0, "stat", &diffstat,
+                       "instead of applying the patch, output diffstat for the input"),
+               OPT_BOOLEAN(0, "allow-binary-replacement", &binary,
+                       "now no-op"),
+               OPT_BOOLEAN(0, "binary", &binary,
+                       "now no-op"),
+               OPT_BOOLEAN(0, "numstat", &numstat,
+                       "shows number of added and deleted lines in decimal notation"),
+               OPT_BOOLEAN(0, "summary", &summary,
+                       "instead of applying the patch, output a summary for the input"),
+               OPT_BOOLEAN(0, "check", &check,
+                       "instead of applying the patch, see if the patch is applicable"),
+               OPT_BOOLEAN(0, "index", &check_index,
+                       "make sure the patch is applicable to the current index"),
+               OPT_BOOLEAN(0, "cached", &cached,
+                       "apply a patch without touching the working tree"),
+               OPT_BOOLEAN(0, "apply", &force_apply,
+                       "also apply the patch (use with --stat/--summary/--check)"),
+               OPT_STRING(0, "build-fake-ancestor", &fake_ancestor, "file",
+                       "build a temporary index based on embedded index information"),
+               { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
+                       "paths are separated with NUL character",
+                       PARSE_OPT_NOARG, option_parse_z },
+               OPT_INTEGER('C', NULL, &p_context,
+                               "ensure at least <n> lines of context match"),
+               { OPTION_CALLBACK, 0, "whitespace", &whitespace_option, "action",
+                       "detect new or modified lines that have whitespace errors",
+                       0, option_parse_whitespace },
+               OPT_BOOLEAN('R', "reverse", &apply_in_reverse,
+                       "apply the patch in reverse"),
+               OPT_BOOLEAN(0, "unidiff-zero", &unidiff_zero,
+                       "don't expect at least one line of context"),
+               OPT_BOOLEAN(0, "reject", &apply_with_reject,
+                       "leave the rejected hunks in corresponding *.rej files"),
+               OPT__VERBOSE(&apply_verbosely),
+               OPT_BIT(0, "inaccurate-eof", &options,
+                       "tolerate incorrectly detected missing new-line at the end of file",
+                       INACCURATE_EOF),
+               OPT_BIT(0, "recount", &options,
+                       "do not trust the line counts in the hunk headers",
+                       RECOUNT),
+               { OPTION_CALLBACK, 0, "directory", NULL, "root",
+                       "prepend <root> to all filenames",
+                       0, option_parse_directory },
+               OPT_END()
+       };
+
        prefix = setup_git_directory_gently(&is_not_gitdir);
        prefix_length = prefix ? strlen(prefix) : 0;
        git_config(git_apply_config, NULL);
        if (apply_default_whitespace)
                parse_whitespace_option(apply_default_whitespace);
 
-       for (i = 1; i < argc; i++) {
+       argc = parse_options(argc, argv, builtin_apply_options,
+                       apply_usage, 0);
+       if (apply_with_reject)
+               apply = apply_verbosely = 1;
+       if (!force_apply && (diffstat || numstat || summary || check || fake_ancestor))
+               apply = 0;
+       if (check_index && is_not_gitdir)
+               die("--index outside a repository");
+       if (cached) {
+               if (is_not_gitdir)
+                       die("--cached outside a repository");
+               check_index = 1;
+       }
+       for (i = 0; i < argc; i++) {
                const char *arg = argv[i];
-               char *end;
                int fd;
 
                if (!strcmp(arg, "-")) {
                        errs |= apply_patch(0, "<stdin>", options);
                        read_stdin = 0;
                        continue;
-               }
-               if (!prefixcmp(arg, "--exclude=")) {
-                       add_name_limit(arg + 10, 1);
-                       continue;
-               }
-               if (!prefixcmp(arg, "--include=")) {
-                       add_name_limit(arg + 10, 0);
-                       has_include = 1;
-                       continue;
-               }
-               if (!prefixcmp(arg, "-p")) {
-                       p_value = atoi(arg + 2);
-                       p_value_known = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--no-add")) {
-                       no_add = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--stat")) {
-                       apply = 0;
-                       diffstat = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--allow-binary-replacement") ||
-                   !strcmp(arg, "--binary")) {
-                       continue; /* now no-op */
-               }
-               if (!strcmp(arg, "--numstat")) {
-                       apply = 0;
-                       numstat = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--summary")) {
-                       apply = 0;
-                       summary = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--check")) {
-                       apply = 0;
-                       check = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--index")) {
-                       if (is_not_gitdir)
-                               die("--index outside a repository");
-                       check_index = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--cached")) {
-                       if (is_not_gitdir)
-                               die("--cached outside a repository");
-                       check_index = 1;
-                       cached = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--apply")) {
-                       apply = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--build-fake-ancestor")) {
-                       apply = 0;
-                       if (++i >= argc)
-                               die ("need a filename");
-                       fake_ancestor = argv[i];
-                       continue;
-               }
-               if (!strcmp(arg, "-z")) {
-                       line_termination = 0;
-                       continue;
-               }
-               if (!prefixcmp(arg, "-C")) {
-                       p_context = strtoul(arg + 2, &end, 0);
-                       if (*end != '\0')
-                               die("unrecognized context count '%s'", arg + 2);
-                       continue;
-               }
-               if (!prefixcmp(arg, "--whitespace=")) {
-                       whitespace_option = arg + 13;
-                       parse_whitespace_option(arg + 13);
-                       continue;
-               }
-               if (!strcmp(arg, "-R") || !strcmp(arg, "--reverse")) {
-                       apply_in_reverse = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--unidiff-zero")) {
-                       unidiff_zero = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--reject")) {
-                       apply = apply_with_reject = apply_verbosely = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-v") || !strcmp(arg, "--verbose")) {
-                       apply_verbosely = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--inaccurate-eof")) {
-                       options |= INACCURATE_EOF;
-                       continue;
-               }
-               if (!strcmp(arg, "--recount")) {
-                       options |= RECOUNT;
-                       continue;
-               }
-               if (!prefixcmp(arg, "--directory=")) {
-                       arg += strlen("--directory=");
-                       root_len = strlen(arg);
-                       if (root_len && arg[root_len - 1] != '/') {
-                               char *new_root;
-                               root = new_root = xmalloc(root_len + 2);
-                               strcpy(new_root, arg);
-                               strcpy(new_root + root_len++, "/");
-                       } else
-                               root = arg;
-                       continue;
-               }
-               if (0 < prefix_length)
+               } else if (0 < prefix_length)
                        arg = prefix_filename(prefix, prefix_length, arg);
 
                fd = open(arg, O_RDONLY);
index 5ceec433fd590e8bf6a51700ea69c37f9af30fa7..60adef93632ec64c528e443cf1fb3d656a54e36c 100644 (file)
@@ -5,44 +5,35 @@
 #include "cache.h"
 #include "builtin.h"
 #include "archive.h"
+#include "parse-options.h"
 #include "pkt-line.h"
 #include "sideband.h"
 
-static int run_remote_archiver(const char *remote, int argc,
-                              const char **argv)
+static void create_output_file(const char *output_file)
+{
+       int output_fd = open(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
+       if (output_fd < 0)
+               die("could not create archive file: %s ", output_file);
+       if (output_fd != 1) {
+               if (dup2(output_fd, 1) < 0)
+                       die("could not redirect output");
+               else
+                       close(output_fd);
+       }
+}
+
+static int run_remote_archiver(int argc, const char **argv,
+                              const char *remote, const char *exec)
 {
        char *url, buf[LARGE_PACKET_MAX];
        int fd[2], i, len, rv;
        struct child_process *conn;
-       const char *exec = "git-upload-archive";
-       int exec_at = 0, exec_value_at = 0;
-
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-               if (!prefixcmp(arg, "--exec=")) {
-                       if (exec_at)
-                               die("multiple --exec specified");
-                       exec = arg + 7;
-                       exec_at = i;
-               } else if (!strcmp(arg, "--exec")) {
-                       if (exec_at)
-                               die("multiple --exec specified");
-                       if (i + 1 >= argc)
-                               die("option --exec requires a value");
-                       exec = argv[i + 1];
-                       exec_at = i;
-                       exec_value_at = ++i;
-               }
-       }
 
        url = xstrdup(remote);
        conn = git_connect(fd, url, exec, 0);
 
-       for (i = 1; i < argc; i++) {
-               if (i == exec_at || i == exec_value_at)
-                       continue;
+       for (i = 1; i < argc; i++)
                packet_write(fd[1], "argument %s\n", argv[i]);
-       }
        packet_flush(fd[1]);
 
        len = packet_read_line(fd[0], buf, sizeof(buf));
@@ -69,51 +60,33 @@ static int run_remote_archiver(const char *remote, int argc,
        return !!rv;
 }
 
-static const char *extract_remote_arg(int *ac, const char **av)
-{
-       int ix, iy, cnt = *ac;
-       int no_more_options = 0;
-       const char *remote = NULL;
-
-       for (ix = iy = 1; ix < cnt; ix++) {
-               const char *arg = av[ix];
-               if (!strcmp(arg, "--"))
-                       no_more_options = 1;
-               if (!no_more_options) {
-                       if (!prefixcmp(arg, "--remote=")) {
-                               if (remote)
-                                       die("Multiple --remote specified");
-                               remote = arg + 9;
-                               continue;
-                       } else if (!strcmp(arg, "--remote")) {
-                               if (remote)
-                                       die("Multiple --remote specified");
-                               if (++ix >= cnt)
-                                       die("option --remote requires a value");
-                               remote = av[ix];
-                               continue;
-                       }
-                       if (arg[0] != '-')
-                               no_more_options = 1;
-               }
-               if (ix != iy)
-                       av[iy] = arg;
-               iy++;
-       }
-       if (remote) {
-               av[--cnt] = NULL;
-               *ac = cnt;
-       }
-       return remote;
-}
+#define PARSE_OPT_KEEP_ALL ( PARSE_OPT_KEEP_DASHDASH |         \
+                            PARSE_OPT_KEEP_ARGV0 |     \
+                            PARSE_OPT_KEEP_UNKNOWN |   \
+                            PARSE_OPT_NO_INTERNAL_HELP )
 
 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;
+       struct option local_opts[] = {
+               OPT_STRING(0, "output", &output, "file",
+                       "write the archive to this file"),
+               OPT_STRING(0, "remote", &remote, "repo",
+                       "retrieve the archive from remote repository <repo>"),
+               OPT_STRING(0, "exec", &exec, "cmd",
+                       "path to the remote git-upload-archive command"),
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, local_opts, NULL, PARSE_OPT_KEEP_ALL);
+
+       if (output)
+               create_output_file(output);
 
-       remote = extract_remote_arg(&argc, argv);
        if (remote)
-               return run_remote_archiver(remote, argc, argv);
+               return run_remote_archiver(argc, argv, remote, exec);
 
        setvbuf(stderr, NULL, _IOLBF, BUFSIZ);
 
index aae14ef8bb63abc598e2e3ce99bf64d5c07e0067..2aedd17c39fdfa12b72551d61af559f534dd65a7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Pickaxe
+ * Blame
  *
  * Copyright (c) 2006, Junio C Hamano
  */
@@ -19,6 +19,7 @@
 #include "string-list.h"
 #include "mailmap.h"
 #include "parse-options.h"
+#include "utf8.h"
 
 static char blame_usage[] = "git blame [options] [rev-opts] [rev] [--] file";
 
@@ -39,6 +40,10 @@ static int reverse;
 static int blank_boundary;
 static int incremental;
 static int xdl_opts = XDF_NEED_MINIMAL;
+
+static enum date_mode blame_date_mode = DATE_ISO8601;
+static size_t blame_date_width;
+
 static struct string_list mailmap;
 
 #ifndef DEBUG
@@ -73,6 +78,7 @@ static unsigned blame_copy_score;
  */
 struct origin {
        int refcnt;
+       struct origin *previous;
        struct commit *commit;
        mmfile_t file;
        unsigned char blob_sha1[20];
@@ -114,6 +120,8 @@ static inline struct origin *origin_incref(struct origin *o)
 static void origin_decref(struct origin *o)
 {
        if (o && --o->refcnt <= 0) {
+               if (o->previous)
+                       origin_decref(o->previous);
                free(o->file.ptr);
                free(o);
        }
@@ -1197,6 +1205,10 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
                struct origin *porigin = sg_origin[i];
                if (!porigin)
                        continue;
+               if (!origin->previous) {
+                       origin_incref(porigin);
+                       origin->previous = porigin;
+               }
                if (pass_blame_to_parent(sb, origin, porigin))
                        goto finish;
        }
@@ -1263,11 +1275,12 @@ struct commit_info
  * Parse author/committer line in the commit object buffer
  */
 static void get_ac_line(const char *inbuf, const char *what,
-                       int bufsz, char *person, const char **mail,
+                       int person_len, char *person,
+                       int mail_len, char *mail,
                        unsigned long *time, const char **tz)
 {
        int len, tzlen, maillen;
-       char *tmp, *endp, *timepos;
+       char *tmp, *endp, *timepos, *mailpos;
 
        tmp = strstr(inbuf, what);
        if (!tmp)
@@ -1278,10 +1291,11 @@ static void get_ac_line(const char *inbuf, const char *what,
                len = strlen(tmp);
        else
                len = endp - tmp;
-       if (bufsz <= len) {
+       if (person_len <= len) {
        error_out:
                /* Ugh */
-               *mail = *tz = "(unknown)";
+               *tz = "(unknown)";
+               strcpy(mail, *tz);
                *time = 0;
                return;
        }
@@ -1304,9 +1318,10 @@ static void get_ac_line(const char *inbuf, const char *what,
        *tmp = 0;
        while (*tmp != ' ')
                tmp--;
-       *mail = tmp + 1;
+       mailpos = tmp + 1;
        *tmp = 0;
        maillen = timepos - tmp;
+       memcpy(mail, mailpos, maillen);
 
        if (!mailmap.nr)
                return;
@@ -1315,20 +1330,23 @@ static void get_ac_line(const char *inbuf, const char *what,
         * mailmap expansion may make the name longer.
         * make room by pushing stuff down.
         */
-       tmp = person + bufsz - (tzlen + 1);
+       tmp = person + person_len - (tzlen + 1);
        memmove(tmp, *tz, tzlen);
        tmp[tzlen] = 0;
        *tz = tmp;
 
-       tmp = tmp - (maillen + 1);
-       memmove(tmp, *mail, maillen);
-       tmp[maillen] = 0;
-       *mail = tmp;
-
        /*
-        * Now, convert e-mail using mailmap
+        * Now, convert both name and e-mail using mailmap
         */
-       map_email(&mailmap, tmp + 1, person, tmp-person-1);
+       if(map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) {
+               /* Add a trailing '>' to email, since map_user returns plain emails
+                  Note: It already has '<', since we replace from mail+1 */
+               mailpos = memchr(mail, '\0', mail_len);
+               if (mailpos && mailpos-mail < mail_len - 1) {
+                       *mailpos = '>';
+                       *(mailpos+1) = '\0';
+               }
+       }
 }
 
 static void get_commit_info(struct commit *commit,
@@ -1337,8 +1355,10 @@ static void get_commit_info(struct commit *commit,
 {
        int len;
        char *tmp, *endp, *reencoded, *message;
-       static char author_buf[1024];
-       static char committer_buf[1024];
+       static char author_name[1024];
+       static char author_mail[1024];
+       static char committer_name[1024];
+       static char committer_mail[1024];
        static char summary_buf[1024];
 
        /*
@@ -1356,9 +1376,11 @@ static void get_commit_info(struct commit *commit,
        }
        reencoded = reencode_commit_message(commit, NULL);
        message   = reencoded ? reencoded : commit->buffer;
-       ret->author = author_buf;
+       ret->author = author_name;
+       ret->author_mail = author_mail;
        get_ac_line(message, "\nauthor ",
-                   sizeof(author_buf), author_buf, &ret->author_mail,
+                   sizeof(author_name), author_name,
+                   sizeof(author_mail), author_mail,
                    &ret->author_time, &ret->author_tz);
 
        if (!detailed) {
@@ -1366,9 +1388,11 @@ static void get_commit_info(struct commit *commit,
                return;
        }
 
-       ret->committer = committer_buf;
+       ret->committer = committer_name;
+       ret->committer_mail = committer_mail;
        get_ac_line(message, "\ncommitter ",
-                   sizeof(committer_buf), committer_buf, &ret->committer_mail,
+                   sizeof(committer_name), committer_name,
+                   sizeof(committer_mail), committer_mail,
                    &ret->committer_time, &ret->committer_tz);
 
        ret->summary = summary_buf;
@@ -1401,6 +1425,39 @@ static void write_filename_info(const char *path)
        write_name_quoted(path, stdout, '\n');
 }
 
+/*
+ * Porcelain/Incremental format wants to show a lot of details per
+ * commit.  Instead of repeating this every line, emit it only once,
+ * the first time each commit appears in the output.
+ */
+static int emit_one_suspect_detail(struct origin *suspect)
+{
+       struct commit_info ci;
+
+       if (suspect->commit->object.flags & METAINFO_SHOWN)
+               return 0;
+
+       suspect->commit->object.flags |= METAINFO_SHOWN;
+       get_commit_info(suspect->commit, &ci, 1);
+       printf("author %s\n", ci.author);
+       printf("author-mail %s\n", ci.author_mail);
+       printf("author-time %lu\n", ci.author_time);
+       printf("author-tz %s\n", ci.author_tz);
+       printf("committer %s\n", ci.committer);
+       printf("committer-mail %s\n", ci.committer_mail);
+       printf("committer-time %lu\n", ci.committer_time);
+       printf("committer-tz %s\n", ci.committer_tz);
+       printf("summary %s\n", ci.summary);
+       if (suspect->commit->object.flags & UNINTERESTING)
+               printf("boundary\n");
+       if (suspect->previous) {
+               struct origin *prev = suspect->previous;
+               printf("previous %s ", sha1_to_hex(prev->commit->object.sha1));
+               write_name_quoted(prev->path, stdout, '\n');
+       }
+       return 1;
+}
+
 /*
  * The blame_entry is found to be guilty for the range.  Mark it
  * as such, and show it in incremental output.
@@ -1416,22 +1473,7 @@ static void found_guilty_entry(struct blame_entry *ent)
                printf("%s %d %d %d\n",
                       sha1_to_hex(suspect->commit->object.sha1),
                       ent->s_lno + 1, ent->lno + 1, ent->num_lines);
-               if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
-                       struct commit_info ci;
-                       suspect->commit->object.flags |= METAINFO_SHOWN;
-                       get_commit_info(suspect->commit, &ci, 1);
-                       printf("author %s\n", ci.author);
-                       printf("author-mail %s\n", ci.author_mail);
-                       printf("author-time %lu\n", ci.author_time);
-                       printf("author-tz %s\n", ci.author_tz);
-                       printf("committer %s\n", ci.committer);
-                       printf("committer-mail %s\n", ci.committer_mail);
-                       printf("committer-time %lu\n", ci.committer_time);
-                       printf("committer-tz %s\n", ci.committer_tz);
-                       printf("summary %s\n", ci.summary);
-                       if (suspect->commit->object.flags & UNINTERESTING)
-                               printf("boundary\n");
-               }
+               emit_one_suspect_detail(suspect);
                write_filename_info(suspect->path);
                maybe_flush_or_die(stdout, "stdout");
        }
@@ -1494,24 +1536,20 @@ static const char *format_time(unsigned long time, const char *tz_str,
                               int show_raw_time)
 {
        static char time_buf[128];
-       time_t t = time;
-       int minutes, tz;
-       struct tm *tm;
+       const char *time_str;
+       int time_len;
+       int tz;
 
        if (show_raw_time) {
                sprintf(time_buf, "%lu %s", time, tz_str);
-               return time_buf;
        }
-
-       tz = atoi(tz_str);
-       minutes = tz < 0 ? -tz : tz;
-       minutes = (minutes / 100)*60 + (minutes % 100);
-       minutes = tz < 0 ? -minutes : minutes;
-       t = time + minutes * 60;
-       tm = gmtime(&t);
-
-       strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", tm);
-       strcat(time_buf, tz_str);
+       else {
+               tz = atoi(tz_str);
+               time_str = show_date(time, tz, blame_date_mode);
+               time_len = strlen(time_str);
+               memcpy(time_buf, time_str, time_len);
+               memset(time_buf + time_len, ' ', blame_date_width - time_len);
+       }
        return time_buf;
 }
 
@@ -1538,24 +1576,8 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
               ent->s_lno + 1,
               ent->lno + 1,
               ent->num_lines);
-       if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
-               struct commit_info ci;
-               suspect->commit->object.flags |= METAINFO_SHOWN;
-               get_commit_info(suspect->commit, &ci, 1);
-               printf("author %s\n", ci.author);
-               printf("author-mail %s\n", ci.author_mail);
-               printf("author-time %lu\n", ci.author_time);
-               printf("author-tz %s\n", ci.author_tz);
-               printf("committer %s\n", ci.committer);
-               printf("committer-mail %s\n", ci.committer_mail);
-               printf("committer-time %lu\n", ci.committer_time);
-               printf("committer-tz %s\n", ci.committer_tz);
-               write_filename_info(suspect->path);
-               printf("summary %s\n", ci.summary);
-               if (suspect->commit->object.flags & UNINTERESTING)
-                       printf("boundary\n");
-       }
-       else if (suspect->commit->object.flags & MORE_THAN_ONE_PATH)
+       if (emit_one_suspect_detail(suspect) ||
+           (suspect->commit->object.flags & MORE_THAN_ONE_PATH))
                write_filename_info(suspect->path);
 
        cp = nth_line(sb, ent->lno);
@@ -1618,13 +1640,14 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
                                printf(" %*d", max_orig_digits,
                                       ent->s_lno + 1 + cnt);
 
-                       if (!(opt & OUTPUT_NO_AUTHOR))
-                               printf(" (%-*.*s %10s",
-                                      longest_author, longest_author,
-                                      ci.author,
+                       if (!(opt & OUTPUT_NO_AUTHOR)) {
+                               int pad = longest_author - utf8_strwidth(ci.author);
+                               printf(" (%s%*s %10s",
+                                      ci.author, pad, "",
                                       format_time(ci.author_time,
                                                   ci.author_tz,
                                                   show_raw_time));
+                       }
                        printf(" %*d) ",
                               max_digits, ent->lno + 1 + cnt);
                }
@@ -1755,7 +1778,7 @@ static void find_alignment(struct scoreboard *sb, int *option)
                if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
                        suspect->commit->object.flags |= METAINFO_SHOWN;
                        get_commit_info(suspect->commit, &ci, 1);
-                       num = strlen(ci.author);
+                       num = utf8_strwidth(ci.author);
                        if (longest_author < num)
                                longest_author = num;
                }
@@ -1792,36 +1815,6 @@ static void sanity_check_refcnt(struct scoreboard *sb)
                        baa = 1;
                }
        }
-       for (ent = sb->ent; ent; ent = ent->next) {
-               /* Mark the ones that haven't been checked */
-               if (0 < ent->suspect->refcnt)
-                       ent->suspect->refcnt = -ent->suspect->refcnt;
-       }
-       for (ent = sb->ent; ent; ent = ent->next) {
-               /*
-                * ... then pick each and see if they have the the
-                * correct refcnt.
-                */
-               int found;
-               struct blame_entry *e;
-               struct origin *suspect = ent->suspect;
-
-               if (0 < suspect->refcnt)
-                       continue;
-               suspect->refcnt = -suspect->refcnt; /* Unmark */
-               for (found = 0, e = sb->ent; e; e = e->next) {
-                       if (e->suspect != suspect)
-                               continue;
-                       found++;
-               }
-               if (suspect->refcnt != found) {
-                       fprintf(stderr, "%s in %s has refcnt %d, not %d\n",
-                               ent->suspect->path,
-                               sha1_to_hex(ent->suspect->commit->object.sha1),
-                               ent->suspect->refcnt, found);
-                       baa = 2;
-               }
-       }
        if (baa) {
                int opt = 0160;
                find_alignment(sb, &opt);
@@ -1961,6 +1954,12 @@ static int git_blame_config(const char *var, const char *value, void *cb)
                blank_boundary = git_config_bool(var, value);
                return 0;
        }
+       if (!strcmp(var, "blame.date")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               blame_date_mode = parse_date_format(value);
+               return 0;
+       }
        return git_default_config(var, value, cb);
 }
 
@@ -2225,6 +2224,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
 
        git_config(git_blame_config, NULL);
        init_revisions(&revs, NULL);
+       revs.date_mode = blame_date_mode;
+
        save_commit_buffer = 0;
        dashdash_pos = 0;
 
@@ -2249,8 +2250,35 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
 parse_done:
        argc = parse_options_end(&ctx);
 
-       if (cmd_is_annotate)
+       if (cmd_is_annotate) {
                output_option |= OUTPUT_ANNOTATE_COMPAT;
+               blame_date_mode = DATE_ISO8601;
+       } else {
+               blame_date_mode = revs.date_mode;
+       }
+
+       /* The maximum width used to show the dates */
+       switch (blame_date_mode) {
+       case DATE_RFC2822:
+               blame_date_width = sizeof("Thu, 19 Oct 2006 16:00:04 -0700");
+               break;
+       case DATE_ISO8601:
+               blame_date_width = sizeof("2006-10-19 16:00:04 -0700");
+               break;
+       case DATE_RAW:
+               blame_date_width = sizeof("1161298804 -0700");
+               break;
+       case DATE_SHORT:
+               blame_date_width = sizeof("2006-10-19");
+               break;
+       case DATE_RELATIVE:
+               /* "normal" is used as the fallback for "relative" */
+       case DATE_LOCAL:
+       case DATE_NORMAL:
+               blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
+               break;
+       }
+       blame_date_width -= 1; /* strip the null */
 
        if (DIFF_OPT_TST(&revs.diffopt, FIND_COPIES_HARDER))
                opt |= (PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE |
@@ -2394,7 +2422,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                die("reading graft file %s failed: %s",
                    revs_file, strerror(errno));
 
-       read_mailmap(&mailmap, ".mailmap", NULL);
+       read_mailmap(&mailmap, NULL);
 
        if (!incremental)
                setup_pager();
index 02fa38fd3b13b5c17aa23edbfd79f91a052adf76..14d4b917e5bb929f40efd3fc5e010cec5d3507de 100644 (file)
@@ -32,18 +32,18 @@ static unsigned char head_sha1[20];
 
 static int branch_use_color = -1;
 static char branch_colors[][COLOR_MAXLEN] = {
-       "\033[m",       /* reset */
-       "",             /* PLAIN (normal) */
-       "\033[31m",     /* REMOTE (red) */
-       "",             /* LOCAL (normal) */
-       "\033[32m",     /* CURRENT (green) */
+       GIT_COLOR_RESET,
+       GIT_COLOR_NORMAL,       /* PLAIN */
+       GIT_COLOR_RED,          /* REMOTE */
+       GIT_COLOR_NORMAL,       /* LOCAL */
+       GIT_COLOR_GREEN,        /* CURRENT */
 };
 enum color_branch {
-       COLOR_BRANCH_RESET = 0,
-       COLOR_BRANCH_PLAIN = 1,
-       COLOR_BRANCH_REMOTE = 2,
-       COLOR_BRANCH_LOCAL = 3,
-       COLOR_BRANCH_CURRENT = 4,
+       BRANCH_COLOR_RESET = 0,
+       BRANCH_COLOR_PLAIN = 1,
+       BRANCH_COLOR_REMOTE = 2,
+       BRANCH_COLOR_LOCAL = 3,
+       BRANCH_COLOR_CURRENT = 4,
 };
 
 static enum merge_filter {
@@ -56,15 +56,15 @@ static unsigned char merge_filter_ref[20];
 static int parse_branch_color_slot(const char *var, int ofs)
 {
        if (!strcasecmp(var+ofs, "plain"))
-               return COLOR_BRANCH_PLAIN;
+               return BRANCH_COLOR_PLAIN;
        if (!strcasecmp(var+ofs, "reset"))
-               return COLOR_BRANCH_RESET;
+               return BRANCH_COLOR_RESET;
        if (!strcasecmp(var+ofs, "remote"))
-               return COLOR_BRANCH_REMOTE;
+               return BRANCH_COLOR_REMOTE;
        if (!strcasecmp(var+ofs, "local"))
-               return COLOR_BRANCH_LOCAL;
+               return BRANCH_COLOR_LOCAL;
        if (!strcasecmp(var+ofs, "current"))
-               return COLOR_BRANCH_CURRENT;
+               return BRANCH_COLOR_CURRENT;
        die("bad config variable '%s'", var);
 }
 
@@ -99,6 +99,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
        const char *fmt, *remote;
        int i;
        int ret = 0;
+       struct strbuf bname = STRBUF_INIT;
 
        switch (kinds) {
        case REF_REMOTE_BRANCH:
@@ -119,20 +120,25 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
                if (!head_rev)
                        die("Couldn't look up commit object for HEAD");
        }
-       for (i = 0; i < argc; i++) {
-               if (kinds == REF_LOCAL_BRANCH && !strcmp(head, argv[i])) {
+       for (i = 0; i < argc; i++, strbuf_release(&bname)) {
+               int len = strlen(argv[i]);
+
+               if (interpret_nth_last_branch(argv[i], &bname) != len)
+                       strbuf_add(&bname, argv[i], len);
+
+               if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) {
                        error("Cannot delete the branch '%s' "
-                               "which you are currently on.", argv[i]);
+                             "which you are currently on.", bname.buf);
                        ret = 1;
                        continue;
                }
 
                free(name);
 
-               name = xstrdup(mkpath(fmt, argv[i]));
+               name = xstrdup(mkpath(fmt, bname.buf));
                if (!resolve_ref(name, sha1, 1, NULL)) {
                        error("%sbranch '%s' not found.",
-                                       remote, argv[i]);
+                                       remote, bname.buf);
                        ret = 1;
                        continue;
                }
@@ -152,22 +158,23 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
                if (!force &&
                    !in_merge_bases(rev, &head_rev, 1)) {
                        error("The branch '%s' is not an ancestor of "
-                               "your current HEAD.\n"
-                               "If you are sure you want to delete it, "
-                               "run 'git branch -D %s'.", argv[i], argv[i]);
+                             "your current HEAD.\n"
+                             "If you are sure you want to delete it, "
+                             "run 'git branch -D %s'.", bname.buf, bname.buf);
                        ret = 1;
                        continue;
                }
 
                if (delete_ref(name, sha1, 0)) {
                        error("Error deleting %sbranch '%s'", remote,
-                              argv[i]);
+                             bname.buf);
                        ret = 1;
                } else {
                        struct strbuf buf = STRBUF_INIT;
-                       printf("Deleted %sbranch %s (%s).\n", remote, argv[i],
-                               find_unique_abbrev(sha1, DEFAULT_ABBREV));
-                       strbuf_addf(&buf, "branch.%s", argv[i]);
+                       printf("Deleted %sbranch %s (%s).\n", remote,
+                              bname.buf,
+                              find_unique_abbrev(sha1, DEFAULT_ABBREV));
+                       strbuf_addf(&buf, "branch.%s", bname.buf);
                        if (git_config_rename_section(buf.buf, NULL) < 0)
                                warning("Update of config-file failed");
                        strbuf_release(&buf);
@@ -181,7 +188,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
 
 struct ref_item {
        char *name;
-       unsigned int kind;
+       char *dest;
+       unsigned int kind, len;
        struct commit *commit;
 };
 
@@ -193,19 +201,18 @@ struct ref_list {
        int kinds;
 };
 
-static int has_commit(struct commit *commit, struct commit_list *with_commit)
+static char *resolve_symref(const char *src, const char *prefix)
 {
-       if (!with_commit)
-               return 1;
-       while (with_commit) {
-               struct commit *other;
-
-               other = with_commit->item;
-               with_commit = with_commit->next;
-               if (in_merge_bases(other, &commit, 1))
-                       return 1;
-       }
-       return 0;
+       unsigned char sha1[20];
+       int flag;
+       const char *dst, *cp;
+
+       dst = resolve_ref(src, sha1, 0, &flag);
+       if (!(dst && (flag & REF_ISSYMREF)))
+               return NULL;
+       if (prefix && (cp = skip_prefix(dst, prefix)))
+               dst = cp;
+       return xstrdup(dst);
 }
 
 static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
@@ -213,17 +220,28 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
        struct ref_list *ref_list = (struct ref_list*)(cb_data);
        struct ref_item *newitem;
        struct commit *commit;
-       int kind;
-       int len;
+       int kind, i;
+       const char *prefix, *orig_refname = refname;
+
+       static struct {
+               int kind;
+               const char *prefix;
+               int pfxlen;
+       } ref_kind[] = {
+               { REF_LOCAL_BRANCH, "refs/heads/", 11 },
+               { REF_REMOTE_BRANCH, "refs/remotes/", 13 },
+       };
 
        /* Detect kind */
-       if (!prefixcmp(refname, "refs/heads/")) {
-               kind = REF_LOCAL_BRANCH;
-               refname += 11;
-       } else if (!prefixcmp(refname, "refs/remotes/")) {
-               kind = REF_REMOTE_BRANCH;
-               refname += 13;
-       } else
+       for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
+               prefix = ref_kind[i].prefix;
+               if (strncmp(refname, prefix, ref_kind[i].pfxlen))
+                       continue;
+               kind = ref_kind[i].kind;
+               refname += ref_kind[i].pfxlen;
+               break;
+       }
+       if (ARRAY_SIZE(ref_kind) <= i)
                return 0;
 
        commit = lookup_commit_reference_gently(sha1, 1);
@@ -231,7 +249,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
                return error("branch '%s' does not point at a commit", refname);
 
        /* Filter with with_commit if specified */
-       if (!has_commit(commit, ref_list->with_commit))
+       if (!is_descendant_of(commit, ref_list->with_commit))
                return 0;
 
        /* Don't add types the caller doesn't want */
@@ -254,9 +272,14 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
        newitem->name = xstrdup(refname);
        newitem->kind = kind;
        newitem->commit = commit;
-       len = strlen(newitem->name);
-       if (len > ref_list->maxwidth)
-               ref_list->maxwidth = len;
+       newitem->len = strlen(refname);
+       newitem->dest = resolve_symref(orig_refname, prefix);
+       /* adjust for "remotes/" */
+       if (newitem->kind == REF_REMOTE_BRANCH &&
+           ref_list->kinds != REF_REMOTE_BRANCH)
+               newitem->len += 8;
+       if (newitem->len > ref_list->maxwidth)
+               ref_list->maxwidth = newitem->len;
 
        return 0;
 }
@@ -265,8 +288,10 @@ static void free_ref_list(struct ref_list *ref_list)
 {
        int i;
 
-       for (i = 0; i < ref_list->index; i++)
+       for (i = 0; i < ref_list->index; i++) {
                free(ref_list->list[i].name);
+               free(ref_list->list[i].dest);
+       }
        free(ref_list->list);
 }
 
@@ -307,34 +332,46 @@ static int matches_merge_filter(struct commit *commit)
 }
 
 static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
-                          int abbrev, int current)
+                          int abbrev, int current, char *prefix)
 {
        char c;
        int color;
        struct commit *commit = item->commit;
+       struct strbuf out = STRBUF_INIT, name = STRBUF_INIT;
 
        if (!matches_merge_filter(commit))
                return;
 
        switch (item->kind) {
        case REF_LOCAL_BRANCH:
-               color = COLOR_BRANCH_LOCAL;
+               color = BRANCH_COLOR_LOCAL;
                break;
        case REF_REMOTE_BRANCH:
-               color = COLOR_BRANCH_REMOTE;
+               color = BRANCH_COLOR_REMOTE;
                break;
        default:
-               color = COLOR_BRANCH_PLAIN;
+               color = BRANCH_COLOR_PLAIN;
                break;
        }
 
        c = ' ';
        if (current) {
                c = '*';
-               color = COLOR_BRANCH_CURRENT;
+               color = BRANCH_COLOR_CURRENT;
        }
 
-       if (verbose) {
+       strbuf_addf(&name, "%s%s", prefix, item->name);
+       if (verbose)
+               strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color),
+                           maxwidth, name.buf,
+                           branch_get_color(BRANCH_COLOR_RESET));
+       else
+               strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color),
+                           name.buf, branch_get_color(BRANCH_COLOR_RESET));
+
+       if (item->dest)
+               strbuf_addf(&out, " -> %s", item->dest);
+       else if (verbose) {
                struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT;
                const char *sub = " **** invalid ref ****";
 
@@ -348,28 +385,25 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
                if (item->kind == REF_LOCAL_BRANCH)
                        fill_tracking_info(&stat, item->name);
 
-               printf("%c %s%-*s%s %s %s%s\n", c, branch_get_color(color),
-                      maxwidth, item->name,
-                      branch_get_color(COLOR_BRANCH_RESET),
-                      find_unique_abbrev(item->commit->object.sha1, abbrev),
-                      stat.buf, sub);
+               strbuf_addf(&out, " %s %s%s",
+                       find_unique_abbrev(item->commit->object.sha1, abbrev),
+                       stat.buf, sub);
                strbuf_release(&stat);
                strbuf_release(&subject);
-       } else {
-               printf("%c %s%s%s\n", c, branch_get_color(color), item->name,
-                      branch_get_color(COLOR_BRANCH_RESET));
        }
+       printf("%s\n", out.buf);
+       strbuf_release(&name);
+       strbuf_release(&out);
 }
 
 static int calc_maxwidth(struct ref_list *refs)
 {
-       int i, l, w = 0;
+       int i, w = 0;
        for (i = 0; i < refs->index; i++) {
                if (!matches_merge_filter(refs->list[i].commit))
                        continue;
-               l = strlen(refs->list[i].name);
-               if (l > w)
-                       w = l;
+               if (refs->list[i].len > w)
+                       w = refs->list[i].len;
        }
        return w;
 }
@@ -401,14 +435,17 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev, str
        qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
 
        detached = (detached && (kinds & REF_LOCAL_BRANCH));
-       if (detached && head_commit && has_commit(head_commit, with_commit)) {
+       if (detached && head_commit &&
+           is_descendant_of(head_commit, with_commit)) {
                struct ref_item item;
                item.name = xstrdup("(no branch)");
+               item.len = strlen(item.name);
                item.kind = REF_LOCAL_BRANCH;
+               item.dest = NULL;
                item.commit = head_commit;
-               if (strlen(item.name) > ref_list.maxwidth)
-                       ref_list.maxwidth = strlen(item.name);
-               print_ref_item(&item, ref_list.maxwidth, verbose, abbrev, 1);
+               if (item.len > ref_list.maxwidth)
+                       ref_list.maxwidth = item.len;
+               print_ref_item(&item, ref_list.maxwidth, verbose, abbrev, 1, "");
                free(item.name);
        }
 
@@ -416,8 +453,11 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev, str
                int current = !detached &&
                        (ref_list.list[i].kind == REF_LOCAL_BRANCH) &&
                        !strcmp(ref_list.list[i].name, head);
+               char *prefix = (kinds != REF_REMOTE_BRANCH &&
+                               ref_list.list[i].kind == REF_REMOTE_BRANCH)
+                               ? "remotes/" : "";
                print_ref_item(&ref_list.list[i], ref_list.maxwidth, verbose,
-                              abbrev, current);
+                              abbrev, current, prefix);
        }
 
        free_ref_list(&ref_list);
@@ -466,22 +506,6 @@ static void rename_branch(const char *oldname, const char *newname, int force)
        strbuf_release(&newsection);
 }
 
-static int opt_parse_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))
-               die("malformed object name %s", arg);
-       commit = lookup_commit_reference(sha1);
-       if (!commit)
-               die("no such commit %s", arg);
-       commit_list_insert(commit, opt->value);
-       return 0;
-}
-
 static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset)
 {
        merge_filter = ((opt->long_name[0] == 'n')
@@ -517,13 +541,13 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                        OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
                        "print only branches that contain the commit",
                        PARSE_OPT_LASTARG_DEFAULT,
-                       opt_parse_with_commit, (intptr_t)"HEAD",
+                       parse_opt_with_commit, (intptr_t)"HEAD",
                },
                {
                        OPTION_CALLBACK, 0, "with", &with_commit, "commit",
                        "print only branches that contain the commit",
                        PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
-                       opt_parse_with_commit, (intptr_t) "HEAD",
+                       parse_opt_with_commit, (intptr_t) "HEAD",
                },
                OPT__ABBREV(&abbrev),
 
index 30d00a66649f749c8cf6e657734045382bc29e13..8fad19daedef8a38674ee35cd543983bad610857 100644 (file)
@@ -137,7 +137,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
                break;
 
        default:
-               die("git cat-file: unknown option: %s\n", exp_type);
+               die("git cat-file: unknown option: %s", exp_type);
        }
 
        if (!buf)
index b5dd9c07b42e0130384259715730f52bc8c4e6c3..c315f63398bac5dd3fd465af881caa5f3b9c9184 100644 (file)
@@ -38,23 +38,13 @@ struct checkout_opts {
 static int post_checkout_hook(struct commit *old, struct commit *new,
                              int changed)
 {
-       struct child_process proc;
-       const char *name = git_path("hooks/post-checkout");
-       const char *argv[5];
+       return run_hook(NULL, "post-checkout",
+                       sha1_to_hex(old ? old->object.sha1 : null_sha1),
+                       sha1_to_hex(new ? new->object.sha1 : null_sha1),
+                       changed ? "1" : "0", NULL);
+       /* "new" can be NULL when checking out from the index before
+          a commit exists. */
 
-       if (access(name, X_OK) < 0)
-               return 0;
-
-       memset(&proc, 0, sizeof(proc));
-       argv[0] = name;
-       argv[1] = xstrdup(sha1_to_hex(old ? old->object.sha1 : null_sha1));
-       argv[2] = xstrdup(sha1_to_hex(new->object.sha1));
-       argv[3] = changed ? "1" : "0";
-       argv[4] = NULL;
-       proc.argv = argv;
-       proc.no_stdin = 1;
-       proc.stdout_to_stderr = 1;
-       return run_command(&proc);
 }
 
 static int update_some(const unsigned char *sha1, const char *base, int baselen,
@@ -240,7 +230,7 @@ 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];
-               pathspec_match(pathspec, ps_matched, ce->name, 0);
+               match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, ps_matched);
        }
 
        if (report_path_error(ps_matched, pathspec, 0))
@@ -249,7 +239,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
        /* Any unmerged paths? */
        for (pos = 0; pos < active_nr; pos++) {
                struct cache_entry *ce = active_cache[pos];
-               if (pathspec_match(pathspec, NULL, ce->name, 0)) {
+               if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
                        if (!ce_stage(ce))
                                continue;
                        if (opts->force) {
@@ -274,7 +264,7 @@ 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 (pathspec_match(pathspec, NULL, ce->name, 0)) {
+               if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
                        if (!ce_stage(ce)) {
                                errs |= checkout_entry(ce, &state, NULL);
                                continue;
@@ -305,6 +295,8 @@ static void show_local_changes(struct object *head)
        init_revisions(&rev, NULL);
        rev.abbrev = 0;
        rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
+       if (diff_setup_done(&rev.diffopt) < 0)
+               die("diff_setup_done failed");
        add_pending_object(&rev, head, NULL);
        run_diff_index(&rev, 0);
 }
@@ -361,8 +353,16 @@ struct branch_info {
 static void setup_branch_path(struct branch_info *branch)
 {
        struct strbuf buf = STRBUF_INIT;
-       strbuf_addstr(&buf, "refs/heads/");
-       strbuf_addstr(&buf, branch->name);
+       int ret;
+
+       if ((ret = interpret_nth_last_branch(branch->name, &buf))
+           && ret == strlen(branch->name)) {
+               branch->name = xstrdup(buf.buf);
+               strbuf_splice(&buf, 0, 0, "refs/heads/", 11);
+       } else {
+               strbuf_addstr(&buf, "refs/heads/");
+               strbuf_addstr(&buf, branch->name);
+       }
        branch->path = strbuf_detach(&buf, NULL);
 }
 
@@ -671,6 +671,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                arg = argv[0];
                has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
 
+               if (!strcmp(arg, "-"))
+                       arg = "@{-1}";
+
                if (get_sha1(arg, rev)) {
                        if (has_dash_dash)          /* case (1) */
                                die("invalid reference: %s", arg);
index 2feac9c5cb8e85ae2b25c5a7a0a602e84e210f4a..39523cee309f16268130427f8209f9a5be0cdc70 100644 (file)
@@ -19,6 +19,8 @@
 #include "strbuf.h"
 #include "dir.h"
 #include "pack-refs.h"
+#include "sigchain.h"
+#include "run-command.h"
 
 /*
  * Overall FIXMEs:
@@ -192,15 +194,15 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest)
 
        dir = opendir(src->buf);
        if (!dir)
-               die("failed to open %s\n", src->buf);
+               die("failed to open %s", src->buf);
 
        if (mkdir(dest->buf, 0777)) {
                if (errno != EEXIST)
-                       die("failed to create directory %s\n", dest->buf);
+                       die("failed to create directory %s", dest->buf);
                else if (stat(dest->buf, &buf))
-                       die("failed to stat %s\n", dest->buf);
+                       die("failed to stat %s", dest->buf);
                else if (!S_ISDIR(buf.st_mode))
-                       die("%s exists and is not a directory\n", dest->buf);
+                       die("%s exists and is not a directory", dest->buf);
        }
 
        strbuf_addch(src, '/');
@@ -224,16 +226,16 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest)
                }
 
                if (unlink(dest->buf) && errno != ENOENT)
-                       die("failed to unlink %s\n", dest->buf);
+                       die("failed to unlink %s", dest->buf);
                if (!option_no_hardlinks) {
                        if (!link(src->buf, dest->buf))
                                continue;
                        if (option_local)
-                               die("failed to create link %s\n", dest->buf);
+                               die("failed to create link %s", dest->buf);
                        option_no_hardlinks = 1;
                }
                if (copy_file(dest->buf, src->buf, 0666))
-                       die("failed to copy file to %s\n", dest->buf);
+                       die("failed to copy file to %s", dest->buf);
        }
        closedir(dir);
 }
@@ -288,7 +290,7 @@ static void remove_junk(void)
 static void remove_junk_on_signal(int signo)
 {
        remove_junk();
-       signal(SIGINT, SIG_DFL);
+       sigchain_pop(signo);
        raise(signo);
 }
 
@@ -349,19 +351,32 @@ static struct ref *write_remote_refs(const struct ref *refs,
        return local_refs;
 }
 
+static void install_branch_config(const char *local,
+                                 const char *origin,
+                                 const char *remote)
+{
+       struct strbuf key = STRBUF_INIT;
+       strbuf_addf(&key, "branch.%s.remote", local);
+       git_config_set(key.buf, origin);
+       strbuf_reset(&key);
+       strbuf_addf(&key, "branch.%s.merge", local);
+       git_config_set(key.buf, remote);
+       strbuf_release(&key);
+}
+
 int cmd_clone(int argc, const char **argv, const char *prefix)
 {
-       int use_local_hardlinks = 1;
-       int use_separate_remote = 1;
        int is_bundle = 0;
        struct stat buf;
        const char *repo_name, *repo, *work_tree, *git_dir;
        char *path, *dir;
+       int dest_exists;
        const struct ref *refs, *head_points_at, *remote_head, *mapped_refs;
        struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
        struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
        struct transport *transport = NULL;
        char *src_ref_prefix = "refs/heads/";
+       int err = 0;
 
        struct refspec refspec;
 
@@ -373,9 +388,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        if (argc == 0)
                die("You must specify a repository to clone.");
 
-       if (option_no_hardlinks)
-               use_local_hardlinks = 0;
-
        if (option_mirror)
                option_bare = 1;
 
@@ -384,7 +396,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                        die("--bare and --origin %s options are incompatible.",
                            option_origin);
                option_no_checkout = 1;
-               use_separate_remote = 0;
        }
 
        if (!option_origin)
@@ -406,8 +417,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                dir = guess_dir_name(repo_name, is_bundle, option_bare);
        strip_trailing_slashes(dir);
 
-       if (!stat(dir, &buf))
-               die("destination directory '%s' already exists.", dir);
+       dest_exists = !stat(dir, &buf);
+       if (dest_exists && !is_empty_dir(dir))
+               die("destination path '%s' already exists and is not "
+                       "an empty directory.", dir);
 
        strbuf_addf(&reflog_msg, "clone: from %s", repo);
 
@@ -431,14 +444,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                if (safe_create_leading_directories_const(work_tree) < 0)
                        die("could not create leading directories of '%s': %s",
                                        work_tree, strerror(errno));
-               if (mkdir(work_tree, 0755))
+               if (!dest_exists && mkdir(work_tree, 0755))
                        die("could not create work tree dir '%s': %s.",
                                        work_tree, strerror(errno));
                set_git_work_tree(work_tree);
        }
        junk_git_dir = git_dir;
        atexit(remove_junk);
-       signal(SIGINT, remove_junk_on_signal);
+       sigchain_push_common(remove_junk_on_signal);
 
        setenv(CONFIG_ENVIRONMENT, xstrdup(mkpath("%s/config", git_dir)), 1);
 
@@ -519,14 +532,26 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                                             option_upload_pack);
 
                refs = transport_get_remote_refs(transport);
-               transport_fetch_refs(transport, refs);
+               if(refs)
+                       transport_fetch_refs(transport, refs);
        }
 
-       clear_extra_refs();
+       if (refs) {
+               clear_extra_refs();
 
-       mapped_refs = write_remote_refs(refs, &refspec, reflog_msg.buf);
+               mapped_refs = write_remote_refs(refs, &refspec, reflog_msg.buf);
 
-       head_points_at = locate_head(refs, mapped_refs, &remote_head);
+               head_points_at = locate_head(refs, mapped_refs, &remote_head);
+       }
+       else {
+               warning("You appear to have cloned an empty repository.");
+               head_points_at = NULL;
+               remote_head = NULL;
+               option_no_checkout = 1;
+               if (!option_bare)
+                       install_branch_config("master", option_origin,
+                                             "refs/heads/master");
+       }
 
        if (head_points_at) {
                /* Local default branch link */
@@ -554,11 +579,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                                      head_points_at->peer_ref->name,
                                      reflog_msg.buf);
 
-                       strbuf_addf(&key, "branch.%s.remote", head);
-                       git_config_set(key.buf, option_origin);
-                       strbuf_reset(&key);
-                       strbuf_addf(&key, "branch.%s.merge", head);
-                       git_config_set(key.buf, head_points_at->name);
+                       install_branch_config(head, option_origin,
+                                             head_points_at->name);
                }
        } else if (remote_head) {
                /* Source had detached HEAD pointing somewhere. */
@@ -605,6 +627,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                if (write_cache(fd, active_cache, active_nr) ||
                    commit_locked_index(lock_file))
                        die("unable to write new index file");
+
+               err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
+                               sha1_to_hex(remote_head->old_sha1), "1", NULL);
        }
 
        strbuf_release(&reflog_msg);
@@ -612,5 +637,5 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        strbuf_release(&key);
        strbuf_release(&value);
        junk_pid = 0;
-       return 0;
+       return err;
 }
index 2f0b00a174ff970939d2758334cb8d1dca41cb42..46e649cd7ca0a2ede8f010a6d3bf294f81b85d55 100644 (file)
@@ -166,7 +166,7 @@ static int list_paths(struct string_list *list, const char *with_tree,
                struct cache_entry *ce = active_cache[i];
                if (ce->ce_flags & CE_UPDATE)
                        continue;
-               if (!pathspec_match(pattern, m, ce->name, 0))
+               if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m))
                        continue;
                string_list_insert(ce->name, list);
        }
@@ -361,40 +361,6 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int
        return s.commitable;
 }
 
-static int run_hook(const char *index_file, const char *name, ...)
-{
-       struct child_process hook;
-       const char *argv[10], *env[2];
-       char index[PATH_MAX];
-       va_list args;
-       int i;
-
-       va_start(args, name);
-       argv[0] = git_path("hooks/%s", name);
-       i = 0;
-       do {
-               if (++i >= ARRAY_SIZE(argv))
-                       die ("run_hook(): too many arguments");
-               argv[i] = va_arg(args, const char *);
-       } while (argv[i]);
-       va_end(args);
-
-       snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
-       env[0] = index;
-       env[1] = NULL;
-
-       if (access(argv[0], X_OK) < 0)
-               return 0;
-
-       memset(&hook, 0, sizeof(hook));
-       hook.argv = argv;
-       hook.no_stdin = 1;
-       hook.stdout_to_stderr = 1;
-       hook.env = env;
-
-       return run_command(&hook);
-}
-
 static int is_a_merge(const unsigned char *sha1)
 {
        struct commit *commit = lookup_commit(sha1);
@@ -595,7 +561,6 @@ static int prepare_to_commit(const char *index_file, const char *prefix)
                commitable = run_status(fp, index_file, prefix, 1);
                wt_status_use_color = saved_color_setting;
        } else {
-               struct rev_info rev;
                unsigned char sha1[20];
                const char *parent = "HEAD";
 
@@ -607,16 +572,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix)
 
                if (get_sha1(parent, sha1))
                        commitable = !!active_nr;
-               else {
-                       init_revisions(&rev, "");
-                       rev.abbrev = 0;
-                       setup_revisions(0, NULL, &rev, parent);
-                       DIFF_OPT_SET(&rev.diffopt, QUIET);
-                       DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS);
-                       run_diff_index(&rev, 1 /* cached */);
-
-                       commitable = !!DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES);
-               }
+               else
+                       commitable = index_differs_from(parent, 0);
        }
 
        fclose(fp);
@@ -883,7 +840,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
 {
        struct rev_info rev;
        struct commit *commit;
-       static const char *format = "format:%h: \"%s\"";
+       static const char *format = "format:%h] %s";
        unsigned char junk_sha1[20];
        const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL);
 
@@ -910,7 +867,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
        rev.diffopt.break_opt = 0;
        diff_setup_done(&rev.diffopt);
 
-       printf("[%s%s]: created ",
+       printf("[%s%s ",
                !prefixcmp(head, "refs/heads/") ?
                        head + 11 :
                        !strcmp(head, "HEAD") ?
index f71016204b540d0d935323c909a0ffccb1abdbe2..d52a05744487182cfcd9e974ad1fb8c65e3eca89 100644 (file)
@@ -3,7 +3,7 @@
 #include "color.h"
 
 static const char git_config_set_usage[] =
-"git config [ --global | --system | [ -f | --file ] config-file ] [ --bool | --int | --bool-or-int ] [ -z | --null ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --remove-section name | --list | --get-color var [default] | --get-colorbool name [stdout-is-tty]";
+"git config [ --global | --system | [ -f | --file ] config-file ] [ --bool | --int | --bool-or-int ] [ -z | --null ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --remove-section name | --list | --get-color var [default] | --get-colorbool name [stdout-is-tty] | --edit | -e ]";
 
 static char *key;
 static regex_t *key_regexp;
@@ -27,7 +27,7 @@ static int show_all_config(const char *key_, const char *value_, void *cb)
        return 0;
 }
 
-static int show_config(const char* key_, const char* value_, void *cb)
+static int show_config(const char *key_, const char *value_, void *cb)
 {
        char value[256];
        const char *vptr = value;
@@ -74,7 +74,7 @@ static int show_config(const char* key_, const char* value_, void *cb)
        return 0;
 }
 
-static int get_value(const char* key_, const char* regex_)
+static int get_value(const char *key_, const char *regex_)
 {
        int ret = -1;
        char *tl;
@@ -284,7 +284,7 @@ static int get_colorbool(int argc, const char **argv)
 int cmd_config(int argc, const char **argv, const char *prefix)
 {
        int nongit;
-       charvalue;
+       char *value;
        const char *file = setup_git_directory_gently(&nongit);
 
        config_exclusive_filename = getenv(CONFIG_ENVIRONMENT);
@@ -362,6 +362,14 @@ int cmd_config(int argc, const char **argv, const char *prefix)
                        return get_color(argc-2, argv+2);
                } else if (!strcmp(argv[1], "--get-colorbool")) {
                        return get_colorbool(argc-2, argv+2);
+               } else if (!strcmp(argv[1], "--edit") || !strcmp(argv[1], "-e")) {
+                       if (argc != 2)
+                               usage(git_config_set_usage);
+                       git_config(git_default_config, NULL);
+                       launch_editor(config_exclusive_filename ?
+                                     config_exclusive_filename : git_path("config"),
+                                     NULL, NULL);
+                       return 0;
                } else
                        break;
                argc--;
index ab35b65b073e9bc089fcc96f295ca3bc457c992a..b814fe5070873f5c87fc6bbfde480e3b0a83e397 100644 (file)
@@ -5,6 +5,7 @@
  */
 
 #include "cache.h"
+#include "dir.h"
 #include "builtin.h"
 #include "parse-options.h"
 
@@ -21,9 +22,7 @@ static void count_objects(DIR *d, char *path, int len, int verbose,
                const char *cp;
                int bad = 0;
 
-               if ((ent->d_name[0] == '.') &&
-                   (ent->d_name[1] == 0 ||
-                    ((ent->d_name[1] == '.') && (ent->d_name[2] == 0))))
+               if (is_dot_or_dotdot(ent->d_name))
                        continue;
                for (cp = ent->d_name; *cp; cp++) {
                        int ch = *cp;
@@ -61,7 +60,7 @@ static void count_objects(DIR *d, char *path, int len, int verbose,
                hex[40] = 0;
                if (get_sha1_hex(hex, sha1))
                        die("internal error");
-               if (has_sha1_pack(sha1, NULL))
+               if (has_sha1_pack(sha1))
                        (*packed_loose)++;
        }
 }
index 469b07e240953aa21fd67eb2563e094a7f0f3d42..29356d25db910c6d90df46da87aa374467611350 100644 (file)
@@ -2,6 +2,7 @@
 #include "cache.h"
 #include "refs.h"
 #include "commit.h"
+#include "sigchain.h"
 
 static char *get_stdin(void)
 {
@@ -186,7 +187,7 @@ static void remove_keep(void)
 static void remove_keep_on_signal(int signo)
 {
        remove_keep();
-       signal(SIGINT, SIG_DFL);
+       sigchain_pop(signo);
        raise(signo);
 }
 
@@ -245,7 +246,7 @@ static int fetch_native_store(FILE *fp,
        char buffer[1024];
        int err = 0;
 
-       signal(SIGINT, remove_keep_on_signal);
+       sigchain_push_common(remove_keep_on_signal);
        atexit(remove_keep);
 
        while (fgets(buffer, sizeof(buffer), stdin)) {
index 67fb80ec48d6d8f0327628afd86fb366d8f97617..c2e5adc8847e8ffa94bd009121b7a295f3f0512e 100644 (file)
@@ -216,9 +216,8 @@ static int find_common(int fd[2], unsigned char *result_sha1,
        if (args.depth > 0) {
                char line[1024];
                unsigned char sha1[20];
-               int len;
 
-               while ((len = packet_read_line(fd[0], line, sizeof(line)))) {
+               while (packet_read_line(fd[0], line, sizeof(line))) {
                        if (!prefixcmp(line, "shallow ")) {
                                if (get_sha1_hex(line + 8, sha1))
                                        die("invalid shallow line: %s", line);
index 7568163af24df630c215e05b6082ed764150a315..1e4a3d9c516c88d701819b7f4b73c722412d540f 100644 (file)
@@ -10,6 +10,7 @@
 #include "transport.h"
 #include "run-command.h"
 #include "parse-options.h"
+#include "sigchain.h"
 
 static const char * const builtin_fetch_usage[] = {
        "git fetch [options] [<repository> <refspec>...]",
@@ -58,7 +59,7 @@ static void unlock_pack(void)
 static void unlock_pack_on_signal(int signo)
 {
        unlock_pack();
-       signal(SIGINT, SIG_DFL);
+       sigchain_pop(signo);
        raise(signo);
 }
 
@@ -607,7 +608,7 @@ static void set_option(const char *name, const char *value)
 {
        int r = transport_set_option(transport, name, value);
        if (r < 0)
-               die("Option \"%s\" value \"%s\" is not valid for %s\n",
+               die("Option \"%s\" value \"%s\" is not valid for %s",
                        name, value, transport->url);
        if (r > 0)
                warning("Option \"%s\" is ignored for %s\n",
@@ -672,7 +673,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
                ref_nr = j;
        }
 
-       signal(SIGINT, unlock_pack_on_signal);
+       sigchain_push_common(unlock_pack_on_signal);
        atexit(unlock_pack);
        exit_code = do_fetch(transport,
                        parse_fetch_refspec(ref_nr, refs), ref_nr);
index df18f4070f3877ed907476f2179590001ec972b7..a7883690d74cb1bcef7b20f27ebbea6f6c5dcc53 100644 (file)
@@ -256,8 +256,7 @@ static void shortlog(const char *name, unsigned char *sha1,
 
 int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) {
        int limit = 20, i = 0, pos = 0;
-       char line[1024];
-       char *p = line, *sep = "";
+       char *sep = "";
        unsigned char head_sha1[20];
        const char *current_branch;
 
@@ -271,9 +270,8 @@ int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) {
        /* get a line */
        while (pos < in->len) {
                int len;
-               char *newline;
+               char *newline, *p = in->buf + pos;
 
-               p = in->buf + pos;
                newline = strchr(p, '\n');
                len = newline ? newline - p : strlen(p);
                pos += len + !!newline;
index 5c4c77adaaba36693e304c27d1f171f8e7e9f151..6436bc224840f11af2f7fa26c61b62c25d78d865 100644 (file)
@@ -10,6 +10,7 @@
 #include "tree-walk.h"
 #include "fsck.h"
 #include "parse-options.h"
+#include "dir.h"
 
 #define REACHABLE 0x0001
 #define SEEN      0x0002
@@ -22,6 +23,7 @@ static int check_full;
 static int check_strict;
 static int keep_cache_objects;
 static unsigned char head_sha1[20];
+static const char *head_points_at;
 static int errors_found;
 static int write_lost_and_found;
 static int verbose;
@@ -158,7 +160,7 @@ static void check_reachable_object(struct object *obj)
         * do a full fsck
         */
        if (!obj->parsed) {
-               if (has_sha1_pack(obj->sha1, NULL))
+               if (has_sha1_pack(obj->sha1))
                        return; /* it is in pack - forget about it */
                printf("missing %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1));
                errors_found |= ERROR_REACHABLE;
@@ -395,19 +397,12 @@ static void fsck_dir(int i, char *path)
        while ((de = readdir(dir)) != NULL) {
                char name[100];
                unsigned char sha1[20];
-               int len = strlen(de->d_name);
 
-               switch (len) {
-               case 2:
-                       if (de->d_name[1] != '.')
-                               break;
-               case 1:
-                       if (de->d_name[0] != '.')
-                               break;
+               if (is_dot_or_dotdot(de->d_name))
                        continue;
-               case 38:
+               if (strlen(de->d_name) == 38) {
                        sprintf(name, "%02x", i);
-                       memcpy(name+2, de->d_name, len+1);
+                       memcpy(name+2, de->d_name, 39);
                        if (get_sha1_hex(name, sha1) < 0)
                                break;
                        add_sha1_list(sha1, DIRENT_SORT_HINT(de));
@@ -479,6 +474,8 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int f
 
 static void get_default_heads(void)
 {
+       if (head_points_at && !is_null_sha1(head_sha1))
+               fsck_handle_ref("HEAD", head_sha1, 0, NULL);
        for_each_ref(fsck_handle_ref, NULL);
        if (include_reflogs)
                for_each_reflog(fsck_handle_reflog, NULL);
@@ -518,14 +515,13 @@ static void fsck_object_dir(const char *path)
 
 static int fsck_head_link(void)
 {
-       unsigned char sha1[20];
        int flag;
        int null_is_error = 0;
-       const char *head_points_at = resolve_ref("HEAD", sha1, 0, &flag);
 
        if (verbose)
                fprintf(stderr, "Checking HEAD link\n");
 
+       head_points_at = resolve_ref("HEAD", head_sha1, 0, &flag);
        if (!head_points_at)
                return error("Invalid HEAD");
        if (!strcmp(head_points_at, "HEAD"))
@@ -534,7 +530,7 @@ static int fsck_head_link(void)
        else if (prefixcmp(head_points_at, "refs/heads/"))
                return error("HEAD points to something strange (%s)",
                             head_points_at);
-       if (is_null_sha1(sha1)) {
+       if (is_null_sha1(head_sha1)) {
                if (null_is_error)
                        return error("HEAD: detached HEAD points at nothing");
                fprintf(stderr, "notice: HEAD points to an unborn branch (%s)\n",
@@ -590,6 +586,7 @@ static struct option fsck_opts[] = {
 int cmd_fsck(int argc, const char **argv, const char *prefix)
 {
        int i, heads;
+       struct alternate_object_database *alt;
 
        errors_found = 0;
 
@@ -601,17 +598,19 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
 
        fsck_head_link();
        fsck_object_dir(get_object_directory());
+
+       prepare_alt_odb();
+       for (alt = alt_odb_list; alt; alt = alt->next) {
+               char namebuf[PATH_MAX];
+               int namelen = alt->name - alt->base;
+               memcpy(namebuf, alt->base, namelen);
+               namebuf[namelen - 1] = 0;
+               fsck_object_dir(namebuf);
+       }
+
        if (check_full) {
-               struct alternate_object_database *alt;
                struct packed_git *p;
-               prepare_alt_odb();
-               for (alt = alt_odb_list; alt; alt = alt->next) {
-                       char namebuf[PATH_MAX];
-                       int namelen = alt->name - alt->base;
-                       memcpy(namebuf, alt->base, namelen);
-                       namebuf[namelen - 1] = 0;
-                       fsck_object_dir(namebuf);
-               }
+
                prepare_packed_git();
                for (p = packed_git; p; p = p->next)
                        /* verify gives error messages itself */
@@ -630,8 +629,9 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
        heads = 0;
        for (i = 0; i < argc; i++) {
                const char *arg = argv[i];
-               if (!get_sha1(arg, head_sha1)) {
-                       struct object *obj = lookup_object(head_sha1);
+               unsigned char sha1[20];
+               if (!get_sha1(arg, sha1)) {
+                       struct object *obj = lookup_object(sha1);
 
                        /* Error is printed by lookup_object(). */
                        if (!obj)
index f8eae4adb41d9c338c07aa161e7305bb16742a1e..8d990ed4935804591a17c864e280c0c9b4b7597f 100644 (file)
@@ -144,34 +144,6 @@ static int too_many_packs(void)
        return gc_auto_pack_limit <= cnt;
 }
 
-static int run_hook(void)
-{
-       const char *argv[2];
-       struct child_process hook;
-       int ret;
-
-       argv[0] = git_path("hooks/pre-auto-gc");
-       argv[1] = NULL;
-
-       if (access(argv[0], X_OK) < 0)
-               return 0;
-
-       memset(&hook, 0, sizeof(hook));
-       hook.argv = argv;
-       hook.no_stdin = 1;
-       hook.stdout_to_stderr = 1;
-
-       ret = start_command(&hook);
-       if (ret) {
-               warning("Could not spawn %s", argv[0]);
-               return ret;
-       }
-       ret = finish_command(&hook);
-       if (ret == -ERR_RUN_COMMAND_WAITPID_SIGNAL)
-               warning("%s exited due to uncaught signal", argv[0]);
-       return ret;
-}
-
 static int need_to_gc(void)
 {
        /*
@@ -189,26 +161,28 @@ static int need_to_gc(void)
         */
        if (too_many_packs())
                append_option(argv_repack,
-                             !strcmp(prune_expire, "now") ? "-a" : "-A",
+                             prune_expire && !strcmp(prune_expire, "now") ?
+                             "-a" : "-A",
                              MAX_ADD);
        else if (!too_many_loose_objects())
                return 0;
 
-       if (run_hook())
+       if (run_hook(NULL, "pre-auto-gc", NULL))
                return 0;
        return 1;
 }
 
 int cmd_gc(int argc, const char **argv, const char *prefix)
 {
-       int prune = 0;
        int aggressive = 0;
        int auto_gc = 0;
        int quiet = 0;
        char buf[80];
 
        struct option builtin_gc_options[] = {
-               OPT_BOOLEAN(0, "prune", &prune, "prune unreferenced objects (deprecated)"),
+               { OPTION_STRING, 0, "prune", &prune_expire, "date",
+                       "prune unreferenced objects",
+                       PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire },
                OPT_BOOLEAN(0, "aggressive", &aggressive, "be more thorough (increased runtime)"),
                OPT_BOOLEAN(0, "auto", &auto_gc, "enable auto-gc mode"),
                OPT_BOOLEAN('q', "quiet", &quiet, "suppress progress reports"),
@@ -246,7 +220,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
                        "\"git help gc\" for more information.\n");
        } else
                append_option(argv_repack,
-                             !strcmp(prune_expire, "now") ? "-a" : "-A",
+                             prune_expire && !strcmp(prune_expire, "now")
+                             ? "-a" : "-A",
                              MAX_ADD);
 
        if (pack_refs && run_command_v_opt(argv_pack_refs, RUN_GIT_CMD))
@@ -258,9 +233,11 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
        if (run_command_v_opt(argv_repack, RUN_GIT_CMD))
                return error(FAILED_RUN, argv_repack[0]);
 
-       argv_prune[2] = prune_expire;
-       if (run_command_v_opt(argv_prune, RUN_GIT_CMD))
-               return error(FAILED_RUN, argv_prune[0]);
+       if (prune_expire) {
+               argv_prune[2] = prune_expire;
+               if (run_command_v_opt(argv_prune, RUN_GIT_CMD))
+                       return error(FAILED_RUN, argv_prune[0]);
+       }
 
        if (run_command_v_opt(argv_rerere, RUN_GIT_CMD))
                return error(FAILED_RUN, argv_rerere[0]);
index f076efa9211ddcffdc81ffd7c0c56fdb197a11b0..9b57a746185719eb43c962c4adb96283657692e9 100644 (file)
@@ -329,7 +329,7 @@ static void setup_man_path(void)
         * old_path, the ':' at the end will let 'man' to try
         * system-wide paths after ours to find the manual page. If
         * there is old_path, we need ':' as delimiter. */
-       strbuf_addstr(&new_path, GIT_MAN_PATH);
+       strbuf_addstr(&new_path, system_path(GIT_MAN_PATH));
        strbuf_addch(&new_path, ':');
        if (old_path)
                strbuf_addstr(&new_path, old_path);
@@ -375,7 +375,7 @@ static void show_man_page(const char *git_cmd)
 static void show_info_page(const char *git_cmd)
 {
        const char *page = cmd_to_page(git_cmd);
-       setenv("INFOPATH", GIT_INFO_PATH, 1);
+       setenv("INFOPATH", system_path(GIT_INFO_PATH), 1);
        execlp("info", "info", "gitman", page, NULL);
 }
 
index d30c3fe2ca542b061ab8b7a7696cdd5416e17147..ee3911f8eef5c00eeb4868c843f2b8c5c83d688d 100644 (file)
@@ -29,7 +29,7 @@ static void safe_create_dir(const char *dir, int share)
                }
        }
        else if (share && adjust_shared_perm(dir))
-               die("Could not make %s writable by group\n", dir);
+               die("Could not make %s writable by group", dir);
 }
 
 static void copy_templates_1(char *path, int baselen,
index 60f8dd86048fd3a4ec51d7296b85984a0cefd602..8684fcdb67fc0216ffc860f60f5c060f10907b83 100644 (file)
@@ -16,6 +16,8 @@
 #include "patch-ids.h"
 #include "run-command.h"
 #include "shortlog.h"
+#include "remote.h"
+#include "string-list.h"
 
 /* Set a default date-time format for git log ("log.date" config variable) */
 static const char *default_date_mode = NULL;
@@ -249,22 +251,13 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix)
 
 static void show_tagger(char *buf, int len, struct rev_info *rev)
 {
-       char *email_end, *p;
-       unsigned long date;
-       int tz;
+       struct strbuf out = STRBUF_INIT;
 
-       email_end = memchr(buf, '>', len);
-       if (!email_end)
-               return;
-       p = ++email_end;
-       while (isspace(*p))
-               p++;
-       date = strtoul(p, &p, 10);
-       while (isspace(*p))
-               p++;
-       tz = (int)strtol(p, NULL, 10);
-       printf("Tagger: %.*s\nDate:   %s\n", (int)(email_end - buf), buf,
-              show_date(date, tz, rev->date_mode));
+       pp_user_info("Tagger", rev->commit_format, &out, buf, rev->date_mode,
+               git_log_output_encoding ?
+               git_log_output_encoding: git_commit_encoding);
+       printf("%s\n", out.buf);
+       strbuf_release(&out);
 }
 
 static int show_object(const unsigned char *sha1, int show_tag_object,
@@ -436,6 +429,8 @@ static const char *fmt_patch_suffix = ".patch";
 static int numbered = 0;
 static int auto_number = 1;
 
+static char *default_attach = NULL;
+
 static char **extra_hdr;
 static int extra_hdr_nr;
 static int extra_hdr_alloc;
@@ -467,6 +462,10 @@ static void add_header(const char *value)
        extra_hdr[extra_hdr_nr++] = xstrndup(value, len);
 }
 
+#define THREAD_SHALLOW 1
+#define THREAD_DEEP 2
+static int thread = 0;
+
 static int git_format_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "format.headers")) {
@@ -496,6 +495,25 @@ static int git_format_config(const char *var, const char *value, void *cb)
                auto_number = auto_number && numbered;
                return 0;
        }
+       if (!strcmp(var, "format.attach")) {
+               if (value && *value)
+                       default_attach = xstrdup(value);
+               else
+                       default_attach = xstrdup(git_version_string);
+               return 0;
+       }
+       if (!strcmp(var, "format.thread")) {
+               if (value && !strcasecmp(value, "deep")) {
+                       thread = THREAD_DEEP;
+                       return 0;
+               }
+               if (value && !strcasecmp(value, "shallow")) {
+                       thread = THREAD_SHALLOW;
+                       return 0;
+               }
+               thread = git_config_bool(var, value) && THREAD_SHALLOW;
+               return 0;
+       }
 
        return git_log_config(var, value, cb);
 }
@@ -774,7 +792,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        int numbered_files = 0;         /* _just_ numbers */
        int subject_prefix = 0;
        int ignore_if_in_upstream = 0;
-       int thread = 0;
        int cover_letter = 0;
        int boundary_count = 0;
        int no_binary_diff = 0;
@@ -795,6 +812,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 
        rev.subject_prefix = fmt_patch_subject_prefix;
 
+       if (default_attach) {
+               rev.mime_boundary = default_attach;
+               rev.no_inline = 1;
+       }
+
        /*
         * Parse the arguments before setup_revisions(), or something
         * like "git format-patch -o a123 HEAD^.." may fail; a123 is
@@ -846,7 +868,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                        committer = git_committer_info(IDENT_ERROR_ON_NO_NAME);
                        endpos = strchr(committer, '>');
                        if (!endpos)
-                               die("bogus committer info %s\n", committer);
+                               die("bogus committer info %s", committer);
                        add_signoff = xmemdupz(committer, endpos - committer + 1);
                }
                else if (!strcmp(argv[i], "--attach")) {
@@ -857,6 +879,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                        rev.mime_boundary = argv[i] + 9;
                        rev.no_inline = 1;
                }
+               else if (!strcmp(argv[i], "--no-attach")) {
+                       rev.mime_boundary = NULL;
+                       rev.no_inline = 0;
+               }
                else if (!strcmp(argv[i], "--inline")) {
                        rev.mime_boundary = git_version_string;
                        rev.no_inline = 0;
@@ -867,8 +893,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                }
                else if (!strcmp(argv[i], "--ignore-if-in-upstream"))
                        ignore_if_in_upstream = 1;
-               else if (!strcmp(argv[i], "--thread"))
-                       thread = 1;
+               else if (!strcmp(argv[i], "--thread")
+                       || !strcmp(argv[i], "--thread=shallow"))
+                       thread = THREAD_SHALLOW;
+               else if (!strcmp(argv[i], "--thread=deep"))
+                       thread = THREAD_DEEP;
+               else if (!strcmp(argv[i], "--no-thread"))
+                       thread = 0;
                else if (!prefixcmp(argv[i], "--in-reply-to="))
                        in_reply_to = argv[i] + 14;
                else if (!strcmp(argv[i], "--in-reply-to")) {
@@ -1019,8 +1050,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                numbered = 1;
        if (numbered)
                rev.total = total + start_number - 1;
-       if (in_reply_to)
-               rev.ref_message_id = clean_message_id(in_reply_to);
+       if (in_reply_to || thread || cover_letter)
+               rev.ref_message_ids = xcalloc(1, sizeof(struct string_list));
+       if (in_reply_to) {
+               const char *msgid = clean_message_id(in_reply_to);
+               string_list_append(msgid, rev.ref_message_ids);
+       }
        if (cover_letter) {
                if (thread)
                        gen_message_id(&rev, "cover");
@@ -1039,15 +1074,33 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                        /* Have we already had a message ID? */
                        if (rev.message_id) {
                                /*
-                                * If we've got the ID to be a reply
-                                * to, discard the current ID;
-                                * otherwise, make everything a reply
-                                * to that.
+                                * For deep threading: make every mail
+                                * a reply to the previous one, no
+                                * matter what other options are set.
+                                *
+                                * For shallow threading:
+                                *
+                                * Without --cover-letter and
+                                * --in-reply-to, make every mail a
+                                * reply to the one before.
+                                *
+                                * With --in-reply-to but no
+                                * --cover-letter, make every mail a
+                                * reply to the <reply-to>.
+                                *
+                                * With --cover-letter, make every
+                                * mail but the cover letter a reply
+                                * to the cover letter.  The cover
+                                * letter is a reply to the
+                                * --in-reply-to, if specified.
                                 */
-                               if (rev.ref_message_id)
+                               if (thread == THREAD_SHALLOW
+                                   && rev.ref_message_ids->nr > 0
+                                   && (!cover_letter || rev.nr > 1))
                                        free(rev.message_id);
                                else
-                                       rev.ref_message_id = rev.message_id;
+                                       string_list_append(rev.message_id,
+                                                          rev.ref_message_ids);
                        }
                        gen_message_id(&rev, sha1_to_hex(commit->object.sha1));
                }
@@ -1099,13 +1152,14 @@ static int add_pending_commit(const char *arg, struct rev_info *revs, int flags)
 }
 
 static const char cherry_usage[] =
-"git cherry [-v] <upstream> [<head>] [<limit>]";
+"git cherry [-v] [<upstream> [<head> [<limit>]]]";
 int cmd_cherry(int argc, const char **argv, const char *prefix)
 {
        struct rev_info revs;
        struct patch_ids ids;
        struct commit *commit;
        struct commit_list *list = NULL;
+       struct branch *current_branch;
        const char *upstream;
        const char *head = "HEAD";
        const char *limit = NULL;
@@ -1128,7 +1182,17 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
                upstream = argv[1];
                break;
        default:
-               usage(cherry_usage);
+               current_branch = branch_get(NULL);
+               if (!current_branch || !current_branch->merge
+                                       || !current_branch->merge[0]
+                                       || !current_branch->merge[0]->dst) {
+                       fprintf(stderr, "Could not find a tracked"
+                                       " remote branch, please"
+                                       " specify <upstream> manually.\n");
+                       usage(cherry_usage);
+               }
+
+               upstream = current_branch->merge[0]->dst;
        }
 
        init_revisions(&revs, prefix);
index f72eb854756f602e4d114964f4585bc5a8c55e20..9dec282fba6ba22e37d13bdc5e35fa4a031433b5 100644 (file)
@@ -36,42 +36,6 @@ static const char *tag_other = "";
 static const char *tag_killed = "";
 static const char *tag_modified = "";
 
-
-/*
- * Match a pathspec against a filename. The first "skiplen" characters
- * are the common prefix
- */
-int pathspec_match(const char **spec, char *ps_matched,
-                  const char *filename, int skiplen)
-{
-       const char *m;
-
-       while ((m = *spec++) != NULL) {
-               int matchlen = strlen(m + skiplen);
-
-               if (!matchlen)
-                       goto matched;
-               if (!strncmp(m + skiplen, filename + skiplen, matchlen)) {
-                       if (m[skiplen + matchlen - 1] == '/')
-                               goto matched;
-                       switch (filename[skiplen + matchlen]) {
-                       case '/': case '\0':
-                               goto matched;
-                       }
-               }
-               if (!fnmatch(m + skiplen, filename + skiplen, 0))
-                       goto matched;
-               if (ps_matched)
-                       ps_matched++;
-               continue;
-       matched:
-               if (ps_matched)
-                       *ps_matched = 1;
-               return 1;
-       }
-       return 0;
-}
-
 static void show_dir_entry(const char *tag, struct dir_entry *ent)
 {
        int len = prefix_len;
@@ -80,7 +44,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent)
        if (len >= ent->len)
                die("git ls-files: internal error - directory entry not superset of prefix");
 
-       if (pathspec && !pathspec_match(pathspec, ps_matched, ent->name, len))
+       if (!match_pathspec(pathspec, ent->name, ent->len, len, ps_matched))
                return;
 
        fputs(tag, stdout);
@@ -156,7 +120,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
        if (len >= ce_namelen(ce))
                die("git ls-files: internal error - cache entry not superset of prefix");
 
-       if (pathspec && !pathspec_match(pathspec, ps_matched, ce->name, len))
+       if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), len, ps_matched))
                return;
 
        if (tag && *tag && show_valid_bit &&
@@ -298,6 +262,21 @@ static const char *verify_pathspec(const char *prefix)
        return max ? xmemdupz(prev, max) : NULL;
 }
 
+static void strip_trailing_slash_from_submodules(void)
+{
+       const char **p;
+
+       for (p = pathspec; *p != NULL; p++) {
+               int len = strlen(*p), pos;
+
+               if (len < 1 || (*p)[len - 1] != '/')
+                       continue;
+               pos = cache_name_pos(*p, len - 1);
+               if (pos >= 0 && S_ISGITLINK(active_cache[pos]->ce_mode))
+                       *p = xstrndup(*p, len - 1);
+       }
+}
+
 /*
  * Read the tree specified with --with-tree option
  * (typically, HEAD) into stage #1 and then
@@ -546,6 +525,11 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
 
        pathspec = get_pathspec(prefix, argv + i);
 
+       /* be nice with submodule patsh ending in a slash */
+       read_cache();
+       if (pathspec)
+               strip_trailing_slash_from_submodules();
+
        /* Verify that the pathspec matches the prefix */
        if (pathspec)
                prefix = verify_pathspec(prefix);
@@ -569,7 +553,6 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
              show_killed | show_modified))
                show_cached = 1;
 
-       read_cache();
        if (prefix)
                prune_cache(prefix);
        if (with_tree) {
index 5b63e6eada5cd6de764acef694da624a70ce6dab..fca46312f681caf077565fb4bd5b59a70008eb59 100644 (file)
@@ -68,13 +68,8 @@ static int show_tree(const unsigned char *sha1, const char *base, int baselen,
                 *
                 * Something similar to this incomplete example:
                 *
-               if (show_subprojects(base, baselen, pathname)) {
-                       struct child_process ls_tree;
-
-                       ls_tree.dir = base;
-                       ls_tree.argv = ls-tree;
-                       start_command(&ls_tree);
-               }
+               if (show_subprojects(base, baselen, pathname))
+                       retval = READ_TREE_RECURSIVE;
                 *
                 */
                type = commit_type;
index e890f7a6d1ff7248aed4f03ebbcdfafd7e472dad..2789ccdf7dd43a1170a1ca28a3e4d4802422e719 100644 (file)
@@ -29,6 +29,9 @@ static struct strbuf **p_hdr_data, **s_hdr_data;
 #define MAX_HDR_PARSED 10
 #define MAX_BOUNDARIES 5
 
+static void cleanup_space(struct strbuf *sb);
+
+
 static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email)
 {
        struct strbuf *src = name;
@@ -109,11 +112,19 @@ static void handle_from(const struct strbuf *from)
        strbuf_add(&email, at, el);
        strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0));
 
-       /* The remainder is name.  It could be "John Doe <john.doe@xz>"
-        * or "john.doe@xz (John Doe)", but we have removed the
-        * email part, so trim from both ends, possibly removing
-        * the () pair at the end.
+       /* The remainder is name.  It could be
+        *
+        * - "John Doe <john.doe@xz>"                   (a), or
+        * - "john.doe@xz (John Doe)"                   (b), or
+        * - "John (zzz) Doe <john.doe@xz> (Comment)"   (c)
+        *
+        * but we have removed the email part, so
+        *
+        * - remove extra spaces which could stay after email (case 'c'), and
+        * - trim from both ends, possibly removing the () pair at the end
+        *   (cases 'a' and 'b').
         */
+       cleanup_space(&f);
        strbuf_trim(&f);
        if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') {
                strbuf_remove(&f, 0, 1);
@@ -430,13 +441,6 @@ static struct strbuf *decode_b_segment(const struct strbuf *b_seg)
                        c -= 'a' - 26;
                else if ('0' <= c && c <= '9')
                        c -= '0' - 52;
-               else if (c == '=') {
-                       /* padding is almost like (c == 0), except we do
-                        * not output NUL resulting only from it;
-                        * for now we just trust the data.
-                        */
-                       c = 0;
-               }
                else
                        continue; /* garbage */
                switch (pos++) {
@@ -494,7 +498,7 @@ static void convert_to_utf8(struct strbuf *line, const char *charset)
                return;
        out = reencode_string(line->buf, metainfo_charset, charset);
        if (!out)
-               die("cannot convert from %s to %s\n",
+               die("cannot convert from %s to %s",
                    charset, metainfo_charset);
        strbuf_attach(line, out, strlen(out), strlen(out));
 }
@@ -514,7 +518,25 @@ static int decode_header_bq(struct strbuf *it)
                rfc2047 = 1;
 
                if (in != ep) {
-                       strbuf_add(&outbuf, in, ep - in);
+                       /*
+                        * We are about to process an encoded-word
+                        * that begins at ep, but there is something
+                        * before the encoded word.
+                        */
+                       char *scan;
+                       for (scan = in; scan < ep; scan++)
+                               if (!isspace(*scan))
+                                       break;
+
+                       if (scan != ep || in == it->buf) {
+                               /*
+                                * We should not lose that "something",
+                                * unless we have just processed an
+                                * encoded-word, and there is only LWS
+                                * before the one we are about to process.
+                                */
+                               strbuf_add(&outbuf, in, ep - in);
+                       }
                        in = ep;
                }
                /* E.g.
@@ -860,6 +882,7 @@ static void handle_info(void)
                        }
                        output_header_lines(fout, "Subject", hdr);
                } else if (!memcmp(header[i], "From", 4)) {
+                       cleanup_space(hdr);
                        handle_from(hdr);
                        fprintf(fout, "Author: %s\n", name.buf);
                        fprintf(fout, "Email: %s\n", email.buf);
index 6b534c1a66bf7a4abbe0f38add7585dee65a6a44..703045bfc84a804f5cf58537117fb17859951f7e 100644 (file)
@@ -33,7 +33,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
        }
 
        if (argc < 4)
-               die("Usage: %s <base>... -- <head> <remote> ...\n", argv[0]);
+               die("Usage: %s <base>... -- <head> <remote> ...", argv[0]);
 
        for (i = 1; i < argc; ++i) {
                if (!strcmp(argv[i], "--"))
index cf869751b41c256363bf5f0c465e46684e2920b8..6d2160d0a3db1705d1372029dfa24752d453c8ec 100644 (file)
@@ -36,8 +36,8 @@ struct strategy {
 };
 
 static const char * const builtin_merge_usage[] = {
-       "git-merge [options] <remote>...",
-       "git-merge [options] <msg> HEAD <remote>",
+       "git merge [options] <remote>...",
+       "git merge [options] <msg> HEAD <remote>",
        NULL
 };
 
@@ -300,35 +300,6 @@ static void squash_message(void)
        strbuf_release(&out);
 }
 
-static int run_hook(const char *name)
-{
-       struct child_process hook;
-       const char *argv[3], *env[2];
-       char index[PATH_MAX];
-
-       argv[0] = git_path("hooks/%s", name);
-       if (access(argv[0], X_OK) < 0)
-               return 0;
-
-       snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", get_index_file());
-       env[0] = index;
-       env[1] = NULL;
-
-       if (squash)
-               argv[1] = "1";
-       else
-               argv[1] = "0";
-       argv[2] = NULL;
-
-       memset(&hook, 0, sizeof(hook));
-       hook.argv = argv;
-       hook.no_stdin = 1;
-       hook.stdout_to_stderr = 1;
-       hook.env = env;
-
-       return run_command(&hook);
-}
-
 static void finish(const unsigned char *new_head, const char *msg)
 {
        struct strbuf reflog_message = STRBUF_INIT;
@@ -374,7 +345,7 @@ static void finish(const unsigned char *new_head, const char *msg)
        }
 
        /* Run a post-merge hook */
-       run_hook("post-merge");
+       run_hook(NULL, "post-merge", squash ? "1" : "0", NULL);
 
        strbuf_release(&reflog_message);
 }
@@ -385,9 +356,14 @@ static void merge_name(const char *remote, struct strbuf *msg)
        struct object *remote_head;
        unsigned char branch_head[20], buf_sha[20];
        struct strbuf buf = STRBUF_INIT;
+       struct strbuf bname = STRBUF_INIT;
        const char *ptr;
        int len, early;
 
+       len = strlen(remote);
+       if (interpret_nth_last_branch(remote, &bname) == len)
+               remote = bname.buf;
+
        memset(branch_head, 0, sizeof(branch_head));
        remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
        if (!remote_head)
@@ -400,7 +376,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
        if (!hashcmp(remote_head->sha1, branch_head)) {
                strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
                        sha1_to_hex(branch_head), remote);
-               return;
+               goto cleanup;
        }
 
        /* See if remote matches <name>^^^.. or <name>~<number> */
@@ -440,7 +416,8 @@ static void merge_name(const char *remote, struct strbuf *msg)
                                    sha1_to_hex(remote_head->sha1),
                                    truname.buf + 11,
                                    (early ? " (early part)" : ""));
-                       return;
+                       strbuf_release(&truname);
+                       goto cleanup;
                }
        }
 
@@ -461,10 +438,13 @@ static void merge_name(const char *remote, struct strbuf *msg)
                        strbuf_remove(&line, ptr-line.buf+1, 13);
                strbuf_addbuf(msg, &line);
                strbuf_release(&line);
-               return;
+               goto cleanup;
        }
        strbuf_addf(msg, "%s\t\tcommit '%s'\n",
                sha1_to_hex(remote_head->sha1), remote);
+cleanup:
+       strbuf_release(&buf);
+       strbuf_release(&bname);
 }
 
 static int git_merge_config(const char *k, const char *v, void *cb)
index b616994f4587b9a3d0133b7d6694020743ea0a4d..1c6d2c498bae77aab0aaf32ba6f857fbb7ead44f 100644 (file)
@@ -78,7 +78,7 @@ static int progress = 1;
 static int window = 10;
 static uint32_t pack_size_limit, pack_size_limit_cfg;
 static int depth = 50;
-static int delta_search_threads = 1;
+static int delta_search_threads;
 static int pack_to_stdout;
 static int num_preferred_base;
 static struct progress *progress_state;
@@ -488,9 +488,8 @@ static void write_pack_file(void)
                } else {
                        char tmpname[PATH_MAX];
                        int fd;
-                       snprintf(tmpname, sizeof(tmpname),
-                                "%s/pack/tmp_pack_XXXXXX", get_object_directory());
-                       fd = xmkstemp(tmpname);
+                       fd = odb_mkstemp(tmpname, sizeof(tmpname),
+                                        "pack/tmp_pack_XXXXXX");
                        pack_tmp_name = xstrdup(tmpname);
                        f = sha1fd(fd, pack_tmp_name);
                }
@@ -1612,11 +1611,18 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
                find_deltas(list, &list_size, window, depth, processed);
                return;
        }
+       if (progress > pack_to_stdout)
+               fprintf(stderr, "Delta compression using %d threads.\n",
+                               delta_search_threads);
 
        /* Partition the work amongst work threads. */
        for (i = 0; i < delta_search_threads; i++) {
                unsigned sub_size = list_size / (delta_search_threads - i);
 
+               /* don't use too small segments or no deltas will be found */
+               if (sub_size < 2*window && i+1 < delta_search_threads)
+                       sub_size = 0;
+
                p[i].window = window;
                p[i].depth = depth;
                p[i].processed = processed;
@@ -1960,11 +1966,7 @@ static void add_objects_in_unpacked_packs(struct rev_info *revs)
                const unsigned char *sha1;
                struct object *o;
 
-               for (i = 0; i < revs->num_ignore_packed; i++) {
-                       if (matches_pack_name(p, revs->ignore_packed[i]))
-                               break;
-               }
-               if (revs->num_ignore_packed <= i)
+               if (p->pack_keep)
                        continue;
                if (open_pack_index(p))
                        die("cannot open pack index");
@@ -2000,11 +2002,7 @@ static void loosen_unused_packed_objects(struct rev_info *revs)
        const unsigned char *sha1;
 
        for (p = packed_git; p; p = p->next) {
-               for (i = 0; i < revs->num_ignore_packed; i++) {
-                       if (matches_pack_name(p, revs->ignore_packed[i]))
-                               break;
-               }
-               if (revs->num_ignore_packed <= i)
+               if (p->pack_keep)
                        continue;
 
                if (open_pack_index(p))
@@ -2202,7 +2200,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                        continue;
                }
                if (!strcmp("--unpacked", arg) ||
-                   !prefixcmp(arg, "--unpacked=") ||
+                   !strcmp("--kept-pack-only", arg) ||
                    !strcmp("--reflog", arg) ||
                    !strcmp("--all", arg)) {
                        use_internal_rev_list = 1;
index 10cb8df8457fd5f2ba9be62ecd0f9384e21c3e63..2d5b2cd353a5e80a74e3828aae8e0c7dfa0e1997 100644 (file)
@@ -23,7 +23,7 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts)
                memcpy(hex+2, de->d_name, 38);
                if (get_sha1_hex(hex, sha1))
                        continue;
-               if (!has_sha1_pack(sha1, NULL))
+               if (!has_sha1_pack(sha1))
                        continue;
                memcpy(pathname + len, de->d_name, 38);
                if (opts & DRY_RUN)
index 7b4ec80e62997fe70f53c92380cd4d66e80d62a0..545e9c1f9458ed936e2a99a208f75b70f368294c 100644 (file)
@@ -5,6 +5,7 @@
 #include "builtin.h"
 #include "reachable.h"
 #include "parse-options.h"
+#include "dir.h"
 
 static const char * const prune_usage[] = {
        "git prune [-n] [-v] [--expire <time>] [--] [<head>...]",
@@ -61,19 +62,12 @@ static int prune_dir(int i, char *path)
        while ((de = readdir(dir)) != NULL) {
                char name[100];
                unsigned char sha1[20];
-               int len = strlen(de->d_name);
 
-               switch (len) {
-               case 2:
-                       if (de->d_name[1] != '.')
-                               break;
-               case 1:
-                       if (de->d_name[0] != '.')
-                               break;
+               if (is_dot_or_dotdot(de->d_name))
                        continue;
-               case 38:
+               if (strlen(de->d_name) == 38) {
                        sprintf(name, "%02x", i);
-                       memcpy(name+2, de->d_name, len+1);
+                       memcpy(name+2, de->d_name, 39);
                        if (get_sha1_hex(name, sha1) < 0)
                                break;
 
index db67c3162c6b8ffaba793fd6082324aeae158f86..a970b39505b5dda0bfa32a1a9c14ad676d28ec92 100644 (file)
@@ -9,22 +9,25 @@
 #include "remote.h"
 #include "transport.h"
 
-static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
+static const char receive_pack_usage[] = "git receive-pack <git-dir>";
 
 enum deny_action {
+       DENY_UNCONFIGURED,
        DENY_IGNORE,
        DENY_WARN,
        DENY_REFUSE,
 };
 
-static int deny_deletes = 0;
-static int deny_non_fast_forwards = 0;
-static enum deny_action deny_current_branch = DENY_WARN;
+static int deny_deletes;
+static int deny_non_fast_forwards;
+static enum deny_action deny_current_branch = DENY_UNCONFIGURED;
+static enum deny_action deny_delete_current = DENY_UNCONFIGURED;
 static int receive_fsck_objects;
 static int receive_unpack_limit = -1;
 static int transfer_unpack_limit = -1;
 static int unpack_limit = 100;
 static int report_status;
+static const char *head_name;
 
 static char capabilities[] = " report-status delete-refs ";
 static int capabilities_sent;
@@ -76,6 +79,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
+       if (strcmp(var, "receive.denydeletecurrent") == 0) {
+               deny_delete_current = parse_deny_action(var, value);
+               return 0;
+       }
+
        return git_default_config(var, value, cb);
 }
 
@@ -136,7 +144,7 @@ static int hook_status(int code, const char *hook_name)
        }
 }
 
-static int run_hook(const char *hook_name)
+static int run_receive_hook(const char *hook_name)
 {
        static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4];
        struct command *cmd;
@@ -202,16 +210,67 @@ static int run_update_hook(struct command *cmd)
 
 static int is_ref_checked_out(const char *ref)
 {
-       unsigned char sha1[20];
-       const char *head;
-
        if (is_bare_repository())
                return 0;
 
-       head = resolve_ref("HEAD", sha1, 0, NULL);
-       if (!head)
+       if (!head_name)
                return 0;
-       return !strcmp(head, ref);
+       return !strcmp(head_name, ref);
+}
+
+static char *warn_unconfigured_deny_msg[] = {
+       "Updating the currently checked out branch may cause confusion,",
+       "as the index and work tree do not reflect changes that are in HEAD.",
+       "As a result, you may see the changes you just pushed into it",
+       "reverted when you run 'git diff' over there, and you may want",
+       "to run 'git reset --hard' before starting to work to recover.",
+       "",
+       "You can set 'receive.denyCurrentBranch' configuration variable to",
+       "'refuse' in the remote repository to forbid pushing into its",
+       "current branch."
+       "",
+       "To allow pushing into the current branch, you can set it to 'ignore';",
+       "but this is not recommended unless you arranged to update its work",
+       "tree to match what you pushed in some other way.",
+       "",
+       "To squelch this message, you can set it to 'warn'.",
+       "",
+       "Note that the default will change in a future version of git",
+       "to refuse updating the current branch unless you have the",
+       "configuration variable set to either 'ignore' or 'warn'."
+};
+
+static void warn_unconfigured_deny(void)
+{
+       int i;
+       for (i = 0; i < ARRAY_SIZE(warn_unconfigured_deny_msg); i++)
+               warning("%s", warn_unconfigured_deny_msg[i]);
+}
+
+static char *warn_unconfigured_deny_delete_current_msg[] = {
+       "Deleting the current branch can cause confusion by making the next",
+       "'git clone' not check out any file.",
+       "",
+       "You can set 'receive.denyDeleteCurrent' configuration variable to",
+       "'refuse' in the remote repository to disallow deleting the current",
+       "branch.",
+       "",
+       "You can set it to 'ignore' to allow such a delete without a warning.",
+       "",
+       "To make this warning message less loud, you can set it to 'warn'.",
+       "",
+       "Note that the default will change in a future version of git",
+       "to refuse deleting the current branch unless you have the",
+       "configuration variable set to either 'ignore' or 'warn'."
+};
+
+static void warn_unconfigured_deny_delete_current(void)
+{
+       int i;
+       for (i = 0;
+            i < ARRAY_SIZE(warn_unconfigured_deny_delete_current_msg);
+            i++)
+               warning("%s", warn_unconfigured_deny_delete_current_msg[i]);
 }
 
 static const char *update(struct command *cmd)
@@ -227,22 +286,20 @@ static const char *update(struct command *cmd)
                return "funny refname";
        }
 
-       switch (deny_current_branch) {
-       case DENY_IGNORE:
-               break;
-       case DENY_WARN:
-               if (!is_ref_checked_out(name))
+       if (is_ref_checked_out(name)) {
+               switch (deny_current_branch) {
+               case DENY_IGNORE:
                        break;
-               warning("updating the currently checked out branch; this may"
-                       " cause confusion,\n"
-                       "as the index and working tree do not reflect changes"
-                       " that are now in HEAD.");
-               break;
-       case DENY_REFUSE:
-               if (!is_ref_checked_out(name))
+               case DENY_UNCONFIGURED:
+               case DENY_WARN:
+                       warning("updating the current branch");
+                       if (deny_current_branch == DENY_UNCONFIGURED)
+                               warn_unconfigured_deny();
                        break;
-               error("refusing to update checked out branch: %s", name);
-               return "branch is currently checked out";
+               case DENY_REFUSE:
+                       error("refusing to update checked out branch: %s", name);
+                       return "branch is currently checked out";
+               }
        }
 
        if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) {
@@ -250,12 +307,30 @@ static const char *update(struct command *cmd)
                      "but I can't find it!", sha1_to_hex(new_sha1));
                return "bad pack";
        }
-       if (deny_deletes && is_null_sha1(new_sha1) &&
-           !is_null_sha1(old_sha1) &&
-           !prefixcmp(name, "refs/heads/")) {
-               error("denying ref deletion for %s", name);
-               return "deletion prohibited";
+
+       if (!is_null_sha1(old_sha1) && is_null_sha1(new_sha1)) {
+               if (deny_deletes && !prefixcmp(name, "refs/heads/")) {
+                       error("denying ref deletion for %s", name);
+                       return "deletion prohibited";
+               }
+
+               if (!strcmp(name, head_name)) {
+                       switch (deny_delete_current) {
+                       case DENY_IGNORE:
+                               break;
+                       case DENY_WARN:
+                       case DENY_UNCONFIGURED:
+                               if (deny_delete_current == DENY_UNCONFIGURED)
+                                       warn_unconfigured_deny_delete_current();
+                               warning("deleting the current branch");
+                               break;
+                       case DENY_REFUSE:
+                               error("refusing to delete the current branch: %s", name);
+                               return "deletion of the current branch prohibited";
+                       }
+               }
        }
+
        if (deny_non_fast_forwards && !is_null_sha1(new_sha1) &&
            !is_null_sha1(old_sha1) &&
            !prefixcmp(name, "refs/heads/")) {
@@ -349,6 +424,7 @@ static void run_update_post_hook(struct command *cmd)
 static void execute_commands(const char *unpacker_error)
 {
        struct command *cmd = commands;
+       unsigned char sha1[20];
 
        if (unpacker_error) {
                while (cmd) {
@@ -358,7 +434,7 @@ static void execute_commands(const char *unpacker_error)
                return;
        }
 
-       if (run_hook(pre_receive_hook)) {
+       if (run_receive_hook(pre_receive_hook)) {
                while (cmd) {
                        cmd->error_string = "pre-receive hook declined";
                        cmd = cmd->next;
@@ -366,6 +442,8 @@ static void execute_commands(const char *unpacker_error)
                return;
        }
 
+       head_name = resolve_ref("HEAD", sha1, 0, NULL);
+
        while (cmd) {
                cmd->error_string = update(cmd);
                cmd = cmd->next;
@@ -597,7 +675,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
        setup_path();
 
        if (!enter_repo(dir, 0))
-               die("'%s': unable to chdir or not a git archive", dir);
+               die("'%s' does not appear to be a git repository", dir);
 
        if (is_repository_shallow())
                die("attempt to push into a shallow repository");
@@ -627,7 +705,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
                        unlink(pack_lockfile);
                if (report_status)
                        report(unpack_status);
-               run_hook(post_receive_hook);
+               run_receive_hook(post_receive_hook);
                run_update_post_hook(commands);
        }
        return 0;
index abc8dd8389be4a51b467b4f6d4f74e2037d65423..e171096ece8cd041f7e6a08ca80af444786b50cc 100644 (file)
@@ -298,7 +298,7 @@ static int add_known_remote(struct remote *remote, void *cb_data)
 
 struct branches_for_remote {
        struct remote *remote;
-       struct string_list *branches;
+       struct string_list *branches, *skipped;
        struct known_remotes *keep;
 };
 
@@ -323,6 +323,16 @@ static int add_branch_for_removal(const char *refname,
                        return 0;
        }
 
+       /* don't delete non-remote refs */
+       if (prefixcmp(refname, "refs/remotes")) {
+               /* advise user how to delete local branches */
+               if (!prefixcmp(refname, "refs/heads/"))
+                       string_list_append(abbrev_branch(refname),
+                                          branches->skipped);
+               /* silently skip over other non-remote refs */
+               return 0;
+       }
+
        /* make sure that symrefs are deleted */
        if (flags & REF_ISSYMREF)
                return unlink(git_path("%s", refname));
@@ -474,9 +484,8 @@ static int mv(int argc, const char **argv)
                struct string_list_item *item = remote_branches.items + i;
                int flag = 0;
                unsigned char sha1[20];
-               const char *symref;
 
-               symref = resolve_ref(item->string, sha1, 1, &flag);
+               resolve_ref(item->string, sha1, 1, &flag);
                if (!(flag & REF_ISSYMREF))
                        continue;
                if (delete_ref(item->string, NULL, REF_NODEREF))
@@ -542,8 +551,11 @@ static int rm(int argc, const char **argv)
        struct strbuf buf = STRBUF_INIT;
        struct known_remotes known_remotes = { NULL, NULL };
        struct string_list branches = { NULL, 0, 0, 1 };
-       struct branches_for_remote cb_data = { NULL, &branches, &known_remotes };
-       int i;
+       struct string_list skipped = { NULL, 0, 0, 1 };
+       struct branches_for_remote cb_data = {
+               NULL, &branches, &skipped, &known_remotes
+       };
+       int i, result;
 
        if (argc != 2)
                usage_with_options(builtin_remote_usage, options);
@@ -583,14 +595,26 @@ static int rm(int argc, const char **argv)
         * refs, which are invalidated when deleting a branch.
         */
        cb_data.remote = remote;
-       i = for_each_ref(add_branch_for_removal, &cb_data);
+       result = for_each_ref(add_branch_for_removal, &cb_data);
        strbuf_release(&buf);
 
-       if (!i)
-               i = remove_branches(&branches);
+       if (!result)
+               result = remove_branches(&branches);
        string_list_clear(&branches, 1);
 
-       return i;
+       if (skipped.nr) {
+               fprintf(stderr, skipped.nr == 1 ?
+                       "Note: A non-remote branch was not removed; "
+                       "to delete it, use:\n" :
+                       "Note: Non-remote branches were not removed; "
+                       "to delete them, use:\n");
+               for (i = 0; i < skipped.nr; i++)
+                       fprintf(stderr, "  git branch -d %s\n",
+                               skipped.items[i].string);
+       }
+       string_list_clear(&skipped, 0);
+
+       return result;
 }
 
 static void show_list(const char *title, struct string_list *list,
@@ -731,12 +755,17 @@ static int prune(int argc, const char **argv)
                OPT_END()
        };
        struct ref_states states;
+       const char *dangling_msg;
 
        argc = parse_options(argc, argv, options, builtin_remote_usage, 0);
 
        if (argc < 1)
                usage_with_options(builtin_remote_usage, options);
 
+       dangling_msg = (dry_run
+                       ? " %s will become dangling!\n"
+                       : " %s has become dangling!\n");
+
        memset(&states, 0, sizeof(states));
        for (; argc; argc--, argv++) {
                int i;
@@ -759,6 +788,7 @@ static int prune(int argc, const char **argv)
 
                        printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
                               abbrev_ref(refname, "refs/remotes/"));
+                       warn_dangling_symref(dangling_msg, refname);
                }
 
                /* NEEDSWORK: free remote */
index d4dec6b7156b081bf63933d4de6d0b62ab2212c8..020af7377bb9aa1bced7489d40169e3cb2ff7682 100644 (file)
@@ -1,5 +1,6 @@
 #include "builtin.h"
 #include "cache.h"
+#include "dir.h"
 #include "string-list.h"
 #include "rerere.h"
 #include "xdiff/xdiff.h"
@@ -12,28 +13,17 @@ static const char git_rerere_usage[] =
 static int cutoff_noresolve = 15;
 static int cutoff_resolve = 60;
 
-static const char *rr_path(const char *name, const char *file)
-{
-       return git_path("rr-cache/%s/%s", name, file);
-}
-
 static time_t rerere_created_at(const char *name)
 {
        struct stat st;
-       return stat(rr_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime;
-}
-
-static int has_resolution(const char *name)
-{
-       struct stat st;
-       return !stat(rr_path(name, "postimage"), &st);
+       return stat(rerere_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime;
 }
 
 static void unlink_rr_item(const char *name)
 {
-       unlink(rr_path(name, "thisimage"));
-       unlink(rr_path(name, "preimage"));
-       unlink(rr_path(name, "postimage"));
+       unlink(rerere_path(name, "thisimage"));
+       unlink(rerere_path(name, "preimage"));
+       unlink(rerere_path(name, "postimage"));
        rmdir(git_path("rr-cache/%s", name));
 }
 
@@ -59,17 +49,15 @@ static void garbage_collect(struct string_list *rr)
        git_config(git_rerere_gc_config, NULL);
        dir = opendir(git_path("rr-cache"));
        while ((e = readdir(dir))) {
-               const char *name = e->d_name;
-               if (name[0] == '.' &&
-                   (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')))
+               if (is_dot_or_dotdot(e->d_name))
                        continue;
-               then = rerere_created_at(name);
+               then = rerere_created_at(e->d_name);
                if (!then)
                        continue;
-               cutoff = (has_resolution(name)
+               cutoff = (has_rerere_resolution(e->d_name)
                          ? cutoff_resolve : cutoff_noresolve);
                if (then < now - cutoff * 86400)
-                       string_list_append(name, &to_remove);
+                       string_list_append(e->d_name, &to_remove);
        }
        for (i = 0; i < to_remove.nr; i++)
                unlink_rr_item(to_remove.items[i].string);
@@ -125,7 +113,7 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
        if (!strcmp(argv[1], "clear")) {
                for (i = 0; i < merge_rr.nr; i++) {
                        const char *name = (const char *)merge_rr.items[i].util;
-                       if (!has_resolution(name))
+                       if (!has_rerere_resolution(name))
                                unlink_rr_item(name);
                }
                unlink(git_path("rr-cache/MERGE_RR"));
@@ -138,7 +126,7 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
                for (i = 0; i < merge_rr.nr; i++) {
                        const char *path = merge_rr.items[i].string;
                        const char *name = (const char *)merge_rr.items[i].util;
-                       diff_two(rr_path(name, "preimage"), path, path, path);
+                       diff_two(rerere_path(name, "preimage"), path, path, path);
                }
        else
                usage(git_rerere_usage);
index 9514b77f8c0b4e8a576e888df905b501e198df24..c0cb915c26b56a905807cefa4e246d99d199b297 100644 (file)
 #include "parse-options.h"
 
 static const char * const git_reset_usage[] = {
-       "git reset [--mixed | --soft | --hard] [-q] [<commit>]",
+       "git reset [--mixed | --soft | --hard | --merge] [-q] [<commit>]",
        "git reset [--mixed] <commit> [--] <paths>...",
        NULL
 };
 
+enum reset_type { MIXED, SOFT, HARD, MERGE, NONE };
+static const char *reset_type_names[] = { "mixed", "soft", "hard", "merge", NULL };
+
 static char *args_to_str(const char **argv)
 {
        char *buf = NULL;
@@ -49,7 +52,7 @@ static inline int is_merge(void)
        return !access(git_path("MERGE_HEAD"), F_OK);
 }
 
-static int reset_index_file(const unsigned char *sha1, int is_hard_reset, int quiet)
+static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet)
 {
        int i = 0;
        const char *args[6];
@@ -57,9 +60,17 @@ static int reset_index_file(const unsigned char *sha1, int is_hard_reset, int qu
        args[i++] = "read-tree";
        if (!quiet)
                args[i++] = "-v";
-       args[i++] = "--reset";
-       if (is_hard_reset)
+       switch (reset_type) {
+       case MERGE:
                args[i++] = "-u";
+               args[i++] = "-m";
+               break;
+       case HARD:
+               args[i++] = "-u";
+               /* fallthrough */
+       default:
+               args[i++] = "--reset";
+       }
        args[i++] = sha1_to_hex(sha1);
        args[i] = NULL;
 
@@ -169,9 +180,6 @@ static void prepend_reflog_action(const char *action, char *buf, size_t size)
                warning("Reflog action message too long: %.*s...", 50, buf);
 }
 
-enum reset_type { MIXED, SOFT, HARD, NONE };
-static const char *reset_type_names[] = { "mixed", "soft", "hard", NULL };
-
 int cmd_reset(int argc, const char **argv, const char *prefix)
 {
        int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0;
@@ -186,6 +194,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                OPT_SET_INT(0, "soft", &reset_type, "reset only HEAD", SOFT),
                OPT_SET_INT(0, "hard", &reset_type,
                                "reset HEAD, index and working tree", HARD),
+               OPT_SET_INT(0, "merge", &reset_type,
+                               "reset HEAD, index and working tree", MERGE),
                OPT_BOOLEAN('q', NULL, &quiet,
                                "disable showing new HEAD in hard reset and progress message"),
                OPT_END()
@@ -266,7 +276,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                if (is_merge() || read_cache() < 0 || unmerged_cache())
                        die("Cannot do a soft reset in the middle of a merge.");
        }
-       else if (reset_index_file(sha1, (reset_type == HARD), quiet))
+       else if (reset_index_file(sha1, reset_type, quiet))
                die("Could not reset index file to revision '%s'.", rev);
 
        /* Any resets update HEAD to the head being switched to,
index 436afa45f5b7569551aa8301aee8a0752009a900..40d5fcb6b0b26c76c271624408b531cc01e15f7b 100644 (file)
@@ -574,6 +574,45 @@ static struct commit_list *find_bisection(struct commit_list *list,
        return best;
 }
 
+static inline int log2i(int n)
+{
+       int log2 = 0;
+
+       for (; n > 1; n >>= 1)
+               log2++;
+
+       return log2;
+}
+
+static inline int exp2i(int n)
+{
+       return 1 << n;
+}
+
+/*
+ * Estimate the number of bisect steps left (after the current step)
+ *
+ * For any x between 0 included and 2^n excluded, the probability for
+ * n - 1 steps left looks like:
+ *
+ * P(2^n + x) == (2^n - x) / (2^n + x)
+ *
+ * and P(2^n + x) < 0.5 means 2^n < 3x
+ */
+static int estimate_bisect_steps(int all)
+{
+       int n, x, e;
+
+       if (all < 3)
+               return 0;
+
+       n = log2i(all);
+       e = exp2i(n);
+       x = all - e;
+
+       return (e < 3 * x) ? n : n - 1;
+}
+
 int cmd_rev_list(int argc, const char **argv, const char *prefix)
 {
        struct commit_list *list;
@@ -688,12 +727,14 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
                               "bisect_nr=%d\n"
                               "bisect_good=%d\n"
                               "bisect_bad=%d\n"
-                              "bisect_all=%d\n",
+                              "bisect_all=%d\n"
+                              "bisect_steps=%d\n",
                               hex,
                               cnt - 1,
                               all - reaches - 1,
                               reaches - 1,
-                              all);
+                              all,
+                              estimate_bisect_steps(all));
                        return 0;
                }
        }
index d48313c7453c24e7ab4cbb3e024d05ec311edf7b..3f2614e1bbef009afadefff7d284225533db2a09 100644 (file)
@@ -223,17 +223,6 @@ static char *help_msg(const unsigned char *sha1)
        return helpbuf;
 }
 
-static int index_is_dirty(void)
-{
-       struct rev_info rev;
-       init_revisions(&rev, NULL);
-       setup_revisions(0, NULL, &rev, "HEAD");
-       DIFF_OPT_SET(&rev.diffopt, QUIET);
-       DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS);
-       run_diff_index(&rev, 1);
-       return !!DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES);
-}
-
 static struct tree *empty_tree(void)
 {
        struct tree *tree = xcalloc(1, sizeof(struct tree));
@@ -279,7 +268,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
        } else {
                if (get_sha1("HEAD", head))
                        die ("You do not have a valid HEAD");
-               if (index_is_dirty())
+               if (index_differs_from("HEAD", 0))
                        die ("Dirty index: cannot %s", me);
        }
        discard_cache();
@@ -387,6 +376,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
            (write_cache(index_fd, active_cache, active_nr) ||
             commit_locked_index(&index_lock)))
                die("%s: Unable to write new index file", me);
+       rollback_lock_file(&index_lock);
 
        if (!clean) {
                add_to_msg("\nConflicts:\n\n");
index e49290687f2a0c55317398ef8b04ac319018c91b..badd9120388569c79b137f679505b495cc1dbbe9 100644 (file)
@@ -39,8 +39,8 @@ static void insert_one_record(struct shortlog *log,
        const char *dot3 = log->common_repo_prefix;
        char *buffer, *p;
        struct string_list_item *item;
-       struct string_list *onelines;
        char namebuf[1024];
+       char emailbuf[1024];
        size_t len;
        const char *eol;
        const char *boemail, *eoemail;
@@ -52,7 +52,19 @@ static void insert_one_record(struct shortlog *log,
        eoemail = strchr(boemail, '>');
        if (!eoemail)
                return;
-       if (!map_email(&log->mailmap, boemail+1, namebuf, sizeof(namebuf))) {
+
+       /* copy author name to namebuf, to support matching on both name and email */
+       memcpy(namebuf, author, boemail - author);
+       len = boemail - author;
+       while(len > 0 && isspace(namebuf[len-1]))
+               len--;
+       namebuf[len] = 0;
+
+       /* copy email name to emailbuf, to allow email replacement as well */
+       memcpy(emailbuf, boemail+1, eoemail - boemail);
+       emailbuf[eoemail - boemail - 1] = 0;
+
+       if (!map_user(&log->mailmap, emailbuf, sizeof(emailbuf), namebuf, sizeof(namebuf))) {
                while (author < boemail && isspace(*author))
                        author++;
                for (len = 0;
@@ -68,16 +80,13 @@ static void insert_one_record(struct shortlog *log,
 
        if (log->email) {
                size_t room = sizeof(namebuf) - len - 1;
-               int maillen = eoemail - boemail + 1;
-               snprintf(namebuf + len, room, " %.*s", maillen, boemail);
+               int maillen = strlen(emailbuf);
+               snprintf(namebuf + len, room, " <%.*s>", maillen, emailbuf);
        }
 
-       buffer = xstrdup(namebuf);
-       item = string_list_insert(buffer, &log->list);
+       item = string_list_insert(namebuf, &log->list);
        if (item->util == NULL)
                item->util = xcalloc(1, sizeof(struct string_list));
-       else
-               free(buffer);
 
        /* Skip any leading whitespace, including any blank lines. */
        while (*oneline && isspace(*oneline))
@@ -107,16 +116,7 @@ static void insert_one_record(struct shortlog *log,
                }
        }
 
-       onelines = item->util;
-       if (onelines->nr >= onelines->alloc) {
-               onelines->alloc = alloc_nr(onelines->nr);
-               onelines->items = xrealloc(onelines->items,
-                               onelines->alloc
-                               * sizeof(struct string_list_item));
-       }
-
-       onelines->items[onelines->nr].util = NULL;
-       onelines->items[onelines->nr++].string = buffer;
+       string_list_append(buffer, item->util);
 }
 
 static void read_from_stdin(struct shortlog *log)
@@ -232,7 +232,7 @@ void shortlog_init(struct shortlog *log)
 {
        memset(log, 0, sizeof(*log));
 
-       read_mailmap(&log->mailmap, ".mailmap", &log->common_repo_prefix);
+       read_mailmap(&log->mailmap, &log->common_repo_prefix);
 
        log->list.strdup_strings = 1;
        log->wrap = DEFAULT_WRAPLEN;
@@ -261,6 +261,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
        struct parse_opt_ctx_t ctx;
 
        prefix = setup_git_directory_gently(&nongit);
+       git_config(git_default_config, NULL);
        shortlog_init(&log);
        init_revisions(&rev, prefix);
        parse_options_start(&ctx, argc, argv, PARSE_OPT_KEEP_DASHDASH |
@@ -326,13 +327,12 @@ void shortlog_output(struct shortlog *log)
                }
 
                onelines->strdup_strings = 1;
-               string_list_clear(onelines, 1);
+               string_list_clear(onelines, 0);
                free(onelines);
                log->list.items[i].util = NULL;
        }
 
        log->list.strdup_strings = 1;
        string_list_clear(&log->list, 1);
-       log->mailmap.strdup_strings = 1;
-       string_list_clear(&log->mailmap, 1);
+       clear_mailmap(&log->mailmap);
 }
index bfc78bb3f6eff2f8e39649b9649ae7263f143ad9..6ae6bcc0e8d02d9af8a81a7d694c0bfd2c6c0514 100644 (file)
@@ -44,6 +44,9 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
                check_symref(argv[0], quiet);
                break;
        case 2:
+               if (!strcmp(argv[0], "HEAD") &&
+                   prefixcmp(argv[1], "refs/"))
+                       die("Refusing to point HEAD outside of refs/");
                create_symref(argv[0], argv[1], msg);
                break;
        default:
index a3984998915921476a80839a07b133779ece72f3..01e73747d02f384c5e31b846340a4b586c84aab3 100644 (file)
@@ -26,6 +26,7 @@ static char signingkey[1000];
 struct tag_filter {
        const char *pattern;
        int lines;
+       struct commit_list *with_commit;
 };
 
 #define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
@@ -42,6 +43,16 @@ static int show_reference(const char *refname, const unsigned char *sha1,
                char *buf, *sp, *eol;
                size_t len;
 
+               if (filter->with_commit) {
+                       struct commit *commit;
+
+                       commit = lookup_commit_reference_gently(sha1, 1);
+                       if (!commit)
+                               return 0;
+                       if (!is_descendant_of(commit, filter->with_commit))
+                               return 0;
+               }
+
                if (!filter->lines) {
                        printf("%s\n", refname);
                        return 0;
@@ -79,7 +90,8 @@ 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 *pattern, int lines,
+                       struct commit_list *with_commit)
 {
        struct tag_filter filter;
 
@@ -88,6 +100,7 @@ static int list_tags(const char *pattern, int lines)
 
        filter.pattern = pattern;
        filter.lines = lines;
+       filter.with_commit = with_commit;
 
        for_each_tag_ref(show_reference, (void *) &filter);
 
@@ -360,6 +373,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                list = 0, delete = 0, verify = 0;
        const char *msgfile = NULL, *keyid = NULL;
        struct msg_arg msg = { 0, STRBUF_INIT };
+       struct commit_list *with_commit = NULL;
        struct option options[] = {
                OPT_BOOLEAN('l', NULL, &list, "list tag names"),
                { OPTION_INTEGER, 'n', NULL, &lines, NULL,
@@ -378,6 +392,14 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                OPT_STRING('u', NULL, &keyid, "key-id",
                                        "use another key to sign the tag"),
                OPT_BOOLEAN('f', NULL, &force, "replace the tag if exists"),
+
+               OPT_GROUP("Tag listing options"),
+               {
+                       OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
+                       "print only tags that contain the commit",
+                       PARSE_OPT_LASTARG_DEFAULT,
+                       parse_opt_with_commit, (intptr_t)"HEAD",
+               },
                OPT_END()
        };
 
@@ -402,9 +424,12 @@ 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[0], lines == -1 ? 0 : lines,
+                                with_commit);
        if (lines != -1)
                die("-n option is only allowed with -l.");
+       if (with_commit)
+               die("--contains option is only allowed with -l.");
        if (delete)
                return for_each_tag_name(argv, delete_tag);
        if (verify)
index daca0f775e4b49f2576a7c444895146d05b7022c..dd43d5bef425af318a884bfc235a4aff40931633 100644 (file)
@@ -486,7 +486,7 @@ static int unresolve_one(const char *path)
 static void read_head_pointers(void)
 {
        if (read_ref("HEAD", head_sha1))
-               die("No HEAD -- no initial commit yet?\n");
+               die("No HEAD -- no initial commit yet?");
        if (read_ref("MERGE_HEAD", merge_head_sha1)) {
                fprintf(stderr, "Not in the middle of a merge.\n");
                exit(0);
index a9b02fa32f372a6810867c10560a20d58b5b2a91..0206b416cbf08ae4c1b0d753784fb0b61bac46a1 100644 (file)
@@ -35,7 +35,7 @@ static int run_upload_archive(int argc, const char **argv, const char *prefix)
        strcpy(buf, argv[1]); /* enter-repo smudges its argument */
 
        if (!enter_repo(buf, 0))
-               die("not a git archive");
+               die("'%s' does not appear to be a git repository", buf);
 
        /* put received options in sent_argv[] */
        sent_argc = 1;
index 25a29f11a4b9642c1bd367b81779d8f09ae04604..0ee0a9af60b0601fe0e6db98ec582e059f5e9064 100644 (file)
@@ -107,7 +107,7 @@ static int verify_one_pack(const char *path, int verbose)
        return err;
 }
 
-static const char verify_pack_usage[] = "git-verify-pack [-v] <pack>...";
+static const char verify_pack_usage[] = "git verify-pack [-v] <pack>...";
 
 int cmd_verify_pack(int argc, const char **argv, const char *prefix)
 {
index b20f2101f265786ed61e2ca08764aae249bad9d4..d0dd818b31faa04caa4d418a39ad3020a919aa2d 100644 (file)
--- a/bundle.c
+++ b/bundle.c
@@ -167,6 +167,32 @@ int list_bundle_refs(struct bundle_header *header, int argc, const char **argv)
        return list_refs(&header->references, argc, argv);
 }
 
+static int is_tag_in_date_range(struct object *tag, struct rev_info *revs)
+{
+       unsigned long size;
+       enum object_type type;
+       char *buf, *line, *lineend;
+       unsigned long date;
+
+       if (revs->max_age == -1 && revs->min_age == -1)
+               return 1;
+
+       buf = read_sha1_file(tag->sha1, &type, &size);
+       if (!buf)
+               return 1;
+       line = memmem(buf, size, "\ntagger ", 8);
+       if (!line++)
+               return 1;
+       lineend = memchr(line, buf + size - line, '\n');
+       line = memchr(line, lineend ? lineend - line : buf + size - line, '>');
+       if (!line++)
+               return 1;
+       date = strtoul(line, NULL, 10);
+       free(buf);
+       return (revs->max_age == -1 || revs->max_age < date) &&
+               (revs->min_age == -1 || revs->min_age > date);
+}
+
 int create_bundle(struct bundle_header *header, const char *path,
                int argc, const char **argv)
 {
@@ -257,6 +283,12 @@ int create_bundle(struct bundle_header *header, const char *path,
                        flag = 0;
                display_ref = (flag & REF_ISSYMREF) ? e->name : ref;
 
+               if (e->item->type == OBJ_TAG &&
+                               !is_tag_in_date_range(e->item, &revs)) {
+                       e->item->flags |= UNINTERESTING;
+                       continue;
+               }
+
                /*
                 * Make sure the refs we wrote out is correct; --max-count and
                 * other limiting options could have prevented all the tips
diff --git a/cache.h b/cache.h
index 0e2f219b2b4d9ff5944e68dbaf2338c76055161f..fdc4ada43a24fd64b93dc63589d13da911c70508 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -625,8 +625,9 @@ int is_directory(const char *);
 const char *make_absolute_path(const char *path);
 const char *make_nonrelative_path(const char *path);
 const char *make_relative_path(const char *abs, const char *base);
-int normalize_absolute_path(char *buf, const char *path);
+int normalize_path_copy(char *dst, const char *src);
 int longest_ancestor_length(const char *path, const char *prefix_list);
+char *strip_path_suffix(const char *path, const char *suffix);
 
 /* Read and unpack a sha1 file into memory, write memory to a sha1 file */
 extern int sha1_object_info(const unsigned char *, unsigned long *);
@@ -636,9 +637,6 @@ extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsig
 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);
 
-/* just like read_sha1_file(), but non fatal in presence of bad objects */
-extern void *read_object(const unsigned char *sha1, enum object_type *type, unsigned long *size);
-
 /* global flag to enable extra checks when accessing packed objects */
 extern int do_check_packed_object_crc;
 
@@ -646,7 +644,8 @@ extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned l
 
 extern int move_temp_to_file(const char *tmpfile, const char *filename);
 
-extern int has_sha1_pack(const unsigned char *sha1, const char **ignore);
+extern int has_sha1_pack(const unsigned char *sha1);
+extern int has_sha1_kept_pack(const unsigned char *sha1);
 extern int has_sha1_file(const unsigned char *sha1);
 extern int has_loose_object_nonlocal(const unsigned char *sha1);
 
@@ -671,6 +670,7 @@ extern int read_ref(const char *filename, unsigned char *sha1);
 extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *);
 extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
 extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
+extern int interpret_nth_last_branch(const char *str, struct strbuf *);
 
 extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);
 extern const char *ref_rev_parse_rules[];
@@ -697,7 +697,8 @@ enum date_mode {
        DATE_SHORT,
        DATE_LOCAL,
        DATE_ISO8601,
-       DATE_RFC2822
+       DATE_RFC2822,
+       DATE_RAW
 };
 
 const char *show_date(unsigned long time, int timezone, enum date_mode mode);
@@ -725,6 +726,10 @@ struct checkout {
 
 extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath);
 extern int has_symlink_leading_path(int len, const char *name);
+extern int has_symlink_or_noent_leading_path(int len, const char *name);
+extern int has_dirs_only_path(int len, const char *name, int prefix_len);
+extern void invalidate_lstat_cache(int len, const char *name);
+extern void clear_lstat_cache(void);
 
 extern struct alternate_object_database {
        struct alternate_object_database *next;
@@ -835,7 +840,6 @@ extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsign
 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 matches_pack_name(struct packed_git *p, const char *name);
 
 /* Dumb servers support */
 extern int update_server_info(int);
@@ -867,6 +871,7 @@ extern int user_ident_explicitly_given;
 
 extern const char *git_commit_encoding;
 extern const char *git_log_output_encoding;
+extern const char *git_mailmap_file;
 
 /* IO helper functions */
 extern void maybe_flush_or_die(FILE *, const char *);
@@ -942,7 +947,6 @@ extern int ws_fix_copy(char *, const char *, int, unsigned, int *);
 extern int ws_blank_line(const char *line, int len, unsigned ws_rule);
 
 /* ls-files */
-int pathspec_match(const char **spec, char *matched, const char *filename, int skiplen);
 int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset);
 void overlay_tree_on_cache(const char *tree_name, const char *prefix);
 
diff --git a/color.c b/color.c
index fc0b72ad59b13e4bd86372e5e81b4f400c99d26e..62977f4808ae339fdfe797e16b4eb28dc6abb85d 100644 (file)
--- a/color.c
+++ b/color.c
@@ -1,8 +1,6 @@
 #include "cache.h"
 #include "color.h"
 
-#define COLOR_RESET "\033[m"
-
 int git_use_color_default = 0;
 
 static int parse_color(const char *name, int len)
@@ -40,30 +38,41 @@ static int parse_attr(const char *name, int len)
 }
 
 void color_parse(const char *value, const char *var, char *dst)
+{
+       color_parse_mem(value, strlen(value), var, dst);
+}
+
+void color_parse_mem(const char *value, int value_len, const char *var,
+               char *dst)
 {
        const char *ptr = value;
+       int len = value_len;
        int attr = -1;
        int fg = -2;
        int bg = -2;
 
-       if (!strcasecmp(value, "reset")) {
-               strcpy(dst, "\033[m");
+       if (!strncasecmp(value, "reset", len)) {
+               strcpy(dst, GIT_COLOR_RESET);
                return;
        }
 
        /* [fg [bg]] [attr] */
-       while (*ptr) {
+       while (len > 0) {
                const char *word = ptr;
-               int val, len = 0;
+               int val, wordlen = 0;
 
-               while (word[len] && !isspace(word[len]))
-                       len++;
+               while (len > 0 && !isspace(word[wordlen])) {
+                       wordlen++;
+                       len--;
+               }
 
-               ptr = word + len;
-               while (*ptr && isspace(*ptr))
+               ptr = word + wordlen;
+               while (len > 0 && isspace(*ptr)) {
                        ptr++;
+                       len--;
+               }
 
-               val = parse_color(word, len);
+               val = parse_color(word, wordlen);
                if (val >= -1) {
                        if (fg == -2) {
                                fg = val;
@@ -75,7 +84,7 @@ void color_parse(const char *value, const char *var, char *dst)
                        }
                        goto bad;
                }
-               val = parse_attr(word, len);
+               val = parse_attr(word, wordlen);
                if (val < 0 || attr != -1)
                        goto bad;
                attr = val;
@@ -115,7 +124,7 @@ void color_parse(const char *value, const char *var, char *dst)
        *dst = 0;
        return;
 bad:
-       die("bad config value '%s' for variable '%s'", value, var);
+       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)
@@ -164,7 +173,7 @@ static int color_vfprintf(FILE *fp, const char *color, const char *fmt,
                r += fprintf(fp, "%s", color);
        r += vfprintf(fp, fmt, args);
        if (*color)
-               r += fprintf(fp, "%s", COLOR_RESET);
+               r += fprintf(fp, "%s", GIT_COLOR_RESET);
        if (trail)
                r += fprintf(fp, "%s", trail);
        return r;
@@ -191,3 +200,31 @@ int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...)
        va_end(args);
        return r;
 }
+
+/*
+ * This function splits the buffer by newlines and colors the lines individually.
+ *
+ * Returns 0 on success.
+ */
+int color_fwrite_lines(FILE *fp, const char *color,
+               size_t count, const char *buf)
+{
+       if (!*color)
+               return fwrite(buf, count, 1, fp) != 1;
+       while (count) {
+               char *p = memchr(buf, '\n', count);
+               if (p != buf && (fputs(color, fp) < 0 ||
+                               fwrite(buf, p ? p - buf : count, 1, fp) != 1 ||
+                               fputs(GIT_COLOR_RESET, fp) < 0))
+                       return -1;
+               if (!p)
+                       return 0;
+               if (fputc('\n', fp) < 0)
+                       return -1;
+               count -= p + 1 - buf;
+               buf = p + 1;
+       }
+       return 0;
+}
+
+
diff --git a/color.h b/color.h
index 6cf5c88aaf8d0e38e2853e6fd212e3cdd6c180cb..6846be1706c1842f5a53abafd4e03758a36695e9 100644 (file)
--- a/color.h
+++ b/color.h
@@ -4,6 +4,16 @@
 /* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */
 #define COLOR_MAXLEN 24
 
+#define GIT_COLOR_NORMAL       ""
+#define GIT_COLOR_RESET                "\033[m"
+#define GIT_COLOR_BOLD         "\033[1m"
+#define GIT_COLOR_RED          "\033[31m"
+#define GIT_COLOR_GREEN                "\033[32m"
+#define GIT_COLOR_YELLOW       "\033[33m"
+#define GIT_COLOR_BLUE         "\033[34m"
+#define GIT_COLOR_CYAN         "\033[36m"
+#define GIT_COLOR_BG_RED       "\033[41m"
+
 /*
  * This variable stores the value of color.ui
  */
@@ -16,8 +26,10 @@ extern int git_use_color_default;
 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);
-void color_parse(const char *var, const char *value, char *dst);
+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);
 int color_fprintf(FILE *fp, const char *color, const char *fmt, ...);
 int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...);
+int color_fwrite_lines(FILE *fp, const char *color, size_t count, const char *buf);
 
 #endif /* COLOR_H */
index bccc018ab2666e769e7865d3cad1d61f04443700..b3b86aebcb81972ca50a031f4e15bb7a905101e0 100644 (file)
@@ -526,7 +526,6 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
                return; /* result deleted */
 
        while (1) {
-               struct sline *sl = &sline[lno];
                unsigned long hunk_end;
                unsigned long rlines;
                const char *hunk_comment = NULL;
@@ -592,7 +591,7 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
                        struct lline *ll;
                        int j;
                        unsigned long p_mask;
-                       sl = &sline[lno++];
+                       struct sline *sl = &sline[lno++];
                        ll = (sl->flag & no_pre_delete) ? NULL : sl->lost_head;
                        while (ll) {
                                fputs(c_old, stdout);
index c99db162a48e0a47e73e6bfc2ae5fd4b7c9dfa1a..aa3b35b6a86891ac9d0628e20a6a46d506bf7700 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -705,6 +705,21 @@ struct commit_list *get_merge_bases(struct commit *one, struct commit *two,
        return get_merge_bases_many(one, 1, &two, cleanup);
 }
 
+int is_descendant_of(struct commit *commit, struct commit_list *with_commit)
+{
+       if (!with_commit)
+               return 1;
+       while (with_commit) {
+               struct commit *other;
+
+               other = with_commit->item;
+               with_commit = with_commit->next;
+               if (in_merge_bases(other, &commit, 1))
+                       return 1;
+       }
+       return 0;
+}
+
 int in_merge_bases(struct commit *commit, struct commit **reference, int num)
 {
        struct commit_list *bases, *b;
index 3a7b06a828930ca23d0fb045feab993a1452b2d3..ba9f63813eba004ae409eba8741266a074161239 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -133,6 +133,7 @@ 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);
 
+int is_descendant_of(struct commit *, struct commit_list *);
 int in_merge_bases(struct commit *, struct commit **, int);
 
 extern int interactive_add(int argc, const char **argv, const char *prefix);
index cd0d8773641f2fdc808d8b246a8dd2bcd0e5814d..56bcb4277f47c295993c853a84edfe45e6aa3911 100644 (file)
@@ -5,6 +5,8 @@ void *gitmemmem(const void *haystack, size_t haystack_len,
 {
        const char *begin = haystack;
        const char *last_possible = begin + haystack_len - needle_len;
+       const char *tail = needle;
+       char point;
 
        /*
         * The first occurrence of the empty string is deemed to occur at
@@ -20,8 +22,9 @@ void *gitmemmem(const void *haystack, size_t haystack_len,
        if (haystack_len < needle_len)
                return NULL;
 
+       point = *tail++;
        for (; begin <= last_possible; begin++) {
-               if (!memcmp(begin, needle, needle_len))
+               if (*begin == point && !memcmp(begin + 1, tail, needle_len - 1))
                        return (void *)begin;
        }
 
index 3dbe6a77ffa4675b19e7183fd49e11212cb2cda0..27bcf3fd6b481eb058b357241da320a02e3d4096 100644 (file)
@@ -46,7 +46,8 @@ static int do_lstat(const char *file_name, struct stat *buf)
                buf->st_uid = 0;
                buf->st_nlink = 1;
                buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
-               buf->st_size = fdata.nFileSizeLow; /* Can't use nFileSizeHigh, since it's not a stat64 */
+               buf->st_size = fdata.nFileSizeLow |
+                       (((off_t)fdata.nFileSizeHigh)<<32);
                buf->st_dev = buf->st_rdev = 0; /* not used by Git */
                buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
                buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
@@ -101,7 +102,7 @@ int mingw_fstat(int fd, struct stat *buf)
        }
        /* direct non-file handles to MS's fstat() */
        if (GetFileType(fh) != FILE_TYPE_DISK)
-               return fstat(fd, buf);
+               return _fstati64(fd, buf);
 
        if (GetFileInformationByHandle(fh, &fdata)) {
                buf->st_ino = 0;
@@ -109,7 +110,8 @@ int mingw_fstat(int fd, struct stat *buf)
                buf->st_uid = 0;
                buf->st_nlink = 1;
                buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
-               buf->st_size = fdata.nFileSizeLow; /* Can't use nFileSizeHigh, since it's not a stat64 */
+               buf->st_size = fdata.nFileSizeLow |
+                       (((off_t)fdata.nFileSizeHigh)<<32);
                buf->st_dev = buf->st_rdev = 0; /* not used by Git */
                buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
                buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
index 4f275cb8e6a67515292a9dfc60bd1343065067a9..6e246864429e276f116761e64bf89eb6e036fc28 100644 (file)
@@ -21,12 +21,12 @@ typedef int pid_t;
 #define WEXITSTATUS(x) ((x) & 0xff)
 #define WIFSIGNALED(x) ((unsigned)(x) > 259)
 
-#define SIGKILL 0
-#define SIGCHLD 0
-#define SIGPIPE 0
-#define SIGHUP 0
-#define SIGQUIT 0
-#define SIGALRM 100
+#define SIGHUP 1
+#define SIGQUIT 3
+#define SIGKILL 9
+#define SIGPIPE 13
+#define SIGALRM 14
+#define SIGCHLD 17
 
 #define F_GETFD 1
 #define F_SETFD 2
@@ -163,11 +163,14 @@ int mingw_rename(const char*, const char*);
 /* Use mingw_lstat() instead of lstat()/stat() and
  * mingw_fstat() instead of fstat() on Windows.
  */
+#define off_t off64_t
+#define stat _stati64
+#define lseek _lseeki64
 int mingw_lstat(const char *file_name, struct stat *buf);
 int mingw_fstat(int fd, struct stat *buf);
 #define fstat mingw_fstat
 #define lstat mingw_lstat
-#define stat(x,y) mingw_lstat(x,y)
+#define _stati64(x,y) mingw_lstat(x,y)
 
 int mingw_utime(const char *file_name, const struct utimbuf *times);
 #define utime mingw_utime
index 790405a213b12a4d1c62d9354e1292e0dc6af057..0c8c76f13b03028ad400b1c5b72b3cf0a7ec0940 100644 (file)
--- a/config.c
+++ b/config.c
@@ -565,6 +565,15 @@ static int git_default_branch_config(const char *var, const char *value)
        return 0;
 }
 
+static int git_default_mailmap_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "mailmap.file"))
+               return git_config_string(&git_mailmap_file, var, value);
+
+       /* Add other config variables here and to Documentation/config.txt. */
+       return 0;
+}
+
 int git_default_config(const char *var, const char *value, void *dummy)
 {
        if (!prefixcmp(var, "core."))
@@ -579,6 +588,9 @@ int git_default_config(const char *var, const char *value, void *dummy)
        if (!prefixcmp(var, "branch."))
                return git_default_branch_config(var, value);
 
+       if (!prefixcmp(var, "mailmap."))
+               return git_default_mailmap_config(var, value);
+
        if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) {
                pager_use_color = git_config_bool(var,value);
                return 0;
index 14dfb21fa59efb7482ea0727845c8198cc996271..7cce0c12d507b222d47d8469abf59bf3ef9096b9 100644 (file)
@@ -13,9 +13,9 @@ TCLTK_PATH = @TCLTK_PATH@
 prefix = @prefix@
 exec_prefix = @exec_prefix@
 bindir = @bindir@
-gitexecdir = @libexecdir@/git-core/
+gitexecdir = @libexecdir@/git-core
 datarootdir = @datarootdir@
-template_dir = @datadir@/git-core/templates/
+template_dir = @datadir@/git-core/templates
 
 mandir=@mandir@
 
@@ -52,4 +52,5 @@ NO_DEFLATE_BOUND=@NO_DEFLATE_BOUND@
 FREAD_READS_DIRECTORIES=@FREAD_READS_DIRECTORIES@
 SNPRINTF_RETURNS_BOGUS=@SNPRINTF_RETURNS_BOGUS@
 NO_PTHREADS=@NO_PTHREADS@
+THREADED_DELTA_SEARCH=@THREADED_DELTA_SEARCH@
 PTHREAD_LIBS=@PTHREAD_LIBS@
index 0a5fc8c6f6f91099c3c5df10f08240e547126e0a..082a03d3cf6e9416c80b5d486c3191d3ac64cca4 100644 (file)
@@ -114,31 +114,31 @@ AC_MSG_NOTICE([CHECKS for programs])
 #
 AC_PROG_CC([cc gcc])
 # which switch to pass runtime path to dynamic libraries to the linker
-AC_CACHE_CHECK([if linker supports -R], ld_dashr, [
+AC_CACHE_CHECK([if linker supports -R], git_cv_ld_dashr, [
    SAVE_LDFLAGS="${LDFLAGS}"
    LDFLAGS="${SAVE_LDFLAGS} -R /"
-   AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [ld_dashr=yes], [ld_dashr=no])
+   AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [git_cv_ld_dashr=yes], [git_cv_ld_dashr=no])
    LDFLAGS="${SAVE_LDFLAGS}"
 ])
-if test "$ld_dashr" = "yes"; then
+if test "$git_cv_ld_dashr" = "yes"; then
    AC_SUBST(CC_LD_DYNPATH, [-R])
 else
-   AC_CACHE_CHECK([if linker supports -Wl,-rpath,], ld_wl_rpath, [
+   AC_CACHE_CHECK([if linker supports -Wl,-rpath,], git_cv_ld_wl_rpath, [
       SAVE_LDFLAGS="${LDFLAGS}"
       LDFLAGS="${SAVE_LDFLAGS} -Wl,-rpath,/"
-      AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [ld_wl_rpath=yes], [ld_wl_rpath=no])
+      AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [git_cv_ld_wl_rpath=yes], [git_cv_ld_wl_rpath=no])
       LDFLAGS="${SAVE_LDFLAGS}"
    ])
-   if test "$ld_wl_rpath" = "yes"; then
+   if test "$git_cv_ld_wl_rpath" = "yes"; then
       AC_SUBST(CC_LD_DYNPATH, [-Wl,-rpath,])
    else
-      AC_CACHE_CHECK([if linker supports -rpath], ld_rpath, [
+      AC_CACHE_CHECK([if linker supports -rpath], git_cv_ld_rpath, [
          SAVE_LDFLAGS="${LDFLAGS}"
          LDFLAGS="${SAVE_LDFLAGS} -rpath /"
-         AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [ld_rpath=yes], [ld_rpath=no])
+         AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [git_cv_ld_rpath=yes], [git_cv_ld_rpath=no])
          LDFLAGS="${SAVE_LDFLAGS}"
       ])
-      if test "$ld_rpath" = "yes"; then
+      if test "$git_cv_ld_rpath" = "yes"; then
          AC_SUBST(CC_LD_DYNPATH, [-rpath])
       else
          AC_MSG_WARN([linker does not support runtime path to dynamic libraries])
@@ -492,7 +492,8 @@ AC_SUBST(NO_MKDTEMP)
 #
 # Define NO_PTHREADS if we do not have pthreads
 #
-# Define PTHREAD_LIBS to the linker flag used for Pthread support.
+# Define PTHREAD_LIBS to the linker flag used for Pthread support and define
+# THREADED_DELTA_SEARCH if Pthreads are available.
 AC_LANG_CONFTEST([AC_LANG_PROGRAM(
   [[#include <pthread.h>]],
   [[pthread_mutex_t test_mutex;]]
@@ -500,16 +501,19 @@ AC_LANG_CONFTEST([AC_LANG_PROGRAM(
 ${CC} -pthread conftest.c -o conftest.o > /dev/null 2>&1
 if test $? -eq 0;then
  PTHREAD_LIBS="-pthread"
+ THREADED_DELTA_SEARCH=YesPlease
 else
  ${CC} -lpthread conftest.c -o conftest.o > /dev/null 2>&1
  if test $? -eq 0;then
   PTHREAD_LIBS="-lpthread"
+  THREADED_DELTA_SEARCH=YesPlease
  else
   NO_PTHREADS=UnfortunatelyYes
  fi
 fi
 AC_SUBST(PTHREAD_LIBS)
 AC_SUBST(NO_PTHREADS)
+AC_SUBST(THREADED_DELTA_SEARCH)
 
 ## Site configuration (override autodetection)
 ## --with-PACKAGE[=ARG] and --without-PACKAGE
index 2f55ad2c256bc01b3062b99251af4386eef5af22..0a35cc1b25c22256845acffb35828cfa44316892 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -315,7 +315,7 @@ static int git_tcp_connect_sock(char *host, int flags)
                /* Not numeric */
                struct servent *se = getservbyname(port,"tcp");
                if ( !se )
-                       die("Unknown port %s\n", port);
+                       die("Unknown port %s", port);
                nport = se->s_port;
        }
 
@@ -373,8 +373,6 @@ static void git_tcp_connect(int fd[2], char *host, int flags)
 
 
 static char *git_proxy_command;
-static const char *rhost_name;
-static int rhost_len;
 
 static int git_proxy_command_options(const char *var, const char *value,
                void *cb)
@@ -383,6 +381,8 @@ static int git_proxy_command_options(const char *var, const char *value,
                const char *for_pos;
                int matchlen = -1;
                int hostlen;
+               const char *rhost_name = cb;
+               int rhost_len = strlen(rhost_name);
 
                if (git_proxy_command)
                        return 0;
@@ -426,11 +426,8 @@ static int git_proxy_command_options(const char *var, const char *value,
 
 static int git_use_proxy(const char *host)
 {
-       rhost_name = host;
-       rhost_len = strlen(host);
        git_proxy_command = getenv("GIT_PROXY_COMMAND");
-       git_config(git_proxy_command_options, NULL);
-       rhost_name = NULL;
+       git_config(git_proxy_command_options, (void*)host);
        return (git_proxy_command && *git_proxy_command);
 }
 
index e00454983ee16624910e40fec9ddb12849110b1b..271b911f7a1300057c0b100c1bdfe5d68a0e3248 100755 (executable)
@@ -1,3 +1,4 @@
+#!bash
 #
 # bash completion support for core Git.
 #
 #       are currently in a git repository.  The %s token will be
 #       the name of the current branch.
 #
+#       In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty
+#       value, unstaged (*) and staged (+) changes will be shown next
+#       to the branch name.  You can configure this per-repository
+#       with the bash.showDirtyState variable, which defaults to true
+#       once GIT_PS1_SHOWDIRTYSTATE is enabled.
+#
 # To submit patches:
 #
 #    *) Read Documentation/SubmittingPatches
@@ -50,10 +57,12 @@ case "$COMP_WORDBREAKS" in
 *)   COMP_WORDBREAKS="$COMP_WORDBREAKS:"
 esac
 
+# __gitdir accepts 0 or 1 arguments (i.e., location)
+# returns location of .git repo
 __gitdir ()
 {
-       if [ -z "$1" ]; then
-               if [ -n "$__git_dir" ]; then
+       if [ -z "${1-}" ]; then
+               if [ -n "${__git_dir-}" ]; then
                        echo "$__git_dir"
                elif [ -d .git ]; then
                        echo .git
@@ -67,58 +76,81 @@ __gitdir ()
        fi
 }
 
+# __git_ps1 accepts 0 or 1 arguments (i.e., format string)
+# returns text to add to bash PS1 prompt (includes branch name)
 __git_ps1 ()
 {
-       local g="$(git rev-parse --git-dir 2>/dev/null)"
+       local g="$(__gitdir)"
        if [ -n "$g" ]; then
                local r
                local b
-               if [ -d "$g/rebase-apply" ]
-               then
-                       if test -f "$g/rebase-apply/rebasing"
-                       then
+               if [ -d "$g/rebase-apply" ]; then
+                       if [ -f "$g/rebase-apply/rebasing" ]; then
                                r="|REBASE"
-                       elif test -f "$g/rebase-apply/applying"
-                       then
+               elif [ -f "$g/rebase-apply/applying" ]; then
                                r="|AM"
                        else
                                r="|AM/REBASE"
                        fi
                        b="$(git symbolic-ref HEAD 2>/dev/null)"
-               elif [ -f "$g/rebase-merge/interactive" ]
-               then
+               elif [ -f "$g/rebase-merge/interactive" ]; then
                        r="|REBASE-i"
                        b="$(cat "$g/rebase-merge/head-name")"
-               elif [ -d "$g/rebase-merge" ]
-               then
+               elif [ -d "$g/rebase-merge" ]; then
                        r="|REBASE-m"
                        b="$(cat "$g/rebase-merge/head-name")"
-               elif [ -f "$g/MERGE_HEAD" ]
-               then
+               elif [ -f "$g/MERGE_HEAD" ]; then
                        r="|MERGING"
                        b="$(git symbolic-ref HEAD 2>/dev/null)"
                else
-                       if [ -f "$g/BISECT_LOG" ]
-                       then
+                       if [ -f "$g/BISECT_LOG" ]; then
                                r="|BISECTING"
                        fi
-                       if ! b="$(git symbolic-ref HEAD 2>/dev/null)"
-                       then
-                               if ! b="$(git describe --exact-match HEAD 2>/dev/null)"
-                               then
-                                       b="$(cut -c1-7 "$g/HEAD")..."
+                       if ! b="$(git symbolic-ref HEAD 2>/dev/null)"; then
+                               if ! b="$(git describe --exact-match HEAD 2>/dev/null)"; then
+                                       if [ -r "$g/HEAD" ]; then
+                                               b="$(cut -c1-7 "$g/HEAD")..."
+                                       fi
                                fi
                        fi
                fi
 
-               if [ -n "$1" ]; then
-                       printf "$1" "${b##refs/heads/}$r"
-               else
-                       printf " (%s)" "${b##refs/heads/}$r"
+               local w
+               local i
+               local c
+
+               if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then
+                       if [ "true" = "$(git config --bool core.bare 2>/dev/null)" ]; then
+                               c="BARE:"
+                       else
+                               b="GIT_DIR!"
+                       fi
+               elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then
+                       if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ]; then
+                               if [ "$(git config --bool bash.showDirtyState)" != "false" ]; then
+                                       git diff --no-ext-diff --ignore-submodules \
+                                               --quiet --exit-code || w="*"
+                                       if git rev-parse --quiet --verify HEAD >/dev/null; then
+                                               git diff-index --cached --quiet \
+                                                       --ignore-submodules HEAD -- || i="+"
+                                       else
+                                               i="#"
+                                       fi
+                               fi
+                       fi
+               fi
+
+               if [ -n "$b" ]; then
+                       if [ -n "${1-}" ]; then
+                               printf "$1" "$c${b##refs/heads/}$w$i$r"
+                       else
+                               printf " (%s)" "$c${b##refs/heads/}$w$i$r"
+                       fi
                fi
        fi
 }
 
+# __gitcomp_1 requires 2 arguments
 __gitcomp_1 ()
 {
        local c IFS=' '$'\t'$'\n'
@@ -131,6 +163,8 @@ __gitcomp_1 ()
        done
 }
 
+# __gitcomp accepts 1, 2, 3, or 4 arguments
+# generates completion reply with compgen
 __gitcomp ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
@@ -143,22 +177,23 @@ __gitcomp ()
                ;;
        *)
                local IFS=$'\n'
-               COMPREPLY=($(compgen -P "$2" \
-                       -W "$(__gitcomp_1 "$1" "$4")" \
+               COMPREPLY=($(compgen -P "${2-}" \
+                       -W "$(__gitcomp_1 "${1-}" "${4-}")" \
                        -- "$cur"))
                ;;
        esac
 }
 
+# __git_heads accepts 0 or 1 arguments (to pass to __gitdir)
 __git_heads ()
 {
-       local cmd i is_hash=y dir="$(__gitdir "$1")"
+       local cmd i is_hash=y dir="$(__gitdir "${1-}")"
        if [ -d "$dir" ]; then
                git --git-dir="$dir" for-each-ref --format='%(refname:short)' \
                        refs/heads
                return
        fi
-       for i in $(git ls-remote "$1" 2>/dev/null); do
+       for i in $(git ls-remote "${1-}" 2>/dev/null); do
                case "$is_hash,$i" in
                y,*) is_hash=n ;;
                n,*^{}) is_hash=y ;;
@@ -168,15 +203,16 @@ __git_heads ()
        done
 }
 
+# __git_tags accepts 0 or 1 arguments (to pass to __gitdir)
 __git_tags ()
 {
-       local cmd i is_hash=y dir="$(__gitdir "$1")"
+       local cmd i is_hash=y dir="$(__gitdir "${1-}")"
        if [ -d "$dir" ]; then
                git --git-dir="$dir" for-each-ref --format='%(refname:short)' \
                        refs/tags
                return
        fi
-       for i in $(git ls-remote "$1" 2>/dev/null); do
+       for i in $(git ls-remote "${1-}" 2>/dev/null); do
                case "$is_hash,$i" in
                y,*) is_hash=n ;;
                n,*^{}) is_hash=y ;;
@@ -186,9 +222,10 @@ __git_tags ()
        done
 }
 
+# __git_refs accepts 0 or 1 arguments (to pass to __gitdir)
 __git_refs ()
 {
-       local i is_hash=y dir="$(__gitdir "$1")"
+       local i is_hash=y dir="$(__gitdir "${1-}")"
        local cur="${COMP_WORDS[COMP_CWORD]}" format refs
        if [ -d "$dir" ]; then
                case "$cur" in
@@ -218,6 +255,7 @@ __git_refs ()
        done
 }
 
+# __git_refs2 requires 1 argument (to pass to __git_refs)
 __git_refs2 ()
 {
        local i
@@ -226,6 +264,7 @@ __git_refs2 ()
        done
 }
 
+# __git_refs_remotes requires 1 argument (to pass to ls-remote)
 __git_refs_remotes ()
 {
        local cmd i is_hash=y
@@ -264,7 +303,7 @@ __git_remotes ()
 
 __git_merge_strategies ()
 {
-       if [ -n "$__git_merge_strategylist" ]; then
+       if [ -n "${__git_merge_strategylist-}" ]; then
                echo "$__git_merge_strategylist"
                return
        fi
@@ -348,9 +387,88 @@ __git_complete_revlist ()
        esac
 }
 
+__git_complete_remote_or_refspec ()
+{
+       local cmd="${COMP_WORDS[1]}"
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       local i c=2 remote="" pfx="" lhs=1 no_complete_refspec=0
+       while [ $c -lt $COMP_CWORD ]; do
+               i="${COMP_WORDS[c]}"
+               case "$i" in
+               --all|--mirror) [ "$cmd" = "push" ] && no_complete_refspec=1 ;;
+               -*) ;;
+               *) remote="$i"; break ;;
+               esac
+               c=$((++c))
+       done
+       if [ -z "$remote" ]; then
+               __gitcomp "$(__git_remotes)"
+               return
+       fi
+       if [ $no_complete_refspec = 1 ]; then
+               COMPREPLY=()
+               return
+       fi
+       [ "$remote" = "." ] && remote=
+       case "$cur" in
+       *:*)
+               case "$COMP_WORDBREAKS" in
+               *:*) : great ;;
+               *)   pfx="${cur%%:*}:" ;;
+               esac
+               cur="${cur#*:}"
+               lhs=0
+               ;;
+       +*)
+               pfx="+"
+               cur="${cur#+}"
+               ;;
+       esac
+       case "$cmd" in
+       fetch)
+               if [ $lhs = 1 ]; then
+                       __gitcomp "$(__git_refs2 "$remote")" "$pfx" "$cur"
+               else
+                       __gitcomp "$(__git_refs)" "$pfx" "$cur"
+               fi
+               ;;
+       pull)
+               if [ $lhs = 1 ]; then
+                       __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur"
+               else
+                       __gitcomp "$(__git_refs)" "$pfx" "$cur"
+               fi
+               ;;
+       push)
+               if [ $lhs = 1 ]; then
+                       __gitcomp "$(__git_refs)" "$pfx" "$cur"
+               else
+                       __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur"
+               fi
+               ;;
+       esac
+}
+
+__git_complete_strategy ()
+{
+       case "${COMP_WORDS[COMP_CWORD-1]}" in
+       -s|--strategy)
+               __gitcomp "$(__git_merge_strategies)"
+               return 0
+       esac
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       case "$cur" in
+       --strategy=*)
+               __gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}"
+               return 0
+               ;;
+       esac
+       return 1
+}
+
 __git_all_commands ()
 {
-       if [ -n "$__git_all_commandlist" ]; then
+       if [ -n "${__git_all_commandlist-}" ]; then
                echo "$__git_all_commandlist"
                return
        fi
@@ -368,7 +486,7 @@ __git_all_commandlist="$(__git_all_commands 2>/dev/null)"
 
 __git_porcelain_commands ()
 {
-       if [ -n "$__git_porcelain_commandlist" ]; then
+       if [ -n "${__git_porcelain_commandlist-}" ]; then
                echo "$__git_porcelain_commandlist"
                return
        fi
@@ -470,6 +588,7 @@ __git_aliases ()
        done
 }
 
+# __git_aliased_command requires 1 argument
 __git_aliased_command ()
 {
        local word cmdline=$(git --git-dir="$(__gitdir)" \
@@ -482,6 +601,7 @@ __git_aliased_command ()
        done
 }
 
+# __git_find_subcommand requires 1 argument
 __git_find_subcommand ()
 {
        local word subcommand c=1
@@ -563,7 +683,7 @@ _git_add ()
        --*)
                __gitcomp "
                        --interactive --refresh --patch --update --dry-run
-                       --ignore-errors
+                       --ignore-errors --intent-to-add
                        "
                return
        esac
@@ -628,7 +748,6 @@ _git_branch ()
        done
 
        case "${COMP_WORDS[COMP_CWORD]}" in
-       --*=*)  COMPREPLY=() ;;
        --*)
                __gitcomp "
                        --color --no-color --verbose --abbrev= --no-abbrev
@@ -759,23 +878,30 @@ _git_describe ()
        __gitcomp "$(__git_refs)"
 }
 
-_git_diff ()
-{
-       __git_has_doubledash && return
-
-       local cur="${COMP_WORDS[COMP_CWORD]}"
-       case "$cur" in
-       --*)
-               __gitcomp "--cached --stat --numstat --shortstat --summary
+__git_diff_common_options="--stat --numstat --shortstat --summary
                        --patch-with-stat --name-only --name-status --color
                        --no-color --color-words --no-renames --check
                        --full-index --binary --abbrev --diff-filter=
-                       --find-copies-harder --pickaxe-all --pickaxe-regex
+                       --find-copies-harder
                        --text --ignore-space-at-eol --ignore-space-change
                        --ignore-all-space --exit-code --quiet --ext-diff
                        --no-ext-diff
                        --no-prefix --src-prefix= --dst-prefix=
+                       --inter-hunk-context=
+                       --patience
+                       --raw
+"
+
+_git_diff ()
+{
+       __git_has_doubledash && return
+
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       case "$cur" in
+       --*)
+               __gitcomp "--cached --pickaxe-all --pickaxe-regex
                        --base --ours --theirs
+                       $__git_diff_common_options
                        "
                return
                ;;
@@ -783,27 +909,21 @@ _git_diff ()
        __git_complete_file
 }
 
+__git_fetch_options="
+       --quiet --verbose --append --upload-pack --force --keep --depth=
+       --tags --no-tags
+"
+
 _git_fetch ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
-
-       if [ "$COMP_CWORD" = 2 ]; then
-               __gitcomp "$(__git_remotes)"
-       else
-               case "$cur" in
-               *:*)
-                       local pfx=""
-                       case "$COMP_WORDBREAKS" in
-                       *:*) : great ;;
-                       *)   pfx="${cur%%:*}:" ;;
-                       esac
-                       __gitcomp "$(__git_refs)" "$pfx" "${cur#*:}"
-                       ;;
-               *)
-                       __gitcomp "$(__git_refs2 "${COMP_WORDS[2]}")"
-                       ;;
-               esac
-       fi
+       case "$cur" in
+       --*)
+               __gitcomp "$__git_fetch_options"
+               return
+               ;;
+       esac
+       __git_complete_remote_or_refspec
 }
 
 _git_format_patch ()
@@ -823,6 +943,8 @@ _git_format_patch ()
                        --not --all
                        --cover-letter
                        --no-prefix --src-prefix= --dst-prefix=
+                       --inline --suffix= --ignore-if-in-upstream
+                       --subject-prefix=
                        "
                return
                ;;
@@ -930,18 +1052,50 @@ _git_ls_tree ()
        __git_complete_file
 }
 
+# Options that go well for log, shortlog and gitk
+__git_log_common_options="
+       --not --all
+       --branches --tags --remotes
+       --first-parent --no-merges
+       --max-count=
+       --max-age= --since= --after=
+       --min-age= --until= --before=
+"
+# Options that go well for log and gitk (not shortlog)
+__git_log_gitk_options="
+       --dense --sparse --full-history
+       --simplify-merges --simplify-by-decoration
+       --left-right
+"
+# Options that go well for log and shortlog (not gitk)
+__git_log_shortlog_options="
+       --author= --committer= --grep=
+       --all-match
+"
+
+__git_log_pretty_formats="oneline short medium full fuller email raw format:"
+
 _git_log ()
 {
        __git_has_doubledash && return
 
        local cur="${COMP_WORDS[COMP_CWORD]}"
+       local g="$(git rev-parse --git-dir 2>/dev/null)"
+       local merge=""
+       if [ -f $g/MERGE_HEAD ]; then
+               merge="--merge"
+       fi
        case "$cur" in
        --pretty=*)
-               __gitcomp "
-                       oneline short medium full fuller email raw
+               __gitcomp "$__git_log_pretty_formats
                        " "" "${cur##--pretty=}"
                return
                ;;
+       --format=*)
+               __gitcomp "$__git_log_pretty_formats
+                       " "" "${cur##--format=}"
+               return
+               ;;
        --date=*)
                __gitcomp "
                        relative iso8601 rfc2822 short local default
@@ -950,23 +1104,22 @@ _git_log ()
                ;;
        --*)
                __gitcomp "
-                       --max-count= --max-age= --since= --after=
-                       --min-age= --before= --until=
+                       $__git_log_common_options
+                       $__git_log_shortlog_options
+                       $__git_log_gitk_options
                        --root --topo-order --date-order --reverse
-                       --no-merges --follow
+                       --follow
                        --abbrev-commit --abbrev=
                        --relative-date --date=
-                       --author= --committer= --grep=
-                       --all-match
-                       --pretty= --name-status --name-only --raw
-                       --not --all
-                       --left-right --cherry-pick
+                       --pretty= --format= --oneline
+                       --cherry-pick
                        --graph
-                       --stat --numstat --shortstat
-                       --decorate --diff-filter=
-                       --color-words --walk-reflogs
-                       --parents --children --full-history
-                       --merge
+                       --decorate
+                       --walk-reflogs
+                       --parents --children
+                       $merge
+                       $__git_diff_common_options
+                       --pickaxe-all --pickaxe-regex
                        "
                return
                ;;
@@ -974,23 +1127,19 @@ _git_log ()
        __git_complete_revlist
 }
 
+__git_merge_options="
+       --no-commit --no-stat --log --no-log --squash --strategy
+       --commit --stat --no-squash --ff --no-ff
+"
+
 _git_merge ()
 {
+       __git_complete_strategy && return
+
        local cur="${COMP_WORDS[COMP_CWORD]}"
-       case "${COMP_WORDS[COMP_CWORD-1]}" in
-       -s|--strategy)
-               __gitcomp "$(__git_merge_strategies)"
-               return
-       esac
        case "$cur" in
-       --strategy=*)
-               __gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}"
-               return
-               ;;
        --*)
-               __gitcomp "
-                       --no-commit --no-stat --log --no-log --squash --strategy
-                       "
+               __gitcomp "$__git_merge_options"
                return
        esac
        __gitcomp "$(__git_refs)"
@@ -1039,40 +1188,44 @@ _git_name_rev ()
 
 _git_pull ()
 {
-       local cur="${COMP_WORDS[COMP_CWORD]}"
+       __git_complete_strategy && return
 
-       if [ "$COMP_CWORD" = 2 ]; then
-               __gitcomp "$(__git_remotes)"
-       else
-               __gitcomp "$(__git_refs "${COMP_WORDS[2]}")"
-       fi
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       case "$cur" in
+       --*)
+               __gitcomp "
+                       --rebase --no-rebase
+                       $__git_merge_options
+                       $__git_fetch_options
+               "
+               return
+               ;;
+       esac
+       __git_complete_remote_or_refspec
 }
 
 _git_push ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
-
-       if [ "$COMP_CWORD" = 2 ]; then
+       case "${COMP_WORDS[COMP_CWORD-1]}" in
+       --repo)
                __gitcomp "$(__git_remotes)"
-       else
-               case "$cur" in
-               *:*)
-                       local pfx=""
-                       case "$COMP_WORDBREAKS" in
-                       *:*) : great ;;
-                       *)   pfx="${cur%%:*}:" ;;
-                       esac
-
-                       __gitcomp "$(__git_refs "${COMP_WORDS[2]}")" "$pfx" "${cur#*:}"
-                       ;;
-               +*)
-                       __gitcomp "$(__git_refs)" + "${cur#+}"
-                       ;;
-               *)
-                       __gitcomp "$(__git_refs)"
-                       ;;
-               esac
-       fi
+               return
+       esac
+       case "$cur" in
+       --repo=*)
+               __gitcomp "$(__git_remotes)" "" "${cur##--repo=}"
+               return
+               ;;
+       --*)
+               __gitcomp "
+                       --all --mirror --tags --dry-run --force --verbose
+                       --receive-pack= --repo=
+               "
+               return
+               ;;
+       esac
+       __git_complete_remote_or_refspec
 }
 
 _git_rebase ()
@@ -1082,16 +1235,8 @@ _git_rebase ()
                __gitcomp "--continue --skip --abort"
                return
        fi
-       case "${COMP_WORDS[COMP_CWORD-1]}" in
-       -s|--strategy)
-               __gitcomp "$(__git_merge_strategies)"
-               return
-       esac
+       __git_complete_strategy && return
        case "$cur" in
-       --strategy=*)
-               __gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}"
-               return
-               ;;
        --*)
                __gitcomp "--onto --merge --strategy --interactive"
                return
@@ -1149,10 +1294,14 @@ _git_config ()
                __gitcomp "$(__git_merge_strategies)"
                return
                ;;
-       color.branch|color.diff|color.status)
+       color.branch|color.diff|color.interactive|color.status|color.ui)
                __gitcomp "always never auto"
                return
                ;;
+       color.pager)
+               __gitcomp "false true"
+               return
+               ;;
        color.*.*)
                __gitcomp "
                        normal black red green yellow blue magenta cyan white
@@ -1367,7 +1516,7 @@ _git_config ()
 
 _git_remote ()
 {
-       local subcommands="add rm show prune update"
+       local subcommands="add rename rm show prune update"
        local subcommand="$(__git_find_subcommand "$subcommands")"
        if [ -z "$subcommand" ]; then
                __gitcomp "$subcommands"
@@ -1375,7 +1524,7 @@ _git_remote ()
        fi
 
        case "$subcommand" in
-       rm|show|prune)
+       rename|rm|show|prune)
                __gitcomp "$(__git_remotes)"
                ;;
        update)
@@ -1403,7 +1552,7 @@ _git_reset ()
        local cur="${COMP_WORDS[COMP_CWORD]}"
        case "$cur" in
        --*)
-               __gitcomp "--mixed --hard --soft"
+               __gitcomp "--merge --mixed --hard --soft"
                return
                ;;
        esac
@@ -1444,12 +1593,8 @@ _git_shortlog ()
        case "$cur" in
        --*)
                __gitcomp "
-                       --max-count= --max-age= --since= --after=
-                       --min-age= --before= --until=
-                       --no-merges
-                       --author= --committer= --grep=
-                       --all-match
-                       --not --all
+                       $__git_log_common_options
+                       $__git_log_shortlog_options
                        --numbered --summary
                        "
                return
@@ -1465,13 +1610,19 @@ _git_show ()
        local cur="${COMP_WORDS[COMP_CWORD]}"
        case "$cur" in
        --pretty=*)
-               __gitcomp "
-                       oneline short medium full fuller email raw
+               __gitcomp "$__git_log_pretty_formats
                        " "" "${cur##--pretty=}"
                return
                ;;
+       --format=*)
+               __gitcomp "$__git_log_pretty_formats
+                       " "" "${cur##--format=}"
+               return
+               ;;
        --*)
-               __gitcomp "--pretty="
+               __gitcomp "--pretty= --format=
+                       $__git_diff_common_options
+                       "
                return
                ;;
        esac
@@ -1547,7 +1698,8 @@ _git_svn ()
        local subcommands="
                init fetch clone rebase dcommit log find-rev
                set-tree commit-diff info create-ignore propget
-               proplist show-ignore show-externals
+               proplist show-ignore show-externals branch tag blame
+               migrate
                "
        local subcommand="$(__git_find_subcommand "$subcommands")"
        if [ -z "$subcommand" ]; then
@@ -1558,13 +1710,15 @@ _git_svn ()
                        --follow-parent --authors-file= --repack=
                        --no-metadata --use-svm-props --use-svnsync-props
                        --log-window-size= --no-checkout --quiet
-                       --repack-flags --user-log-author $remote_opts
+                       --repack-flags --use-log-author --localtime
+                       --ignore-paths= $remote_opts
                        "
                local init_opts="
                        --template= --shared= --trunk= --tags=
                        --branches= --stdlayout --minimize-url
                        --no-metadata --use-svm-props --use-svnsync-props
-                       --rewrite-root= $remote_opts
+                       --rewrite-root= --prefix= --use-log-author
+                       --add-author-from $remote_opts
                        "
                local cmt_opts="
                        --edit --rmdir --find-copies-harder --copy-similarity=
@@ -1584,7 +1738,8 @@ _git_svn ()
                dcommit,--*)
                        __gitcomp "
                                --merge --strategy= --verbose --dry-run
-                               --fetch-all --no-rebase $cmt_opts $fc_opts
+                               --fetch-all --no-rebase --commit-url
+                               --revision $cmt_opts $fc_opts
                                "
                        ;;
                set-tree,--*)
@@ -1598,13 +1753,13 @@ _git_svn ()
                        __gitcomp "
                                --limit= --revision= --verbose --incremental
                                --oneline --show-commit --non-recursive
-                               --authors-file=
+                               --authors-file= --color
                                "
                        ;;
                rebase,--*)
                        __gitcomp "
                                --merge --verbose --strategy= --local
-                               --fetch-all $fc_opts
+                               --fetch-all --dry-run $fc_opts
                                "
                        ;;
                commit-diff,--*)
@@ -1613,6 +1768,21 @@ _git_svn ()
                info,--*)
                        __gitcomp "--url"
                        ;;
+               branch,--*)
+                       __gitcomp "--dry-run --message --tag"
+                       ;;
+               tag,--*)
+                       __gitcomp "--dry-run --message"
+                       ;;
+               blame,--*)
+                       __gitcomp "--git-format"
+                       ;;
+               migrate,--*)
+                       __gitcomp "
+                               --config-dir= --ignore-paths= --minimize
+                               --no-auth-cache --username=
+                               "
+                       ;;
                *)
                        COMPREPLY=()
                        ;;
@@ -1672,7 +1842,6 @@ _git ()
 
        if [ -z "$command" ]; then
                case "${COMP_WORDS[COMP_CWORD]}" in
-               --*=*) COMPREPLY=() ;;
                --*)   __gitcomp "
                        --paginate
                        --no-pager
@@ -1736,6 +1905,7 @@ _git ()
        show)        _git_show ;;
        show-branch) _git_show_branch ;;
        stash)       _git_stash ;;
+       stage)       _git_add ;;
        submodule)   _git_submodule ;;
        svn)         _git_svn ;;
        tag)         _git_tag ;;
@@ -1749,27 +1919,34 @@ _gitk ()
        __git_has_doubledash && return
 
        local cur="${COMP_WORDS[COMP_CWORD]}"
-       local g="$(git rev-parse --git-dir 2>/dev/null)"
+       local g="$(__gitdir)"
        local merge=""
        if [ -f $g/MERGE_HEAD ]; then
                merge="--merge"
        fi
        case "$cur" in
        --*)
-               __gitcomp "--not --all $merge"
+               __gitcomp "
+                       $__git_log_common_options
+                       $__git_log_gitk_options
+                       $merge
+                       "
                return
                ;;
        esac
        __git_complete_revlist
 }
 
-complete -o default -o nospace -F _git git
-complete -o default -o nospace -F _gitk gitk
+complete -o bashdefault -o default -o nospace -F _git git 2>/dev/null \
+       || complete -o default -o nospace -F _git git
+complete -o bashdefault -o default -o nospace -F _gitk gitk 2>/dev/null \
+       || complete -o default -o nospace -F _gitk gitk
 
 # The following are necessary only for Cygwin, and only are needed
 # when the user has tab-completed the executable name and consequently
 # included the '.exe' suffix.
 #
 if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then
-complete -o default -o nospace -F _git git.exe
+complete -o bashdefault -o default -o nospace -F _git git.exe 2>/dev/null \
+       || complete -o default -o nospace -F _git git.exe
 fi
diff --git a/contrib/difftool/git-difftool b/contrib/difftool/git-difftool
new file mode 100755 (executable)
index 0000000..0cda3d2
--- /dev/null
@@ -0,0 +1,73 @@
+#!/usr/bin/env perl
+# Copyright (c) 2009 David Aguilar
+#
+# This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
+# git-difftool-helper script.  This script exports
+# GIT_EXTERNAL_DIFF and GIT_PAGER for use by git, and
+# GIT_DIFFTOOL_NO_PROMPT and GIT_MERGE_TOOL for use by git-difftool-helper.
+# Any arguments that are unknown to this script are forwarded to 'git diff'.
+
+use strict;
+use warnings;
+use Cwd qw(abs_path);
+use File::Basename qw(dirname);
+
+my $DIR = abs_path(dirname($0));
+
+
+sub usage
+{
+       print << 'USAGE';
+usage: git difftool [--tool=<tool>] [--no-prompt] ["git diff" options]
+USAGE
+       exit 1;
+}
+
+sub setup_environment
+{
+       $ENV{PATH} = "$DIR:$ENV{PATH}";
+       $ENV{GIT_PAGER} = '';
+       $ENV{GIT_EXTERNAL_DIFF} = 'git-difftool-helper';
+}
+
+sub exe
+{
+       my $exe = shift;
+       return defined $ENV{COMSPEC} ? "$exe.exe" : $exe;
+}
+
+sub generate_command
+{
+       my @command = (exe('git'), 'diff');
+       my $skip_next = 0;
+       my $idx = -1;
+       for my $arg (@ARGV) {
+               $idx++;
+               if ($skip_next) {
+                       $skip_next = 0;
+                       next;
+               }
+               if ($arg eq '-t' or $arg eq '--tool') {
+                       usage() if $#ARGV <= $idx;
+                       $ENV{GIT_MERGE_TOOL} = $ARGV[$idx + 1];
+                       $skip_next = 1;
+                       next;
+               }
+               if ($arg =~ /^--tool=/) {
+                       $ENV{GIT_MERGE_TOOL} = substr($arg, 7);
+                       next;
+               }
+               if ($arg eq '--no-prompt') {
+                       $ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true';
+                       next;
+               }
+               if ($arg eq '-h' or $arg eq '--help') {
+                       usage();
+               }
+               push @command, $arg;
+       }
+       return @command
+}
+
+setup_environment();
+exec(generate_command());
diff --git a/contrib/difftool/git-difftool-helper b/contrib/difftool/git-difftool-helper
new file mode 100755 (executable)
index 0000000..db3af6a
--- /dev/null
@@ -0,0 +1,240 @@
+#!/bin/sh
+# git-difftool-helper is a GIT_EXTERNAL_DIFF-compatible diff tool launcher.
+# It supports kdiff3, kompare, tkdiff, xxdiff, meld, opendiff,
+# emerge, ecmerge, vimdiff, gvimdiff, and custom user-configurable tools.
+# This script is typically launched by using the 'git difftool'
+# convenience command.
+#
+# Copyright (c) 2009 David Aguilar
+
+# Set GIT_DIFFTOOL_NO_PROMPT to bypass the per-file prompt.
+should_prompt () {
+       ! test -n "$GIT_DIFFTOOL_NO_PROMPT"
+}
+
+# Should we keep the backup .orig file?
+keep_backup_mode="$(git config --bool merge.keepBackup || echo true)"
+keep_backup () {
+       test "$keep_backup_mode" = "true"
+}
+
+# This function manages the backup .orig file.
+# A backup $MERGED.orig file is created if changes are detected.
+cleanup_temp_files () {
+       if test -n "$MERGED"; then
+               if keep_backup && test "$MERGED" -nt "$BACKUP"; then
+                       test -f "$BACKUP" && mv -- "$BACKUP" "$MERGED.orig"
+               else
+                       rm -f -- "$BACKUP"
+               fi
+       fi
+}
+
+# This is called when users Ctrl-C out of git-difftool-helper
+sigint_handler () {
+       cleanup_temp_files
+       exit 1
+}
+
+# This function prepares temporary files and launches the appropriate
+# merge tool.
+launch_merge_tool () {
+       # Merged is the filename as it appears in the work tree
+       # Local is the contents of a/filename
+       # Remote is the contents of b/filename
+       # Custom merge tool commands might use $BASE so we provide it
+       MERGED="$1"
+       LOCAL="$2"
+       REMOTE="$3"
+       BASE="$1"
+       ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')"
+       BACKUP="$MERGED.BACKUP.$ext"
+
+       # Create and ensure that we clean up $BACKUP
+       test -f "$MERGED" && cp -- "$MERGED" "$BACKUP"
+       trap sigint_handler INT
+
+       # $LOCAL and $REMOTE are temporary files so prompt
+       # the user with the real $MERGED name before launching $merge_tool.
+       if should_prompt; then
+               printf "\nViewing: '$MERGED'\n"
+               printf "Hit return to launch '%s': " "$merge_tool"
+               read ans
+       fi
+
+       # Run the appropriate merge tool command
+       case "$merge_tool" in
+       kdiff3)
+               basename=$(basename "$MERGED")
+               "$merge_tool_path" --auto \
+                       --L1 "$basename (A)" \
+                       --L2 "$basename (B)" \
+                       -o "$MERGED" "$LOCAL" "$REMOTE" \
+                       > /dev/null 2>&1
+               ;;
+
+       kompare)
+               "$merge_tool_path" "$LOCAL" "$REMOTE"
+               ;;
+
+       tkdiff)
+               "$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE"
+               ;;
+
+       meld)
+               "$merge_tool_path" "$LOCAL" "$REMOTE"
+               ;;
+
+       vimdiff)
+               "$merge_tool_path" -c "wincmd l" "$LOCAL" "$REMOTE"
+               ;;
+
+       gvimdiff)
+               "$merge_tool_path" -c "wincmd l" -f "$LOCAL" "$REMOTE"
+               ;;
+
+       xxdiff)
+               "$merge_tool_path" \
+                       -X \
+                       -R 'Accel.SaveAsMerged: "Ctrl-S"' \
+                       -R 'Accel.Search: "Ctrl+F"' \
+                       -R 'Accel.SearchForward: "Ctrl-G"' \
+                       --merged-file "$MERGED" \
+                       "$LOCAL" "$REMOTE"
+               ;;
+
+       opendiff)
+               "$merge_tool_path" "$LOCAL" "$REMOTE" \
+                       -merge "$MERGED" | cat
+               ;;
+
+       ecmerge)
+               "$merge_tool_path" "$LOCAL" "$REMOTE" \
+                       --default --mode=merge2 --to="$MERGED"
+               ;;
+
+       emerge)
+               "$merge_tool_path" -f emerge-files-command \
+                       "$LOCAL" "$REMOTE" "$(basename "$MERGED")"
+               ;;
+
+       *)
+               if test -n "$merge_tool_cmd"; then
+                       ( eval $merge_tool_cmd )
+               fi
+               ;;
+       esac
+
+       cleanup_temp_files
+}
+
+# Verifies that mergetool.<tool>.cmd exists
+valid_custom_tool() {
+       merge_tool_cmd="$(git config mergetool.$1.cmd)"
+       test -n "$merge_tool_cmd"
+}
+
+# Verifies that the chosen merge tool is properly setup.
+# Built-in merge tools are always valid.
+valid_tool() {
+       case "$1" in
+       kdiff3 | kompare | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge)
+               ;; # happy
+       *)
+               if ! valid_custom_tool "$1"
+               then
+                       return 1
+               fi
+               ;;
+       esac
+}
+
+# Sets up the merge_tool_path variable.
+# This handles the mergetool.<tool>.path configuration.
+init_merge_tool_path() {
+       merge_tool_path=$(git config mergetool."$1".path)
+       if test -z "$merge_tool_path"; then
+               case "$1" in
+               emerge)
+                       merge_tool_path=emacs
+                       ;;
+               *)
+                       merge_tool_path="$1"
+                       ;;
+               esac
+       fi
+}
+
+# Allow the GIT_MERGE_TOOL variable to provide a default value
+test -n "$GIT_MERGE_TOOL" && merge_tool="$GIT_MERGE_TOOL"
+
+# If not merge tool was specified then use the merge.tool
+# configuration variable.  If that's invalid then reset merge_tool.
+if test -z "$merge_tool"; then
+       merge_tool=$(git config merge.tool)
+       if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then
+               echo >&2 "git config option merge.tool set to unknown tool: $merge_tool"
+               echo >&2 "Resetting to default..."
+               unset merge_tool
+       fi
+fi
+
+# Try to guess an appropriate merge tool if no tool has been set.
+if test -z "$merge_tool"; then
+       # We have a $DISPLAY so try some common UNIX merge tools
+       if test -n "$DISPLAY"; then
+               # If gnome then prefer meld, otherwise, prefer kdiff3 or kompare
+               if test -n "$GNOME_DESKTOP_SESSION_ID" ; then
+                       merge_tool_candidates="meld kdiff3 kompare tkdiff xxdiff gvimdiff"
+               else
+                       merge_tool_candidates="kdiff3 kompare tkdiff xxdiff meld gvimdiff"
+               fi
+       fi
+       if echo "${VISUAL:-$EDITOR}" | grep 'emacs' > /dev/null 2>&1; then
+               # $EDITOR is emacs so add emerge as a candidate
+               merge_tool_candidates="$merge_tool_candidates emerge opendiff vimdiff"
+       elif echo "${VISUAL:-$EDITOR}" | grep 'vim' > /dev/null 2>&1; then
+               # $EDITOR is vim so add vimdiff as a candidate
+               merge_tool_candidates="$merge_tool_candidates vimdiff opendiff emerge"
+       else
+               merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff"
+       fi
+       echo "merge tool candidates: $merge_tool_candidates"
+
+       # Loop over each candidate and stop when a valid merge tool is found.
+       for i in $merge_tool_candidates
+       do
+               init_merge_tool_path $i
+               if type "$merge_tool_path" > /dev/null 2>&1; then
+                       merge_tool=$i
+                       break
+               fi
+       done
+
+       if test -z "$merge_tool" ; then
+               echo "No known merge resolution program available."
+               exit 1
+       fi
+
+else
+       # A merge tool has been set, so verify that it's valid.
+       if ! valid_tool "$merge_tool"; then
+               echo >&2 "Unknown merge tool $merge_tool"
+               exit 1
+       fi
+
+       init_merge_tool_path "$merge_tool"
+
+       if test -z "$merge_tool_cmd" && ! type "$merge_tool_path" > /dev/null 2>&1; then
+               echo "The merge tool $merge_tool is not available as '$merge_tool_path'"
+               exit 1
+       fi
+fi
+
+
+# Launch the merge tool on each path provided by 'git diff'
+while test $# -gt 6
+do
+       launch_merge_tool "$1" "$2" "$5"
+       shift 7
+done
diff --git a/contrib/difftool/git-difftool.txt b/contrib/difftool/git-difftool.txt
new file mode 100644 (file)
index 0000000..6e2610c
--- /dev/null
@@ -0,0 +1,105 @@
+git-difftool(1)
+===============
+
+NAME
+----
+git-difftool - compare changes using common merge tools
+
+SYNOPSIS
+--------
+'git difftool' [--tool=<tool>] [--no-prompt] ['git diff' options]
+
+DESCRIPTION
+-----------
+'git-difftool' is a git command that allows you to compare and edit files
+between revisions using common merge tools.  At its most basic level,
+'git-difftool' does what 'git-mergetool' does but its use is for non-merge
+situations such as when preparing commits or comparing changes against
+the index.
+
+'git difftool' is a frontend to 'git diff' and accepts the same
+arguments and options.
+
+See linkgit:git-diff[1] for the full list of supported options.
+
+OPTIONS
+-------
+-t <tool>::
+--tool=<tool>::
+       Use the merge resolution program specified by <tool>.
+       Valid merge tools are:
+       kdiff3, kompare, tkdiff, meld, xxdiff, emerge,
+       vimdiff, gvimdiff, ecmerge, and opendiff
++
+If a merge resolution program is not specified, 'git-difftool'
+will use the configuration variable `merge.tool`.  If the
+configuration variable `merge.tool` is not set, 'git difftool'
+will pick a suitable default.
++
+You can explicitly provide a full path to the tool by setting the
+configuration variable `mergetool.<tool>.path`. For example, you
+can configure the absolute path to kdiff3 by setting
+`mergetool.kdiff3.path`. Otherwise, 'git-difftool' assumes the
+tool is available in PATH.
++
+Instead of running one of the known merge tool programs,
+'git-difftool' can be customized to run an alternative program
+by specifying the command line to invoke in a configuration
+variable `mergetool.<tool>.cmd`.
++
+When 'git-difftool' is invoked with this tool (either through the
+`-t` or `--tool` option or the `merge.tool` configuration variable)
+the configured command line will be invoked with the following
+variables available: `$LOCAL` is set to the name of the temporary
+file containing the contents of the diff pre-image and `$REMOTE`
+is set to the name of the temporary file containing the contents
+of the diff post-image.  `$BASE` is provided for compatibility
+with custom merge tool commands and has the same value as `$LOCAL`.
+
+--no-prompt::
+       Do not prompt before launching a diff tool.
+
+CONFIG VARIABLES
+----------------
+merge.tool::
+       The default merge tool to use.
++
+See the `--tool=<tool>` option above for more details.
+
+merge.keepBackup::
+       The original, unedited file content can be saved to a file with
+       a `.orig` extension.  Defaults to `true` (i.e. keep the backup files).
+
+mergetool.<tool>.path::
+       Override the path for the given tool.  This is useful in case
+       your tool is not in the PATH.
+
+mergetool.<tool>.cmd::
+       Specify the command to invoke the specified merge tool.
++
+See the `--tool=<tool>` option above for more details.
+
+
+SEE ALSO
+--------
+linkgit:git-diff[1]::
+        Show changes between commits, commit and working tree, etc
+
+linkgit:git-mergetool[1]::
+       Run merge conflict resolution tools to resolve merge conflicts
+
+linkgit:git-config[1]::
+        Get and set repository or global options
+
+
+AUTHOR
+------
+Written by David Aguilar <davvid@gmail.com>.
+
+Documentation
+--------------
+Documentation by David Aguilar and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the linkgit:git[1] suite
index a48540a92b4aa5140a87469b36c1f9b3d8e46e7f..24d931294180c10646249bbbe31b5215f9996a66 100644 (file)
@@ -2,7 +2,7 @@
 
 EMACS = emacs
 
-ELC = git.elc vc-git.elc git-blame.elc
+ELC = git.elc git-blame.elc
 INSTALL ?= install
 INSTALL_ELC = $(INSTALL) -m 644
 prefix ?= $(HOME)
diff --git a/contrib/emacs/README b/contrib/emacs/README
new file mode 100644 (file)
index 0000000..82368bd
--- /dev/null
@@ -0,0 +1,39 @@
+This directory contains various modules for Emacs support.
+
+To make the modules available to Emacs, you should add this directory
+to your load-path, and then require the modules you want. This can be
+done by adding to your .emacs something like this:
+
+  (add-to-list 'load-path ".../git/contrib/emacs")
+  (require 'git)
+  (require 'git-blame)
+
+
+The following modules are available:
+
+* git.el:
+
+  Status manager that displays the state of all the files of the
+  project, and provides easy access to the most frequently used git
+  commands. The user interface is as far as possible compatible with
+  the pcl-cvs mode. It can be started with `M-x git-status'.
+
+* git-blame.el:
+
+  Emacs implementation of incremental git-blame.  When you turn it on
+  while viewing a file, the editor buffer will be updated by setting
+  the background of individual lines to a color that reflects which
+  commit it comes from.  And when you move around the buffer, a
+  one-line summary will be shown in the echo area.
+
+* vc-git.el:
+
+  This file used to contain the VC-mode backend for git, but it is no
+  longer distributed with git. It is now maintained as part of Emacs
+  and included in standard Emacs distributions starting from version
+  22.2.
+
+  If you have an earlier Emacs version, upgrading to Emacs 22 is
+  recommended, since the VC mode in older Emacs is not generic enough
+  to be able to support git in a reasonable manner, and no attempt has
+  been made to backport vc-git.el.
index 09e8bae3a41827a20f6f0693c28f91a98adcb8a4..eace9c18eb1d17075836694ce664a009f3e02038 100644 (file)
@@ -1,6 +1,6 @@
 ;;; git.el --- A user interface for git
 
-;; Copyright (C) 2005, 2006, 2007 Alexandre Julliard <julliard@winehq.org>
+;; Copyright (C) 2005, 2006, 2007, 2008, 2009 Alexandre Julliard <julliard@winehq.org>
 
 ;; Version: 1.0
 
 ;; To start: `M-x git-status'
 ;;
 ;; TODO
-;;  - portability to XEmacs
 ;;  - diff against other branch
 ;;  - renaming files from the status buffer
 ;;  - creating tags
 ;;  - fetch/pull
-;;  - switching branches
 ;;  - revlist browser
 ;;  - git-show-branch browser
-;;  - menus
+;;
+
+;;; Compatibility:
+;;
+;; This file works on GNU Emacs 21 or later. It may work on older
+;; versions but this is not guaranteed.
+;;
+;; It may work on XEmacs 21, provided that you first install the ewoc
+;; and log-edit packages.
 ;;
 
 (eval-when-compile (require 'cl))
@@ -222,7 +228,7 @@ the process output as a string, or nil if the git command failed."
     (with-current-buffer buffer
       (cd dir)
       (apply #'call-process-region start end program
-             nil (list output-buffer nil) nil args))))
+             nil (list output-buffer t) nil args))))
 
 (defun git-run-command-buffer (buffer-name &rest args)
   "Run a git command, sending the output to a buffer named BUFFER-NAME."
@@ -239,13 +245,15 @@ the process output as a string, or nil if the git command failed."
 
 (defun git-run-command-region (buffer start end env &rest args)
   "Run a git command with specified buffer region as input."
-  (unless (eq 0 (if env
-                    (git-run-process-region
-                     buffer start end "env"
-                     (append (git-get-env-strings env) (list "git") args))
+  (with-temp-buffer
+    (if (eq 0 (if env
                   (git-run-process-region
-                   buffer start end "git" args)))
-    (error "Failed to run \"git %s\":\n%s" (mapconcat (lambda (x) x) args " ") (buffer-string))))
+                   buffer start end "env"
+                   (append (git-get-env-strings env) (list "git") args))
+                (git-run-process-region buffer start end "git" args)))
+        (buffer-string)
+      (display-message-or-buffer (current-buffer))
+      nil)))
 
 (defun git-run-hook (hook env &rest args)
   "Run a git hook and display its output if any."
@@ -397,6 +405,17 @@ the process output as a string, or nil if the git command failed."
     (unless newval (push "-d" args))
     (apply 'git-call-process-display-error "update-ref" args)))
 
+(defun git-for-each-ref (&rest specs)
+  "Return a list of refs using git-for-each-ref.
+Each entry is a cons of (SHORT-NAME . FULL-NAME)."
+  (let (refs)
+    (with-temp-buffer
+      (apply #'git-call-process t "for-each-ref" "--format=%(refname)" specs)
+      (goto-char (point-min))
+      (while (re-search-forward "^[^/\n]+/[^/\n]+/\\(.+\\)$" nil t)
+       (push (cons (match-string 1) (match-string 0)) refs)))
+    (nreverse refs)))
+
 (defun git-read-tree (tree &optional index-file)
   "Read a tree into the index file."
   (let ((process-environment
@@ -447,18 +466,16 @@ the process output as a string, or nil if the git command failed."
       (setq coding-system-for-write buffer-file-coding-system))
     (let ((commit
            (git-get-string-sha1
-            (with-output-to-string
-              (with-current-buffer standard-output
-                (let ((env `(("GIT_AUTHOR_NAME" . ,author-name)
-                             ("GIT_AUTHOR_EMAIL" . ,author-email)
-                             ("GIT_COMMITTER_NAME" . ,(git-get-committer-name))
-                             ("GIT_COMMITTER_EMAIL" . ,(git-get-committer-email)))))
-                  (when author-date (push `("GIT_AUTHOR_DATE" . ,author-date) env))
-                  (apply #'git-run-command-region
-                         buffer log-start log-end env
-                         "commit-tree" tree (nreverse args))))))))
-      (and (git-update-ref "HEAD" commit head subject)
-           commit))))
+            (let ((env `(("GIT_AUTHOR_NAME" . ,author-name)
+                         ("GIT_AUTHOR_EMAIL" . ,author-email)
+                         ("GIT_COMMITTER_NAME" . ,(git-get-committer-name))
+                         ("GIT_COMMITTER_EMAIL" . ,(git-get-committer-email)))))
+              (when author-date (push `("GIT_AUTHOR_DATE" . ,author-date) env))
+              (apply #'git-run-command-region
+                     buffer log-start log-end env
+                     "commit-tree" tree (nreverse args))))))
+      (when commit (git-update-ref "HEAD" commit head subject))
+      commit)))
 
 (defun git-empty-db-p ()
   "Check if the git db is empty (no commit done yet)."
@@ -513,9 +530,9 @@ the process output as a string, or nil if the git command failed."
           (git-fileinfo->needs-refresh info) t)))
 
 (defun git-status-filenames-map (status func files &rest args)
-  "Apply FUNC to the status files names in the FILES list."
+  "Apply FUNC to the status files names in the FILES list.
+The list must be sorted."
   (when files
-    (setq files (sort files #'string-lessp))
     (let ((file (pop files))
           (node (ewoc-nth status 0)))
       (while (and file node)
@@ -528,7 +545,7 @@ the process output as a string, or nil if the git command failed."
             (setq file (pop files))))))))
 
 (defun git-set-filenames-state (status files state)
-  "Set the state of a list of named files."
+  "Set the state of a list of named files. The list must be sorted"
   (when files
     (git-status-filenames-map status #'git-set-fileinfo-state files state)
     (unless state  ;; delete files whose state has been set to nil
@@ -562,29 +579,29 @@ the process output as a string, or nil if the git command failed."
   (let* ((old-type (lsh (or old-perm 0) -9))
         (new-type (lsh (or new-perm 0) -9))
         (str (case new-type
-               (?\100  ;; file
+               (64  ;; file
                 (case old-type
-                  (?\100 nil)
-                  (?\120 "   (type change symlink -> file)")
-                  (?\160 "   (type change subproject -> file)")))
-                (?\120  ;; symlink
+                  (64 nil)
+                  (80 "   (type change symlink -> file)")
+                  (112 "   (type change subproject -> file)")))
+                (80  ;; symlink
                  (case old-type
-                   (?\100 "   (type change file -> symlink)")
-                   (?\160 "   (type change subproject -> symlink)")
+                   (64 "   (type change file -> symlink)")
+                   (112 "   (type change subproject -> symlink)")
                    (t "   (symlink)")))
-                 (?\160  ;; subproject
+                 (112  ;; subproject
                   (case old-type
-                    (?\100 "   (type change file -> subproject)")
-                    (?\120 "   (type change symlink -> subproject)")
+                    (64 "   (type change file -> subproject)")
+                    (80 "   (type change symlink -> subproject)")
                     (t "   (subproject)")))
-                  (?\110 nil)  ;; directory (internal, not a real git state)
-                 (?\000  ;; deleted or unknown
+                  (72 nil)  ;; directory (internal, not a real git state)
+                 (0  ;; deleted or unknown
                   (case old-type
-                    (?\120 "   (symlink)")
-                    (?\160 "   (subproject)")))
+                    (80 "   (symlink)")
+                    (112 "   (subproject)")))
                  (t (format "   (unknown type %o)" new-type)))))
     (cond (str (propertize str 'face 'git-status-face))
-          ((eq new-type ?\110) "/")
+          ((eq new-type 72) "/")
           (t ""))))
 
 (defun git-rename-as-string (info)
@@ -733,6 +750,7 @@ Return the list of files that haven't been handled."
     (let (unmerged-files)
       (while (re-search-forward "[0-7]\\{6\\} [0-9a-f]\\{40\\} [123]\t\\([^\0]+\\)\0" nil t)
         (push (match-string 1) unmerged-files))
+      (setq unmerged-files (nreverse unmerged-files))  ;; assume it is sorted already
       (git-set-filenames-state status unmerged-files 'unmerged))))
 
 (defun git-get-exclude-files ()
@@ -753,17 +771,18 @@ Return the list of files that haven't been handled."
            (append options (mapcar (lambda (f) (concat "--exclude-from=" f)) exclude-files)))))
 
 (defun git-update-status-files (&optional files mark-files)
-  "Update the status of FILES from the index."
+  "Update the status of FILES from the index.
+The FILES list must be sorted."
   (unless git-status (error "Not in git-status buffer."))
   ;; set the needs-update flag on existing files
-  (if (setq files (sort files #'string-lessp))
+  (if files
       (git-status-filenames-map
        git-status (lambda (info) (setf (git-fileinfo->needs-update info) t)) files)
     (ewoc-map (lambda (info) (setf (git-fileinfo->needs-update info) t) nil) git-status)
     (git-call-process nil "update-index" "--refresh")
     (when git-show-uptodate
       (git-run-ls-files-cached git-status nil 'uptodate)))
-  (let* ((remaining-files
+  (let ((remaining-files
           (if (git-empty-db-p) ; we need some special handling for an empty db
              (git-run-ls-files-cached git-status files 'added)
             (git-run-diff-index git-status files))))
@@ -808,13 +827,13 @@ Return the list of files that haven't been handled."
       (list (ewoc-data (ewoc-locate git-status)))))
 
 (defun git-marked-files-state (&rest states)
-  "Return marked files that are in the specified states."
+  "Return a sorted list of marked files that are in the specified states."
   (let ((files (git-marked-files))
         result)
     (dolist (info files)
       (when (memq (git-fileinfo->state info) states)
         (push info result)))
-    result))
+    (nreverse result)))
 
 (defun git-refresh-files ()
   "Refresh all files that need it and clear the needs-refresh flag."
@@ -1049,7 +1068,9 @@ Return the list of files that haven't been handled."
     (unless files
       (push (file-relative-name (read-file-name "File to remove: " nil nil t)) files))
     (if (yes-or-no-p
-         (format "Remove %d file%s? " (length files) (if (> (length files) 1) "s" "")))
+         (if (cdr files)
+             (format "Remove %d files? " (length files))
+           (format "Remove %s? " (car files))))
         (progn
           (dolist (name files)
             (ignore-errors
@@ -1068,7 +1089,9 @@ Return the list of files that haven't been handled."
         added modified)
     (when (and files
                (yes-or-no-p
-                (format "Revert %d file%s? " (length files) (if (> (length files) 1) "s" ""))))
+                (if (cdr files)
+                    (format "Revert %d files? " (length files))
+                  (format "Revert %s? " (git-fileinfo->name (car files))))))
       (dolist (info files)
         (case (git-fileinfo->state info)
           ('added (push (git-fileinfo->name info) added))
@@ -1084,13 +1107,14 @@ Return the list of files that haven't been handled."
                  (or (not added)
                      (apply 'git-call-process-display-error "update-index" "--force-remove" "--" added))
                  (or (not modified)
-                     (apply 'git-call-process-display-error "checkout" "HEAD" modified)))))
-        (git-update-status-files (append added modified))
+                     (apply 'git-call-process-display-error "checkout" "HEAD" modified))))
+            (names (git-get-filenames files)))
+        (git-update-status-files names)
         (when ok
           (dolist (file modified)
             (let ((buffer (get-file-buffer file)))
               (when buffer (with-current-buffer buffer (revert-buffer t t t)))))
-          (git-success-message "Reverted" (git-get-filenames files)))))))
+          (git-success-message "Reverted" names))))))
 
 (defun git-resolve-file ()
   "Resolve conflicts in marked file(s)."
@@ -1320,6 +1344,7 @@ Return the list of files that haven't been handled."
                                         (log-edit-diff-function . git-log-edit-diff)) buffer)
        (log-edit 'git-do-commit nil 'git-log-edit-files buffer))
       (setq font-lock-keywords (font-lock-compile-keywords git-log-edit-font-lock-keywords))
+      (setq paragraph-separate (concat (regexp-quote git-log-msg-separator) "$\\|Author: \\|Date: \\|Merge: \\|Signed-off-by: \\|\f\\|[        ]*$"))
       (setq buffer-file-coding-system coding-system)
       (re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t))))
 
@@ -1347,14 +1372,44 @@ Return the list of files that haven't been handled."
                           (mapconcat #'identity msg "\n"))))
 
 (defun git-get-commit-files (commit)
-  "Retrieve the list of files modified by COMMIT."
+  "Retrieve a sorted list of files modified by COMMIT."
   (let (files)
     (with-temp-buffer
       (git-call-process t "diff-tree" "-m" "-r" "-z" "--name-only" "--no-commit-id" "--root" commit)
       (goto-char (point-min))
       (while (re-search-forward "\\([^\0]*\\)\0" nil t 1)
         (push (match-string 1) files)))
-    files))
+    (sort files #'string-lessp)))
+
+(defun git-read-commit-name (prompt &optional default)
+  "Ask for a commit name, with completion for local branch, remote branch and tag."
+  (completing-read prompt
+                   (list* "HEAD" "ORIG_HEAD" "FETCH_HEAD" (mapcar #'car (git-for-each-ref)))
+                  nil nil nil nil default))
+
+(defun git-checkout (branch &optional merge)
+  "Checkout a branch, tag, or any commit.
+Use a prefix arg if git should merge while checking out."
+  (interactive
+   (list (git-read-commit-name "Checkout: ")
+         current-prefix-arg))
+  (unless git-status (error "Not in git-status buffer."))
+  (let ((args (list branch "--")))
+    (when merge (push "-m" args))
+    (when (apply #'git-call-process-display-error "checkout" args)
+      (git-update-status-files))))
+
+(defun git-branch (branch)
+  "Create a branch from the current HEAD and switch to it."
+  (interactive (list (git-read-commit-name "Branch: ")))
+  (unless git-status (error "Not in git-status buffer."))
+  (if (git-rev-parse (concat "refs/heads/" branch))
+      (if (yes-or-no-p (format "Branch %s already exists, replace it? " branch))
+          (and (git-call-process-display-error "branch" "-f" branch)
+               (git-call-process-display-error "checkout" branch))
+        (message "Canceled."))
+    (git-call-process-display-error "checkout" "-b" branch))
+    (git-refresh-ewoc-hf git-status))
 
 (defun git-amend-commit ()
   "Undo the last commit on HEAD, and set things up to commit an
@@ -1372,6 +1427,44 @@ amended version of it."
       (git-setup-commit-buffer commit)
       (git-commit-file))))
 
+(defun git-cherry-pick-commit (arg)
+  "Cherry-pick a commit."
+  (interactive (list (git-read-commit-name "Cherry-pick commit: ")))
+  (unless git-status (error "Not in git-status buffer."))
+  (let ((commit (git-rev-parse (concat arg "^0"))))
+    (unless commit (error "Not a valid commit '%s'." arg))
+    (when (git-rev-parse (concat commit "^2"))
+      (error "Cannot cherry-pick a merge commit."))
+    (let ((files (git-get-commit-files commit))
+          (ok (git-call-process-display-error "cherry-pick" "-n" commit)))
+      (git-update-status-files files ok)
+      (with-current-buffer (git-setup-commit-buffer commit)
+        (goto-char (point-min))
+        (if (re-search-forward "^\n*Signed-off-by:" nil t 1)
+            (goto-char (match-beginning 0))
+          (goto-char (point-max)))
+        (insert "(cherry picked from commit " commit ")\n"))
+      (when ok (git-commit-file)))))
+
+(defun git-revert-commit (arg)
+  "Revert a commit."
+  (interactive (list (git-read-commit-name "Revert commit: ")))
+  (unless git-status (error "Not in git-status buffer."))
+  (let ((commit (git-rev-parse (concat arg "^0"))))
+    (unless commit (error "Not a valid commit '%s'." arg))
+    (when (git-rev-parse (concat commit "^2"))
+      (error "Cannot revert a merge commit."))
+    (let ((files (git-get-commit-files commit))
+          (subject (git-get-commit-description commit))
+          (ok (git-call-process-display-error "revert" "-n" commit)))
+      (git-update-status-files files ok)
+      (when (string-match "^[0-9a-f]+ - \\(.*\\)$" subject)
+        (setq subject (match-string 1 subject)))
+      (git-setup-log-buffer (get-buffer-create "*git-commit*")
+                            (git-get-merge-heads) nil nil (format "Revert \"%s\"" subject) nil
+                            (format "This reverts commit %s.\n" commit))
+      (when ok (git-commit-file)))))
+
 (defun git-find-file ()
   "Visit the current file in its own buffer."
   (interactive)
@@ -1471,6 +1564,10 @@ amended version of it."
     (define-key map "\M-\C-?" 'git-unmark-all)
     ; the commit submap
     (define-key commit-map "\C-a" 'git-amend-commit)
+    (define-key commit-map "\C-b" 'git-branch)
+    (define-key commit-map "\C-o" 'git-checkout)
+    (define-key commit-map "\C-p" 'git-cherry-pick-commit)
+    (define-key commit-map "\C-v" 'git-revert-commit)
     ; the diff submap
     (define-key diff-map "b" 'git-diff-file-base)
     (define-key diff-map "c" 'git-diff-file-combined)
@@ -1491,6 +1588,10 @@ amended version of it."
     `("Git"
       ["Refresh" git-refresh-status t]
       ["Commit" git-commit-file t]
+      ["Checkout..." git-checkout t]
+      ["New Branch..." git-branch t]
+      ["Cherry-pick Commit..." git-cherry-pick-commit t]
+      ["Revert Commit..." git-revert-commit t]
       ("Merge"
        ["Next Unmerged File" git-next-unmerged-file t]
        ["Prev Unmerged File" git-prev-unmerged-file t]
diff --git a/contrib/emacs/vc-git.el b/contrib/emacs/vc-git.el
deleted file mode 100644 (file)
index b8f6be5..0000000
+++ /dev/null
@@ -1,216 +0,0 @@
-;;; vc-git.el --- VC backend for the git version control system
-
-;; Copyright (C) 2006 Alexandre Julliard
-
-;; 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 of
-;; the License, 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., 59 Temple Place, Suite 330, Boston,
-;; MA 02111-1307 USA
-
-;;; Commentary:
-
-;; This file contains a VC backend for the git version control
-;; system.
-;;
-;; To install: put this file on the load-path and add GIT to the list
-;; of supported backends in `vc-handled-backends'; the following line,
-;; placed in your ~/.emacs, will accomplish this:
-;;
-;;     (add-to-list 'vc-handled-backends 'GIT)
-;;
-;; TODO
-;;  - changelog generation
-;;  - working with revisions other than HEAD
-;;
-
-(eval-when-compile (require 'cl))
-
-(defvar git-commits-coding-system 'utf-8
-  "Default coding system for git commits.")
-
-(defun vc-git--run-command-string (file &rest args)
-  "Run a git command on FILE and return its output as string."
-  (let* ((ok t)
-         (str (with-output-to-string
-                (with-current-buffer standard-output
-                  (unless (eq 0 (apply #'call-process "git" nil '(t nil) nil
-                                       (append args (list (file-relative-name file)))))
-                    (setq ok nil))))))
-    (and ok str)))
-
-(defun vc-git--run-command (file &rest args)
-  "Run a git command on FILE, discarding any output."
-  (let ((name (file-relative-name file)))
-    (eq 0 (apply #'call-process "git" nil (get-buffer "*Messages") nil (append args (list name))))))
-
-(defun vc-git-registered (file)
-  "Check whether FILE is registered with git."
-  (with-temp-buffer
-    (let* ((dir (file-name-directory file))
-           (name (file-relative-name file dir)))
-      (and (ignore-errors
-             (when dir (cd dir))
-             (eq 0 (call-process "git" nil '(t nil) nil "ls-files" "-c" "-z" "--" name)))
-           (let ((str (buffer-string)))
-             (and (> (length str) (length name))
-                  (string= (substring str 0 (1+ (length name))) (concat name "\0"))))))))
-
-(defun vc-git-state (file)
-  "git-specific version of `vc-state'."
-  (let ((diff (vc-git--run-command-string file "diff-index" "-z" "HEAD" "--")))
-    (if (and diff (string-match ":[0-7]\\{6\\} [0-7]\\{6\\} [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} [ADMU]\0[^\0]+\0" diff))
-        'edited
-      'up-to-date)))
-
-(defun vc-git-workfile-version (file)
-  "git-specific version of `vc-workfile-version'."
-  (let ((str (with-output-to-string
-               (with-current-buffer standard-output
-                 (call-process "git" nil '(t nil) nil "symbolic-ref" "HEAD")))))
-    (if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str)
-        (match-string 2 str)
-      str)))
-
-(defun vc-git-symbolic-commit (commit)
-  "Translate COMMIT string into symbolic form.
-Returns nil if not possible."
-  (and commit
-       (with-temp-buffer
-        (and
-         (zerop
-          (call-process "git" nil '(t nil) nil "name-rev"
-                        "--name-only" "--tags"
-                        commit))
-         (goto-char (point-min))
-         (= (forward-line 2) 1)
-         (bolp)
-         (buffer-substring-no-properties (point-min) (1- (point-max)))))))
-
-(defun vc-git-previous-version (file rev)
-  "git-specific version of `vc-previous-version'."
-  (let ((default-directory (file-name-directory (expand-file-name file)))
-       (file (file-name-nondirectory file)))
-    (vc-git-symbolic-commit
-     (with-temp-buffer
-       (and
-       (zerop
-        (call-process "git" nil '(t nil) nil "rev-list"
-                      "-2" rev "--" file))
-       (goto-char (point-max))
-       (bolp)
-       (zerop (forward-line -1))
-       (not (bobp))
-       (buffer-substring-no-properties
-          (point)
-          (1- (point-max))))))))
-
-(defun vc-git-next-version (file rev)
-  "git-specific version of `vc-next-version'."
-  (let* ((default-directory (file-name-directory
-                            (expand-file-name file)))
-       (file (file-name-nondirectory file))
-       (current-rev
-        (with-temp-buffer
-          (and
-           (zerop
-            (call-process "git" nil '(t nil) nil "rev-list"
-                          "-1" rev "--" file))
-           (goto-char (point-max))
-           (bolp)
-           (zerop (forward-line -1))
-           (bobp)
-           (buffer-substring-no-properties
-            (point)
-            (1- (point-max)))))))
-    (and current-rev
-        (vc-git-symbolic-commit
-         (with-temp-buffer
-           (and
-            (zerop
-             (call-process "git" nil '(t nil) nil "rev-list"
-                           "HEAD" "--" file))
-            (goto-char (point-min))
-            (search-forward current-rev nil t)
-            (zerop (forward-line -1))
-            (buffer-substring-no-properties
-             (point)
-             (progn (forward-line 1) (1- (point))))))))))
-
-(defun vc-git-revert (file &optional contents-done)
-  "Revert FILE to the version stored in the git repository."
-  (if contents-done
-      (vc-git--run-command file "update-index" "--")
-    (vc-git--run-command file "checkout" "HEAD")))
-
-(defun vc-git-checkout-model (file)
-  'implicit)
-
-(defun vc-git-workfile-unchanged-p (file)
-  (let ((sha1 (vc-git--run-command-string file "hash-object" "--"))
-        (head (vc-git--run-command-string file "ls-tree" "-z" "HEAD" "--")))
-    (and head
-         (string-match "[0-7]\\{6\\} blob \\([0-9a-f]\\{40\\}\\)\t[^\0]+\0" head)
-         (string= (car (split-string sha1 "\n")) (match-string 1 head)))))
-
-(defun vc-git-register (file &optional rev comment)
-  "Register FILE into the git version-control system."
-  (vc-git--run-command file "update-index" "--add" "--"))
-
-(defun vc-git-print-log (file &optional buffer)
-  (let ((name (file-relative-name file))
-        (coding-system-for-read git-commits-coding-system))
-    (vc-do-command buffer 'async "git" name "rev-list" "--pretty" "HEAD" "--")))
-
-(defun vc-git-diff (file &optional rev1 rev2 buffer)
-  (let ((name (file-relative-name file))
-        (buf (or buffer "*vc-diff*")))
-    (if (and rev1 rev2)
-        (vc-do-command buf 0 "git" name "diff-tree" "-p" rev1 rev2 "--")
-      (vc-do-command buf 0 "git" name "diff-index" "-p" (or rev1 "HEAD") "--"))
-    ; git-diff-index doesn't set exit status like diff does
-    (if (vc-git-workfile-unchanged-p file) 0 1)))
-
-(defun vc-git-checkin (file rev comment)
-  (let ((coding-system-for-write git-commits-coding-system))
-    (vc-git--run-command file "commit" "-m" comment "--only" "--")))
-
-(defun vc-git-checkout (file &optional editable rev destfile)
-  (if destfile
-      (let ((fullname (substring
-                       (vc-git--run-command-string file "ls-files" "-z" "--full-name" "--")
-                       0 -1))
-            (coding-system-for-read 'no-conversion)
-            (coding-system-for-write 'no-conversion))
-        (with-temp-file destfile
-          (eq 0 (call-process "git" nil t nil "cat-file" "blob"
-                              (concat (or rev "HEAD") ":" fullname)))))
-    (vc-git--run-command file "checkout" (or rev "HEAD"))))
-
-(defun vc-git-annotate-command (file buf &optional rev)
-  ; FIXME: rev is ignored
-  (let ((name (file-relative-name file)))
-    (call-process "git" nil buf nil "blame" name)))
-
-(defun vc-git-annotate-time ()
-  (and (re-search-forward "[0-9a-f]+ (.* \\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\) \\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\) \\([-+0-9]+\\) +[0-9]+)" nil t)
-       (vc-annotate-convert-time
-        (apply #'encode-time (mapcar (lambda (match) (string-to-number (match-string match))) '(6 5 4 3 2 1 7))))))
-
-;; Not really useful since we can't do anything with the revision yet
-;;(defun vc-annotate-extract-revision-at-line ()
-;;  (save-excursion
-;;    (move-beginning-of-line 1)
-;;    (and (looking-at "[0-9a-f]+")
-;;         (buffer-substring (match-beginning 0) (match-end 0)))))
-
-(provide 'vc-git)
index a13bb6afec2fe5e0b5249523ec8c62d8e517de88..4576c4a862c8ea0565a67eccdae6ef1ac4d9af9a 100755 (executable)
@@ -287,9 +287,9 @@ ($$)
 my $last_branch;
 my $current_rev = $opt_s || 1;
 unless(-d $git_dir) {
-       system("git-init");
+       system("git init");
        die "Cannot init the GIT db at $git_tree: $?\n" if $?;
-       system("git-read-tree");
+       system("git read-tree");
        die "Cannot init an empty tree: $?\n" if $?;
 
        $last_branch = $opt_o;
@@ -303,7 +303,7 @@ ($$)
        -f "$git_dir/svn2git"
                or die "'$git_dir/svn2git' does not exist.\n".
                       "You need that file for incremental imports.\n";
-       open(F, "git-symbolic-ref HEAD |") or
+       open(F, "git symbolic-ref HEAD |") or
                die "Cannot run git-symbolic-ref: $!\n";
        chomp ($last_branch = <F>);
        $last_branch = basename($last_branch);
@@ -331,7 +331,7 @@ ($$)
                                "$git_dir/refs/heads/$opt_o") == 0;
 
        # populate index
-       system('git-read-tree', $last_rev);
+       system('git', 'read-tree', $last_rev);
        die "read-tree failed: $?\n" if $?;
 
        # Get the last import timestamps
@@ -399,7 +399,7 @@ ($$$)
        my $pid = open(my $F, '-|');
        die $! unless defined $pid;
        if (!$pid) {
-           exec("git-hash-object", "-w", $name)
+           exec("git", "hash-object", "-w", $name)
                or die "Cannot create object: $!\n";
        }
        my $sha = <$F>;
@@ -423,7 +423,7 @@ ($$$$$)
                my $pid = open(my $F, '-|');
                die $! unless defined $pid;
                if (!$pid) {
-                       exec("git-hash-object", "-w", $name)
+                       exec("git", "hash-object", "-w", $name)
                            or die "Cannot create object: $!\n";
                }
                my $sha = <$F>;
@@ -547,7 +547,7 @@ ($$$$$$$$)
        my $pid = open my $f,'-|';
        die $! unless defined $pid;
        if (!$pid) {
-               exec("git-ls-tree","-r","-z",$gitrev,$srcpath)
+               exec("git","ls-tree","-r","-z",$gitrev,$srcpath)
                        or die $!;
        }
        local $/ = "\0";
@@ -634,7 +634,7 @@ sub commit {
 
        my $rev;
        if($revision > $opt_s and defined $parent) {
-               open(H,'-|',"git-rev-parse","--verify",$parent);
+               open(H,'-|',"git","rev-parse","--verify",$parent);
                $rev = <H>;
                close(H) or do {
                        print STDERR "$revision: cannot find commit '$parent'!\n";
@@ -671,7 +671,7 @@ sub commit {
                unlink($git_index);
        } elsif ($rev ne $last_rev) {
                print "Switching from $last_rev to $rev ($branch)\n" if $opt_v;
-               system("git-read-tree", $rev);
+               system("git", "read-tree", $rev);
                die "read-tree failed for $rev: $?\n" if $?;
                $last_rev = $rev;
        }
@@ -740,7 +740,7 @@ sub commit {
                        my $pid = open my $F, "-|";
                        die "$!" unless defined $pid;
                        if (!$pid) {
-                               exec("git-ls-files", "-z", @o1) or die $!;
+                               exec("git", "ls-files", "-z", @o1) or die $!;
                        }
                        @o1 = ();
                        local $/ = "\0";
@@ -758,7 +758,7 @@ sub commit {
                                        @o2 = @o1;
                                        @o1 = ();
                                }
-                               system("git-update-index","--force-remove","--",@o2);
+                               system("git","update-index","--force-remove","--",@o2);
                                die "Cannot remove files: $?\n" if $?;
                        }
                }
@@ -770,7 +770,7 @@ sub commit {
                                @n2 = @new;
                                @new = ();
                        }
-                       system("git-update-index","--add",
+                       system("git","update-index","--add",
                                (map { ('--cacheinfo', @$_) } @n2));
                        die "Cannot add files: $?\n" if $?;
                }
@@ -778,7 +778,7 @@ sub commit {
                my $pid = open(C,"-|");
                die "Cannot fork: $!" unless defined $pid;
                unless($pid) {
-                       exec("git-write-tree");
+                       exec("git","write-tree");
                        die "Cannot exec git-write-tree: $!\n";
                }
                chomp(my $tree = <C>);
@@ -830,7 +830,7 @@ sub commit {
                                "GIT_COMMITTER_NAME=$committer_name",
                                "GIT_COMMITTER_EMAIL=$committer_email",
                                "GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
-                               "git-commit-tree", $tree,@par);
+                               "git", "commit-tree", $tree,@par);
                        die "Cannot exec git-commit-tree: $!\n";
                }
                $pw->writer();
@@ -874,7 +874,7 @@ sub commit {
 
                $dest =~ tr/_/\./ if $opt_u;
 
-               system('git-tag', '-f', $dest, $cid) == 0
+               system('git', 'tag', '-f', $dest, $cid) == 0
                        or die "Cannot create tag $dest: $!\n";
 
                print "Created tag '$dest' on '$branch'\n" if $opt_v;
@@ -937,7 +937,7 @@ sub commit_all {
        my $pid = fork();
        die "Fork: $!\n" unless defined $pid;
        unless($pid) {
-               exec("git-repack", "-d")
+               exec("git", "repack", "-d")
                        or die "Cannot repack: $!\n";
        }
        waitpid($pid, 0);
@@ -958,7 +958,7 @@ sub commit_all {
        system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
                if $forward_master;
        unless ($opt_i) {
-               system('git-read-tree', '-m', '-u', 'SVN2GIT_HEAD', 'HEAD');
+               system('git', 'read-tree', '-m', '-u', 'SVN2GIT_HEAD', 'HEAD');
                die "read-tree failed: $?\n" if $?;
        }
 } else {
@@ -966,7 +966,7 @@ sub commit_all {
        print "DONE; creating $orig_branch branch\n" if $opt_v and (not defined $opt_l or $opt_l > 0);
        system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
                unless -f "$git_dir/refs/heads/master";
-       system('git-update-ref', 'HEAD', "$orig_branch");
+       system('git', 'update-ref', 'HEAD', "$orig_branch");
        unless ($opt_i) {
                system('git checkout');
                die "checkout failed: $?\n" if $?;
index 71aad8b45bd4c5f59c2ce3746cb3299821729c2a..3bb871e42f9c786eb8c40553ea3b0e0eb433b195 100644 (file)
@@ -114,9 +114,9 @@ due to SVN memory leaks. (These have been worked around.)
 -R <repack_each_revs>::
        Specify how often git repository should be repacked.
 +
-The default value is 1000. git-svnimport will do import in chunks of 1000
-revisions, after each chunk git repository will be repacked. To disable
-this behavior specify some big value here which is mote than number of
+The default value is 1000. git-svnimport will do imports in chunks of 1000
+revisions, after each chunk the git repository will be repacked. To disable
+this behavior specify some large value here which is greater than the number of
 revisions to import.
 
 -P <path_from_trunk>::
index a85a7b2a583ee9270fc2d765ec8c8c6e9d6b5e32..342529db309821f461e8f77d05bc5e01c76802ec 100755 (executable)
@@ -442,13 +442,14 @@ def p4ChangesForPaths(depotPaths, changeRange):
     output = p4_read_pipe_lines("changes " + ' '.join (["%s...%s" % (p, changeRange)
                                                         for p in depotPaths]))
 
-    changes = []
+    changes = {}
     for line in output:
-        changeNum = line.split(" ")[1]
-        changes.append(int(changeNum))
+       changeNum = int(line.split(" ")[1])
+       changes[changeNum] = True
 
-    changes.sort()
-    return changes
+    changelist = changes.keys()
+    changelist.sort()
+    return changelist
 
 class Command:
     def __init__(self):
@@ -1141,7 +1142,7 @@ class P4Sync(Command):
 
         s = ''
         for (key, val) in self.users.items():
-            s += "%s\t%s\n" % (key, val)
+           s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1))
 
         open(self.getUserCacheFilename(), "wb").write(s)
         self.userMapFromPerforceServer = True
diff --git a/contrib/git-resurrect.sh b/contrib/git-resurrect.sh
new file mode 100755 (executable)
index 0000000..c364dda
--- /dev/null
@@ -0,0 +1,180 @@
+#!/bin/sh
+
+USAGE="[-a] [-r] [-m] [-t] [-n] [-b <newname>] <name>"
+LONG_USAGE="git-resurrect attempts to find traces of a branch tip
+called <name>, and tries to resurrect it.  Currently, the reflog is
+searched for checkout messages, and with -r also merge messages.  With
+-m and -t, the history of all refs is scanned for Merge <name> into
+other/Merge <other> into <name> (respectively) commit subjects, which
+is rather slow but allows you to resurrect other people's topic
+branches."
+
+OPTIONS_SPEC="\
+git resurrect $USAGE
+--
+b,branch=            save branch as <newname> instead of <name>
+a,all                same as -l -r -m -t
+k,keep-going         full rev-list scan (instead of first match)
+l,reflog             scan reflog for checkouts (enabled by default)
+r,reflog-merges      scan for merges recorded in reflog
+m,merges             scan for merges into other branches (slow)
+t,merge-targets      scan for merges of other branches into <name>
+n,dry-run            don't recreate the branch"
+
+. git-sh-setup
+
+search_reflog () {
+        sed -ne 's~^\([^ ]*\) .*\tcheckout: moving from '"$1"' .*~\1~p' \
+                < "$GIT_DIR"/logs/HEAD
+}
+
+search_reflog_merges () {
+       git rev-parse $(
+               sed -ne 's~^[^ ]* \([^ ]*\) .*\tmerge '"$1"':.*~\1^2~p' \
+                       < "$GIT_DIR"/logs/HEAD
+       )
+}
+
+_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"
+
+search_merges () {
+        git rev-list --all --grep="Merge branch '$1'" \
+                --pretty=tformat:"%P %s" |
+        sed -ne "/^$_x40 \($_x40\) Merge .*/ {s//\1/p;$early_exit}"
+}
+
+search_merge_targets () {
+       git rev-list --all --grep="Merge branch '[^']*' into $branch\$" \
+               --pretty=tformat:"%H %s" --all |
+       sed -ne "/^\($_x40\) Merge .*/ {s//\1/p;$early_exit} "
+}
+
+dry_run=
+early_exit=q
+scan_reflog=t
+scan_reflog_merges=
+scan_merges=
+scan_merge_targets=
+new_name=
+
+while test "$#" != 0; do
+       case "$1" in
+           -b|--branch)
+               shift
+               new_name="$1"
+               ;;
+           -n|--dry-run)
+               dry_run=t
+               ;;
+           --no-dry-run)
+               dry_run=
+               ;;
+           -k|--keep-going)
+               early_exit=
+               ;;
+           --no-keep-going)
+               early_exit=q
+               ;;
+           -m|--merges)
+               scan_merges=t
+               ;;
+           --no-merges)
+               scan_merges=
+               ;;
+           -l|--reflog)
+               scan_reflog=t
+               ;;
+           --no-reflog)
+               scan_reflog=
+               ;;
+           -r|--reflog_merges)
+               scan_reflog_merges=t
+               ;;
+           --no-reflog_merges)
+               scan_reflog_merges=
+               ;;
+           -t|--merge-targets)
+               scan_merge_targets=t
+               ;;
+           --no-merge-targets)
+               scan_merge_targets=
+               ;;
+           -a|--all)
+               scan_reflog=t
+               scan_reflog_merges=t
+               scan_merges=t
+               scan_merge_targets=t
+               ;;
+           --)
+               shift
+               break
+               ;;
+           *)
+               usage
+               ;;
+       esac
+       shift
+done
+
+test "$#" = 1 || usage
+
+all_strategies="$scan_reflog$scan_reflog_merges$scan_merges$scan_merge_targets"
+if test -z "$all_strategies"; then
+       die "must enable at least one of -lrmt"
+fi
+
+branch="$1"
+test -z "$new_name" && new_name="$branch"
+
+if test ! -z "$scan_reflog"; then
+       if test -r "$GIT_DIR"/logs/HEAD; then
+               candidates="$(search_reflog $branch)"
+       else
+               die 'reflog scanning requested, but' \
+                       '$GIT_DIR/logs/HEAD not readable'
+       fi
+fi
+if test ! -z "$scan_reflog_merges"; then
+       if test -r "$GIT_DIR"/logs/HEAD; then
+               candidates="$candidates $(search_reflog_merges $branch)"
+       else
+               die 'reflog scanning requested, but' \
+                       '$GIT_DIR/logs/HEAD not readable'
+       fi
+fi
+if test ! -z "$scan_merges"; then
+       candidates="$candidates $(search_merges $branch)"
+fi
+if test ! -z "$scan_merge_targets"; then
+       candidates="$candidates $(search_merge_targets $branch)"
+fi
+
+candidates="$(git rev-parse $candidates | sort -u)"
+
+if test -z "$candidates"; then
+       hint=
+       test "z$all_strategies" != "ztttt" \
+               && hint=" (maybe try again with -a)"
+       die "no candidates for $branch found$hint"
+fi
+
+echo "** Candidates for $branch **"
+for cmt in $candidates; do
+       git --no-pager log --pretty=tformat:"%ct:%h [%cr] %s" --abbrev-commit -1 $cmt
+done \
+| sort -n | cut -d: -f2-
+
+newest="$(git rev-list -1 $candidates)"
+if test ! -z "$dry_run"; then
+       printf "** Most recent: "
+       git --no-pager log -1 --pretty=tformat:"%h %s" $newest
+elif ! git rev-parse --verify --quiet $new_name >/dev/null; then
+       printf "** Restoring $new_name to "
+       git --no-pager log -1 --pretty=tformat:"%h %s" $newest
+       git branch $new_name $newest
+else
+       printf "Most recent: "
+       git --no-pager log -1 --pretty=tformat:"%h %s" $newest
+       echo "** $new_name already exists, doing nothing"
+fi
index 28a3c0e46ecf9951f3f42a025a288a65c70e0424..60cbab65d3f8230be3041a13fac2fd9f9b3018d5 100644 (file)
@@ -615,7 +615,9 @@ show_new_revisions()
                revspec=$oldrev..$newrev
        fi
 
-       git rev-parse --not --branches | grep -v $(git rev-parse $refname) |
+       other_branches=$(git for-each-ref --format='%(refname)' refs/heads/ |
+           grep -F -v $refname)
+       git rev-parse --not $other_branches |
        if [ -z "$custom_showrev" ]
        then
                git rev-list --pretty --stdin $revspec
index c487346eba0f4ecad01903ccc68963040d9944ab..fca1e17251f1f414a1d97bf6e6240bdf20daa648 100644 (file)
@@ -5,11 +5,13 @@ automatically.
 If you have an older version of vim, you can get the latest syntax
 files from the vim project:
 
-  http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/git.vim
-  http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/gitcommit.vim
-  http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/gitconfig.vim
-  http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/gitrebase.vim
-  http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/gitsendemail.vim
+  http://ftp.vim.org/pub/vim/runtime/syntax/git.vim
+  http://ftp.vim.org/pub/vim/runtime/syntax/gitcommit.vim
+  http://ftp.vim.org/pub/vim/runtime/syntax/gitconfig.vim
+  http://ftp.vim.org/pub/vim/runtime/syntax/gitrebase.vim
+  http://ftp.vim.org/pub/vim/runtime/syntax/gitsendemail.vim
+
+These files are also available via FTP at the same location.
 
 To install:
 
diff --git a/ctype.c b/ctype.c
index 9208d674dbc081878532f08d2eddfc66c6a2e327..b90ec004f29c30c4b6a6ea5339599d7a8db0fb8c 100644 (file)
--- a/ctype.c
+++ b/ctype.c
@@ -5,25 +5,22 @@
  */
 #include "cache.h"
 
-/* Just so that no insane platform contaminate namespace with these symbols */
-#undef SS
-#undef AA
-#undef DD
-#undef GS
-
-#define SS GIT_SPACE
-#define AA GIT_ALPHA
-#define DD GIT_DIGIT
-#define GS GIT_SPECIAL  /* \0, *, ?, [, \\ */
+enum {
+       S = GIT_SPACE,
+       A = GIT_ALPHA,
+       D = GIT_DIGIT,
+       G = GIT_GLOB_SPECIAL,   /* *, ?, [, \\ */
+       R = GIT_REGEX_SPECIAL,  /* $, (, ), +, ., ^, {, | * */
+};
 
 unsigned char sane_ctype[256] = {
-       GS,  0,  0,  0,  0,  0,  0,  0,  0, SS, SS,  0,  0, SS,  0,  0,         /* 0-15 */
-        0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,         /* 16-15 */
-       SS,  0,  0,  0,  0,  0,  0,  0,  0,  0, GS,  0,  0,  0,  0,  0,         /* 32-15 */
-       DD, DD, DD, DD, DD, DD, DD, DD, DD, DD,  0,  0,  0,  0,  0, GS,         /* 48-15 */
-        0, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA,         /* 64-15 */
-       AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, GS, GS,  0,  0,  0,         /* 80-15 */
-        0, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA,         /* 96-15 */
-       AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA,  0,  0,  0,  0,  0,         /* 112-15 */
+       0, 0, 0, 0, 0, 0, 0, 0, 0, S, S, 0, 0, S, 0, 0,         /*   0.. 15 */
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,         /*  16.. 31 */
+       S, 0, 0, 0, R, 0, 0, 0, R, R, G, R, 0, 0, R, 0,         /*  32.. 47 */
+       D, D, D, D, D, D, D, D, D, D, 0, 0, 0, 0, 0, G,         /*  48.. 63 */
+       0, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A,         /*  64.. 79 */
+       A, A, A, A, A, A, A, A, A, A, A, G, G, 0, R, 0,         /*  80.. 95 */
+       0, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A,         /*  96..111 */
+       A, A, A, A, A, A, A, A, A, A, A, R, R, 0, 0, 0,         /* 112..127 */
        /* Nothing in the 128.. range */
 };
index 60bf6c743c559676f0c9e0ff8dc6d9a5dfede195..13401f1baf57dc87a7a95bd778f4e1becbdbe323 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -229,7 +229,7 @@ static char *path_ok(char *directory)
        }
 
        if (!path) {
-               logerror("'%s': unable to chdir or not a git archive", dir);
+               logerror("'%s' does not appear to be a git repository", dir);
                return NULL;
        }
 
@@ -716,7 +716,7 @@ static int socksetup(char *listen_addr, int listen_port, int **socklist_p)
 
        gai = getaddrinfo(listen_addr, pbuf, &hints, &ai0);
        if (gai)
-               die("getaddrinfo() failed: %s\n", gai_strerror(gai));
+               die("getaddrinfo() failed: %s", gai_strerror(gai));
 
        for (ai = ai0; ai; ai = ai->ai_next) {
                int sockfd;
@@ -937,6 +937,8 @@ int main(int argc, char **argv)
        gid_t gid = 0;
        int i;
 
+       git_extract_argv0_path(argv[0]);
+
        for (i = 1; i < argc; i++) {
                char *arg = argv[i];
 
diff --git a/date.c b/date.c
index 950b88fdcf74f550a582684f1702ffb58c62c7f9..1165d30adfa783f7cbaa871e38a1370f9f9c4996 100644 (file)
--- a/date.c
+++ b/date.c
@@ -89,6 +89,11 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)
        struct tm *tm;
        static char timebuf[200];
 
+       if (mode == DATE_RAW) {
+               snprintf(timebuf, sizeof(timebuf), "%lu %+05d", time, tz);
+               return timebuf;
+       }
+
        if (mode == DATE_RELATIVE) {
                unsigned long diff;
                struct timeval now;
@@ -128,7 +133,25 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)
                        snprintf(timebuf, sizeof(timebuf), "%lu months ago", (diff + 15) / 30);
                        return timebuf;
                }
-               /* Else fall back on absolute format.. */
+               /* Give years and months for 5 years or so */
+               if (diff < 1825) {
+                       unsigned long years = (diff + 183) / 365;
+                       unsigned long months = (diff % 365 + 15) / 30;
+                       int n;
+                       n = snprintf(timebuf, sizeof(timebuf), "%lu year%s",
+                                       years, (years > 1 ? "s" : ""));
+                       if (months)
+                               snprintf(timebuf + n, sizeof(timebuf) - n,
+                                       ", %lu month%s ago",
+                                       months, (months > 1 ? "s" : ""));
+                       else
+                               snprintf(timebuf + n, sizeof(timebuf) - n,
+                                       " ago");
+                       return timebuf;
+               }
+               /* Otherwise, just years. Centuries is probably overkill. */
+               snprintf(timebuf, sizeof(timebuf), "%lu years ago", (diff + 183) / 365);
+               return timebuf;
        }
 
        if (mode == DATE_LOCAL)
@@ -615,6 +638,8 @@ enum date_mode parse_date_format(const char *format)
                return DATE_LOCAL;
        else if (!strcmp(format, "default"))
                return DATE_NORMAL;
+       else if (!strcmp(format, "raw"))
+               return DATE_RAW;
        else
                die("unknown date format %s", format);
 }
index ae96c64ca209f4df9008198e8a04b160bed618c7..79d06068344f5a602f6c8799f6671ccbf98cf49c 100644 (file)
@@ -61,14 +61,12 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
        int silent_on_removed = option & DIFF_SILENT_ON_REMOVED;
        unsigned ce_option = ((option & DIFF_RACY_IS_MODIFIED)
                              ? CE_MATCH_RACY_IS_DIRTY : 0);
-       char symcache[PATH_MAX];
 
        diff_set_mnemonic_prefix(&revs->diffopt, "i/", "w/");
 
        if (diff_unmerged_stage < 0)
                diff_unmerged_stage = 2;
        entries = active_nr;
-       symcache[0] = '\0';
        for (i = 0; i < entries; i++) {
                struct stat st;
                unsigned int oldmode, newmode;
@@ -198,11 +196,6 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
  * diff-index
  */
 
-struct oneway_unpack_data {
-       struct rev_info *revs;
-       char symcache[PATH_MAX];
-};
-
 /* A file entry went away or appeared */
 static void diff_index_show_file(struct rev_info *revs,
                                 const char *prefix,
@@ -216,8 +209,7 @@ static void diff_index_show_file(struct rev_info *revs,
 static int get_stat_data(struct cache_entry *ce,
                         const unsigned char **sha1p,
                         unsigned int *modep,
-                        int cached, int match_missing,
-                        struct oneway_unpack_data *cbdata)
+                        int cached, int match_missing)
 {
        const unsigned char *sha1 = ce->sha1;
        unsigned int mode = ce->ce_mode;
@@ -248,25 +240,24 @@ static int get_stat_data(struct cache_entry *ce,
        return 0;
 }
 
-static void show_new_file(struct oneway_unpack_data *cbdata,
+static void show_new_file(struct rev_info *revs,
                          struct cache_entry *new,
                          int cached, int match_missing)
 {
        const unsigned char *sha1;
        unsigned int mode;
-       struct rev_info *revs = cbdata->revs;
 
        /*
         * New file in the index: it might actually be different in
         * the working copy.
         */
-       if (get_stat_data(new, &sha1, &mode, cached, match_missing, cbdata) < 0)
+       if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0)
                return;
 
        diff_index_show_file(revs, "+", new, sha1, mode);
 }
 
-static int show_modified(struct oneway_unpack_data *cbdata,
+static int show_modified(struct rev_info *revs,
                         struct cache_entry *old,
                         struct cache_entry *new,
                         int report_missing,
@@ -274,9 +265,8 @@ static int show_modified(struct oneway_unpack_data *cbdata,
 {
        unsigned int mode, oldmode;
        const unsigned char *sha1;
-       struct rev_info *revs = cbdata->revs;
 
-       if (get_stat_data(new, &sha1, &mode, cached, match_missing, cbdata) < 0) {
+       if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) {
                if (report_missing)
                        diff_index_show_file(revs, "-", old,
                                             old->sha1, old->ce_mode);
@@ -344,8 +334,7 @@ static void do_oneway_diff(struct unpack_trees_options *o,
        struct cache_entry *idx,
        struct cache_entry *tree)
 {
-       struct oneway_unpack_data *cbdata = o->unpack_data;
-       struct rev_info *revs = cbdata->revs;
+       struct rev_info *revs = o->unpack_data;
        int match_missing, cached;
 
        /*
@@ -368,7 +357,7 @@ static void do_oneway_diff(struct unpack_trees_options *o,
         * Something added to the tree?
         */
        if (!tree) {
-               show_new_file(cbdata, idx, cached, match_missing);
+               show_new_file(revs, idx, cached, match_missing);
                return;
        }
 
@@ -381,7 +370,7 @@ static void do_oneway_diff(struct unpack_trees_options *o,
        }
 
        /* Show difference between old and new */
-       show_modified(cbdata, tree, idx, 1, cached, match_missing);
+       show_modified(revs, tree, idx, 1, cached, match_missing);
 }
 
 static inline void skip_same_name(struct cache_entry *ce, struct unpack_trees_options *o)
@@ -418,8 +407,7 @@ static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o)
 {
        struct cache_entry *idx = src[0];
        struct cache_entry *tree = src[1];
-       struct oneway_unpack_data *cbdata = o->unpack_data;
-       struct rev_info *revs = cbdata->revs;
+       struct rev_info *revs = o->unpack_data;
 
        if (idx && ce_stage(idx))
                skip_same_name(idx, o);
@@ -446,7 +434,6 @@ int run_diff_index(struct rev_info *revs, int cached)
        const char *tree_name;
        struct unpack_trees_options opts;
        struct tree_desc t;
-       struct oneway_unpack_data unpack_cb;
 
        mark_merge_entries();
 
@@ -456,14 +443,12 @@ int run_diff_index(struct rev_info *revs, int cached)
        if (!tree)
                return error("bad tree object %s", tree_name);
 
-       unpack_cb.revs = revs;
-       unpack_cb.symcache[0] = '\0';
        memset(&opts, 0, sizeof(opts));
        opts.head_idx = 1;
        opts.index_only = cached;
        opts.merge = 1;
        opts.fn = oneway_diff;
-       opts.unpack_data = &unpack_cb;
+       opts.unpack_data = revs;
        opts.src_index = &the_index;
        opts.dst_index = NULL;
 
@@ -486,7 +471,6 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
        struct cache_entry *last = NULL;
        struct unpack_trees_options opts;
        struct tree_desc t;
-       struct oneway_unpack_data unpack_cb;
 
        /*
         * This is used by git-blame to run diff-cache internally;
@@ -515,14 +499,12 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
        if (!tree)
                die("bad tree object %s", sha1_to_hex(tree_sha1));
 
-       unpack_cb.revs = &revs;
-       unpack_cb.symcache[0] = '\0';
        memset(&opts, 0, sizeof(opts));
        opts.head_idx = 1;
        opts.index_only = 1;
        opts.merge = 1;
        opts.fn = oneway_diff;
-       opts.unpack_data = &unpack_cb;
+       opts.unpack_data = &revs;
        opts.src_index = &the_index;
        opts.dst_index = &the_index;
 
@@ -531,3 +513,18 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
                exit(128);
        return 0;
 }
+
+int index_differs_from(const char *def, int diff_flags)
+{
+       struct rev_info rev;
+
+       init_revisions(&rev, NULL);
+       setup_revisions(0, NULL, &rev, def);
+       DIFF_OPT_SET(&rev.diffopt, QUIET);
+       DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS);
+       rev.diffopt.flags |= diff_flags;
+       run_diff_index(&rev, 1);
+       if (rev.pending.alloc)
+               free(rev.pending.objects);
+       return (DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES) != 0);
+}
index 2d541d9aba11ee69525a6a1ef5da6f8a61e9e65e..598687b50ac7b88ca0860faf1670d03047bb3603 100644 (file)
@@ -38,9 +38,13 @@ static int get_mode(const char *path, int *mode)
 
        if (!path || !strcmp(path, "/dev/null"))
                *mode = 0;
+#ifdef _WIN32
+       else if (!strcasecmp(path, "nul"))
+               *mode = 0;
+#endif
        else if (!strcmp(path, "-"))
                *mode = create_ce_mode(0666);
-       else if (stat(path, &st))
+       else if (lstat(path, &st))
                return error("Could not access '%s'", path);
        else
                *mode = st.st_mode;
diff --git a/diff.c b/diff.c
index 416c5aa722afc8d10e4bc910e7b297b3eb0a2760..75d9fab8f87033ba86f687fbc92c5d4ee4c68d7b 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -12,6 +12,7 @@
 #include "run-command.h"
 #include "utf8.h"
 #include "userdiff.h"
+#include "sigchain.h"
 
 #ifdef NO_FAST_WORKING_DIRECTORY
 #define FAST_WORKING_DIRECTORY 0
@@ -23,19 +24,20 @@ static int diff_detect_rename_default;
 static int diff_rename_limit_default = 200;
 static int diff_suppress_blank_empty;
 int diff_use_color_default = -1;
+static const char *diff_word_regex_cfg;
 static const char *external_diff_cmd_cfg;
 int diff_auto_refresh_index = 1;
 static int diff_mnemonic_prefix;
 
 static char diff_colors[][COLOR_MAXLEN] = {
-       "\033[m",       /* reset */
-       "",             /* PLAIN (normal) */
-       "\033[1m",      /* METAINFO (bold) */
-       "\033[36m",     /* FRAGINFO (cyan) */
-       "\033[31m",     /* OLD (red) */
-       "\033[32m",     /* NEW (green) */
-       "\033[33m",     /* COMMIT (yellow) */
-       "\033[41m",     /* WHITESPACE (red background) */
+       GIT_COLOR_RESET,
+       GIT_COLOR_NORMAL,       /* PLAIN */
+       GIT_COLOR_BOLD,         /* METAINFO */
+       GIT_COLOR_CYAN,         /* FRAGINFO */
+       GIT_COLOR_RED,          /* OLD */
+       GIT_COLOR_GREEN,        /* NEW */
+       GIT_COLOR_YELLOW,       /* COMMIT */
+       GIT_COLOR_BG_RED,       /* WHITESPACE */
 };
 
 static void diff_filespec_load_driver(struct diff_filespec *one);
@@ -92,6 +94,8 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
        }
        if (!strcmp(var, "diff.external"))
                return git_config_string(&external_diff_cmd_cfg, var, value);
+       if (!strcmp(var, "diff.wordregex"))
+               return git_config_string(&diff_word_regex_cfg, var, value);
 
        return git_diff_basic_config(var, value, cb);
 }
@@ -167,6 +171,33 @@ static struct diff_tempfile {
        char tmp_path[PATH_MAX];
 } diff_temp[2];
 
+static struct diff_tempfile *claim_diff_tempfile(void) {
+       int i;
+       for (i = 0; i < ARRAY_SIZE(diff_temp); i++)
+               if (!diff_temp[i].name)
+                       return diff_temp + i;
+       die("BUG: diff is failing to clean up its tempfiles");
+}
+
+static int remove_tempfile_installed;
+
+static void remove_tempfile(void)
+{
+       int i;
+       for (i = 0; i < ARRAY_SIZE(diff_temp); i++) {
+               if (diff_temp[i].name == diff_temp[i].tmp_path)
+                       unlink(diff_temp[i].name);
+               diff_temp[i].name = NULL;
+       }
+}
+
+static void remove_tempfile_on_signal(int signo)
+{
+       remove_tempfile();
+       sigchain_pop(signo);
+       raise(signo);
+}
+
 static int count_lines(const char *data, int size)
 {
        int count, ch, completely_empty = 1, nl_just_seen = 0;
@@ -321,82 +352,138 @@ static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
 struct diff_words_buffer {
        mmfile_t text;
        long alloc;
-       long current; /* output pointer */
-       int suppressed_newline;
+       struct diff_words_orig {
+               const char *begin, *end;
+       } *orig;
+       int orig_nr, orig_alloc;
 };
 
 static void diff_words_append(char *line, unsigned long len,
                struct diff_words_buffer *buffer)
 {
-       if (buffer->text.size + len > buffer->alloc) {
-               buffer->alloc = (buffer->text.size + len) * 3 / 2;
-               buffer->text.ptr = xrealloc(buffer->text.ptr, buffer->alloc);
-       }
+       ALLOC_GROW(buffer->text.ptr, buffer->text.size + len, buffer->alloc);
        line++;
        len--;
        memcpy(buffer->text.ptr + buffer->text.size, line, len);
        buffer->text.size += len;
+       buffer->text.ptr[buffer->text.size] = '\0';
 }
 
 struct diff_words_data {
        struct diff_words_buffer minus, plus;
+       const char *current_plus;
        FILE *file;
+       regex_t *word_regex;
 };
 
-static void print_word(FILE *file, struct diff_words_buffer *buffer, int len, int color,
-               int suppress_newline)
+static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len)
 {
-       const char *ptr;
-       int eol = 0;
+       struct diff_words_data *diff_words = priv;
+       int minus_first, minus_len, plus_first, plus_len;
+       const char *minus_begin, *minus_end, *plus_begin, *plus_end;
 
-       if (len == 0)
+       if (line[0] != '@' || parse_hunk_header(line, len,
+                       &minus_first, &minus_len, &plus_first, &plus_len))
                return;
 
-       ptr  = buffer->text.ptr + buffer->current;
-       buffer->current += len;
+       /* POSIX requires that first be decremented by one if len == 0... */
+       if (minus_len) {
+               minus_begin = diff_words->minus.orig[minus_first].begin;
+               minus_end =
+                       diff_words->minus.orig[minus_first + minus_len - 1].end;
+       } else
+               minus_begin = minus_end =
+                       diff_words->minus.orig[minus_first].end;
 
-       if (ptr[len - 1] == '\n') {
-               eol = 1;
-               len--;
+       if (plus_len) {
+               plus_begin = diff_words->plus.orig[plus_first].begin;
+               plus_end = diff_words->plus.orig[plus_first + plus_len - 1].end;
+       } else
+               plus_begin = plus_end = diff_words->plus.orig[plus_first].end;
+
+       if (diff_words->current_plus != plus_begin)
+               fwrite(diff_words->current_plus,
+                               plus_begin - diff_words->current_plus, 1,
+                               diff_words->file);
+       if (minus_begin != minus_end)
+               color_fwrite_lines(diff_words->file,
+                               diff_get_color(1, DIFF_FILE_OLD),
+                               minus_end - minus_begin, minus_begin);
+       if (plus_begin != plus_end)
+               color_fwrite_lines(diff_words->file,
+                               diff_get_color(1, DIFF_FILE_NEW),
+                               plus_end - plus_begin, plus_begin);
+
+       diff_words->current_plus = plus_end;
+}
+
+/* This function starts looking at *begin, and returns 0 iff a word was found. */
+static int find_word_boundaries(mmfile_t *buffer, regex_t *word_regex,
+               int *begin, int *end)
+{
+       if (word_regex && *begin < buffer->size) {
+               regmatch_t match[1];
+               if (!regexec(word_regex, buffer->ptr + *begin, 1, match, 0)) {
+                       char *p = memchr(buffer->ptr + *begin + match[0].rm_so,
+                                       '\n', match[0].rm_eo - match[0].rm_so);
+                       *end = p ? p - buffer->ptr : match[0].rm_eo + *begin;
+                       *begin += match[0].rm_so;
+                       return *begin >= *end;
+               }
+               return -1;
        }
 
-       fputs(diff_get_color(1, color), file);
-       fwrite(ptr, len, 1, file);
-       fputs(diff_get_color(1, DIFF_RESET), file);
+       /* find the next word */
+       while (*begin < buffer->size && isspace(buffer->ptr[*begin]))
+               (*begin)++;
+       if (*begin >= buffer->size)
+               return -1;
+
+       /* find the end of the word */
+       *end = *begin + 1;
+       while (*end < buffer->size && !isspace(buffer->ptr[*end]))
+               (*end)++;
 
-       if (eol) {
-               if (suppress_newline)
-                       buffer->suppressed_newline = 1;
-               else
-                       putc('\n', file);
-       }
+       return 0;
 }
 
-static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len)
+/*
+ * This function splits the words in buffer->text, stores the list with
+ * newline separator into out, and saves the offsets of the original words
+ * in buffer->orig.
+ */
+static void diff_words_fill(struct diff_words_buffer *buffer, mmfile_t *out,
+               regex_t *word_regex)
 {
-       struct diff_words_data *diff_words = priv;
+       int i, j;
+       long alloc = 0;
 
-       if (diff_words->minus.suppressed_newline) {
-               if (line[0] != '+')
-                       putc('\n', diff_words->file);
-               diff_words->minus.suppressed_newline = 0;
-       }
+       out->size = 0;
+       out->ptr = NULL;
 
-       len--;
-       switch (line[0]) {
-               case '-':
-                       print_word(diff_words->file,
-                                  &diff_words->minus, len, DIFF_FILE_OLD, 1);
-                       break;
-               case '+':
-                       print_word(diff_words->file,
-                                  &diff_words->plus, len, DIFF_FILE_NEW, 0);
-                       break;
-               case ' ':
-                       print_word(diff_words->file,
-                                  &diff_words->plus, len, DIFF_PLAIN, 0);
-                       diff_words->minus.current += len;
-                       break;
+       /* fake an empty "0th" word */
+       ALLOC_GROW(buffer->orig, 1, buffer->orig_alloc);
+       buffer->orig[0].begin = buffer->orig[0].end = buffer->text.ptr;
+       buffer->orig_nr = 1;
+
+       for (i = 0; i < buffer->text.size; i++) {
+               if (find_word_boundaries(&buffer->text, word_regex, &i, &j))
+                       return;
+
+               /* store original boundaries */
+               ALLOC_GROW(buffer->orig, buffer->orig_nr + 1,
+                               buffer->orig_alloc);
+               buffer->orig[buffer->orig_nr].begin = buffer->text.ptr + i;
+               buffer->orig[buffer->orig_nr].end = buffer->text.ptr + j;
+               buffer->orig_nr++;
+
+               /* store one word */
+               ALLOC_GROW(out->ptr, out->size + j - i + 1, alloc);
+               memcpy(out->ptr + out->size, buffer->text.ptr + i, j - i);
+               out->ptr[out->size + j - i] = '\n';
+               out->size += j - i + 1;
+
+               i = j - 1;
        }
 }
 
@@ -407,38 +494,36 @@ static void diff_words_show(struct diff_words_data *diff_words)
        xdemitconf_t xecfg;
        xdemitcb_t ecb;
        mmfile_t minus, plus;
-       int i;
+
+       /* special case: only removal */
+       if (!diff_words->plus.text.size) {
+               color_fwrite_lines(diff_words->file,
+                       diff_get_color(1, DIFF_FILE_OLD),
+                       diff_words->minus.text.size, diff_words->minus.text.ptr);
+               diff_words->minus.text.size = 0;
+               return;
+       }
+
+       diff_words->current_plus = diff_words->plus.text.ptr;
 
        memset(&xpp, 0, sizeof(xpp));
        memset(&xecfg, 0, sizeof(xecfg));
-       minus.size = diff_words->minus.text.size;
-       minus.ptr = xmalloc(minus.size);
-       memcpy(minus.ptr, diff_words->minus.text.ptr, minus.size);
-       for (i = 0; i < minus.size; i++)
-               if (isspace(minus.ptr[i]))
-                       minus.ptr[i] = '\n';
-       diff_words->minus.current = 0;
-
-       plus.size = diff_words->plus.text.size;
-       plus.ptr = xmalloc(plus.size);
-       memcpy(plus.ptr, diff_words->plus.text.ptr, plus.size);
-       for (i = 0; i < plus.size; i++)
-               if (isspace(plus.ptr[i]))
-                       plus.ptr[i] = '\n';
-       diff_words->plus.current = 0;
-
+       diff_words_fill(&diff_words->minus, &minus, diff_words->word_regex);
+       diff_words_fill(&diff_words->plus, &plus, diff_words->word_regex);
        xpp.flags = XDF_NEED_MINIMAL;
-       xecfg.ctxlen = diff_words->minus.alloc + diff_words->plus.alloc;
+       /* as only the hunk header will be parsed, we need a 0-context */
+       xecfg.ctxlen = 0;
        xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words,
                      &xpp, &xecfg, &ecb);
        free(minus.ptr);
        free(plus.ptr);
+       if (diff_words->current_plus != diff_words->plus.text.ptr +
+                       diff_words->plus.text.size)
+               fwrite(diff_words->current_plus,
+                       diff_words->plus.text.ptr + diff_words->plus.text.size
+                       - diff_words->current_plus, 1,
+                       diff_words->file);
        diff_words->minus.text.size = diff_words->plus.text.size = 0;
-
-       if (diff_words->minus.suppressed_newline) {
-               putc('\n', diff_words->file);
-               diff_words->minus.suppressed_newline = 0;
-       }
 }
 
 typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
@@ -462,7 +547,10 @@ static void free_diff_words_data(struct emit_callback *ecbdata)
                        diff_words_show(ecbdata->diff_words);
 
                free (ecbdata->diff_words->minus.text.ptr);
+               free (ecbdata->diff_words->minus.orig);
                free (ecbdata->diff_words->plus.text.ptr);
+               free (ecbdata->diff_words->plus.orig);
+               free(ecbdata->diff_words->word_regex);
                free(ecbdata->diff_words);
                ecbdata->diff_words = NULL;
        }
@@ -787,7 +875,7 @@ static void fill_print_name(struct diffstat_file *file)
 
 static void show_stats(struct diffstat_t* data, struct diff_options *options)
 {
-       int i, len, add, del, total, adds = 0, dels = 0;
+       int i, len, add, del, adds = 0, dels = 0;
        int max_change = 0, max_len = 0;
        int total_files = data->nr;
        int width, name_width;
@@ -890,14 +978,12 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options)
                 */
                add = added;
                del = deleted;
-               total = add + del;
                adds += add;
                dels += del;
 
                if (width <= max_change) {
                        add = scale_linear(add, width, max_change);
                        del = scale_linear(del, width, max_change);
-                       total = add + del;
                }
                show_name(options->file, prefix, name, len, reset, set);
                fprintf(options->file, "%5d%s", added + deleted,
@@ -1325,6 +1411,12 @@ static const struct userdiff_funcname *diff_funcname_pattern(struct diff_filespe
        return one->driver->funcname.pattern ? &one->driver->funcname : NULL;
 }
 
+static const char *userdiff_word_regex(struct diff_filespec *one)
+{
+       diff_filespec_load_driver(one);
+       return one->driver->word_regex;
+}
+
 void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b)
 {
        if (!options->a_prefix)
@@ -1471,6 +1563,7 @@ static void builtin_diff(const char *name_a,
                ecbdata.file = o->file;
                xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
                xecfg.ctxlen = o->context;
+               xecfg.interhunkctxlen = o->interhunkcontext;
                xecfg.flags = XDL_EMIT_FUNCNAMES;
                if (pe)
                        xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags);
@@ -1484,6 +1577,21 @@ static void builtin_diff(const char *name_a,
                        ecbdata.diff_words =
                                xcalloc(1, sizeof(struct diff_words_data));
                        ecbdata.diff_words->file = o->file;
+                       if (!o->word_regex)
+                               o->word_regex = userdiff_word_regex(one);
+                       if (!o->word_regex)
+                               o->word_regex = userdiff_word_regex(two);
+                       if (!o->word_regex)
+                               o->word_regex = diff_word_regex_cfg;
+                       if (o->word_regex) {
+                               ecbdata.diff_words->word_regex = (regex_t *)
+                                       xmalloc(sizeof(regex_t));
+                               if (regcomp(ecbdata.diff_words->word_regex,
+                                               o->word_regex,
+                                               REG_EXTENDED | REG_NEWLINE))
+                                       die ("Invalid regular expression: %s",
+                                                       o->word_regex);
+                       }
                }
                xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata,
                              &xpp, &xecfg, &ecb);
@@ -1673,7 +1781,7 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int
         * objects however would tend to be slower as they need
         * to be individually opened and inflated.
         */
-       if (!FAST_WORKING_DIRECTORY && !want_file && has_sha1_pack(sha1, NULL))
+       if (!FAST_WORKING_DIRECTORY && !want_file && has_sha1_pack(sha1))
                return 0;
 
        len = strlen(name);
@@ -1858,10 +1966,11 @@ static void prep_temp_blob(struct diff_tempfile *temp,
        sprintf(temp->mode, "%06o", mode);
 }
 
-static void prepare_temp_file(const char *name,
-                             struct diff_tempfile *temp,
-                             struct diff_filespec *one)
+static struct diff_tempfile *prepare_temp_file(const char *name,
+               struct diff_filespec *one)
 {
+       struct diff_tempfile *temp = claim_diff_tempfile();
+
        if (!DIFF_FILE_VALID(one)) {
        not_a_valid_file:
                /* A '-' entry produces this for file-2, and
@@ -1870,7 +1979,13 @@ static void prepare_temp_file(const char *name,
                temp->name = "/dev/null";
                strcpy(temp->hex, ".");
                strcpy(temp->mode, ".");
-               return;
+               return temp;
+       }
+
+       if (!remove_tempfile_installed) {
+               atexit(remove_tempfile);
+               sigchain_push_common(remove_tempfile_on_signal);
+               remove_tempfile_installed = 1;
        }
 
        if (!one->sha1_valid ||
@@ -1910,7 +2025,7 @@ static void prepare_temp_file(const char *name,
                         */
                        sprintf(temp->mode, "%06o", one->mode);
                }
-               return;
+               return temp;
        }
        else {
                if (diff_populate_filespec(one, 0))
@@ -1918,24 +2033,7 @@ static void prepare_temp_file(const char *name,
                prep_temp_blob(temp, one->data, one->size,
                               one->sha1, one->mode);
        }
-}
-
-static void remove_tempfile(void)
-{
-       int i;
-
-       for (i = 0; i < 2; i++)
-               if (diff_temp[i].name == diff_temp[i].tmp_path) {
-                       unlink(diff_temp[i].name);
-                       diff_temp[i].name = NULL;
-               }
-}
-
-static void remove_tempfile_on_signal(int signo)
-{
-       remove_tempfile();
-       signal(SIGINT, SIG_DFL);
-       raise(signo);
+       return temp;
 }
 
 /* An external diff command takes:
@@ -1953,34 +2051,22 @@ static void run_external_diff(const char *pgm,
                              int complete_rewrite)
 {
        const char *spawn_arg[10];
-       struct diff_tempfile *temp = diff_temp;
        int retval;
-       static int atexit_asked = 0;
-       const char *othername;
        const char **arg = &spawn_arg[0];
 
-       othername = (other? other : name);
-       if (one && two) {
-               prepare_temp_file(name, &temp[0], one);
-               prepare_temp_file(othername, &temp[1], two);
-               if (! atexit_asked &&
-                   (temp[0].name == temp[0].tmp_path ||
-                    temp[1].name == temp[1].tmp_path)) {
-                       atexit_asked = 1;
-                       atexit(remove_tempfile);
-               }
-               signal(SIGINT, remove_tempfile_on_signal);
-       }
-
        if (one && two) {
+               struct diff_tempfile *temp_one, *temp_two;
+               const char *othername = (other ? other : name);
+               temp_one = prepare_temp_file(name, one);
+               temp_two = prepare_temp_file(othername, two);
                *arg++ = pgm;
                *arg++ = name;
-               *arg++ = temp[0].name;
-               *arg++ = temp[0].hex;
-               *arg++ = temp[0].mode;
-               *arg++ = temp[1].name;
-               *arg++ = temp[1].hex;
-               *arg++ = temp[1].mode;
+               *arg++ = temp_one->name;
+               *arg++ = temp_one->hex;
+               *arg++ = temp_one->mode;
+               *arg++ = temp_two->name;
+               *arg++ = temp_two->hex;
+               *arg++ = temp_two->mode;
                if (other) {
                        *arg++ = other;
                        *arg++ = xfrm_msg;
@@ -2111,7 +2197,7 @@ static void diff_fill_sha1_info(struct diff_filespec *one)
                        if (lstat(one->path, &st) < 0)
                                die("stat %s", one->path);
                        if (index_path(one->sha1, one->path, &st, 0))
-                               die("cannot hash %s\n", one->path);
+                               die("cannot hash %s", one->path);
                }
        }
        else
@@ -2238,15 +2324,12 @@ void diff_setup(struct diff_options *options)
        options->break_opt = -1;
        options->rename_limit = -1;
        options->dirstat_percent = 3;
-       DIFF_OPT_CLR(options, DIRSTAT_CUMULATIVE);
        options->context = 3;
 
        options->change = diff_change;
        options->add_remove = diff_addremove;
        if (diff_use_color_default > 0)
                DIFF_OPT_SET(options, COLOR_DIFF);
-       else
-               DIFF_OPT_CLR(options, COLOR_DIFF);
        options->detect_rename = diff_detect_rename_default;
 
        if (!diff_mnemonic_prefix) {
@@ -2482,11 +2565,13 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
 
        /* xdiff options */
        else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space"))
-               options->xdl_opts |= XDF_IGNORE_WHITESPACE;
+               DIFF_XDL_SET(options, IGNORE_WHITESPACE);
        else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change"))
-               options->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE;
+               DIFF_XDL_SET(options, IGNORE_WHITESPACE_CHANGE);
        else if (!strcmp(arg, "--ignore-space-at-eol"))
-               options->xdl_opts |= XDF_IGNORE_WHITESPACE_AT_EOL;
+               DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
+       else if (!strcmp(arg, "--patience"))
+               DIFF_XDL_SET(options, PATIENCE_DIFF);
 
        /* flags options */
        else if (!strcmp(arg, "--binary")) {
@@ -2507,8 +2592,15 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                DIFF_OPT_SET(options, COLOR_DIFF);
        else if (!strcmp(arg, "--no-color"))
                DIFF_OPT_CLR(options, COLOR_DIFF);
-       else if (!strcmp(arg, "--color-words"))
-               options->flags |= DIFF_OPT_COLOR_DIFF | DIFF_OPT_COLOR_DIFF_WORDS;
+       else if (!strcmp(arg, "--color-words")) {
+               DIFF_OPT_SET(options, COLOR_DIFF);
+               DIFF_OPT_SET(options, COLOR_DIFF_WORDS);
+       }
+       else if (!prefixcmp(arg, "--color-words=")) {
+               DIFF_OPT_SET(options, COLOR_DIFF);
+               DIFF_OPT_SET(options, COLOR_DIFF_WORDS);
+               options->word_regex = arg + 14;
+       }
        else if (!strcmp(arg, "--exit-code"))
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
        else if (!strcmp(arg, "--quiet"))
@@ -2554,6 +2646,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                options->b_prefix = arg + 13;
        else if (!strcmp(arg, "--no-prefix"))
                options->a_prefix = options->b_prefix = "";
+       else if (opt_arg(arg, '\0', "inter-hunk-context",
+                        &options->interhunkcontext))
+               ;
        else if (!prefixcmp(arg, "--output=")) {
                options->file = fopen(arg + strlen("--output="), "w");
                options->close_file = 1;
@@ -3460,15 +3555,15 @@ void diff_unmerge(struct diff_options *options,
 static char *run_textconv(const char *pgm, struct diff_filespec *spec,
                size_t *outsize)
 {
-       struct diff_tempfile temp;
+       struct diff_tempfile *temp;
        const char *argv[3];
        const char **arg = argv;
        struct child_process child;
        struct strbuf buf = STRBUF_INIT;
 
-       prepare_temp_file(spec->path, &temp, spec);
+       temp = prepare_temp_file(spec->path, spec);
        *arg++ = pgm;
-       *arg++ = temp.name;
+       *arg++ = temp->name;
        *arg = NULL;
 
        memset(&child, 0, sizeof(child));
@@ -3477,13 +3572,11 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec,
        if (start_command(&child) != 0 ||
            strbuf_read(&buf, child.out, 0) < 0 ||
            finish_command(&child) != 0) {
-               if (temp.name == temp.tmp_path)
-                       unlink(temp.name);
+               remove_tempfile();
                error("error running textconv command '%s'", pgm);
                return NULL;
        }
-       if (temp.name == temp.tmp_path)
-               unlink(temp.name);
+       remove_tempfile();
 
        return strbuf_detach(&buf, outsize);
 }
diff --git a/diff.h b/diff.h
index 42582edee68a4a4717ae5debebf37e6b9610fc8f..6616877ee5d15a8101cbdea0271cc18f8837d2f1 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -69,6 +69,9 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
 #define DIFF_OPT_TST(opts, flag)    ((opts)->flags & DIFF_OPT_##flag)
 #define DIFF_OPT_SET(opts, flag)    ((opts)->flags |= DIFF_OPT_##flag)
 #define DIFF_OPT_CLR(opts, flag)    ((opts)->flags &= ~DIFF_OPT_##flag)
+#define DIFF_XDL_TST(opts, flag)    ((opts)->xdl_opts & XDF_##flag)
+#define DIFF_XDL_SET(opts, flag)    ((opts)->xdl_opts |= XDF_##flag)
+#define DIFF_XDL_CLR(opts, flag)    ((opts)->xdl_opts &= ~XDF_##flag)
 
 struct diff_options {
        const char *filter;
@@ -78,6 +81,7 @@ struct diff_options {
        const char *a_prefix, *b_prefix;
        unsigned flags;
        int context;
+       int interhunkcontext;
        int break_opt;
        int detect_rename;
        int skip_stat_unmatch;
@@ -97,6 +101,7 @@ struct diff_options {
 
        int stat_width;
        int stat_name_width;
+       const char *word_regex;
 
        /* this is set by diffcore for DIFF_FORMAT_PATCH */
        int found_changes;
@@ -263,4 +268,6 @@ extern int diff_result_code(struct diff_options *, int);
 
 extern void diff_no_index(struct rev_info *, int, const char **, int, const char *);
 
+extern int index_differs_from(const char *def, int diff_flags);
+
 #endif /* DIFF_H */
index 31cdcfe8bcdae7df65b0387071846299a14bb7be..d7097bb576cef8900e8e0218ba82e1cc7a87a567 100644 (file)
@@ -45,7 +45,7 @@ static int should_break(struct diff_filespec *src,
         * The value we return is 1 if we want the pair to be broken,
         * or 0 if we do not.
         */
-       unsigned long delta_size, base_size, max_size;
+       unsigned long delta_size, max_size;
        unsigned long src_copied, literal_added, src_removed;
 
        *merge_score_p = 0; /* assume no deletion --- "do not break"
@@ -64,7 +64,6 @@ static int should_break(struct diff_filespec *src,
        if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0))
                return 0; /* error but caught downstream */
 
-       base_size = ((src->size < dst->size) ? src->size : dst->size);
        max_size = ((src->size > dst->size) ? src->size : dst->size);
        if (max_size < MINIMUM_BREAK_SIZE)
                return 0; /* we do not break too small filepair */
index af9fffe6e8e145b066157da8791c749257e7c8e9..574b3e833711fe3794636b86dbf2b9d9deb5e151 100644 (file)
@@ -10,7 +10,7 @@ static unsigned int contains(struct diff_filespec *one,
                             regex_t *regexp)
 {
        unsigned int cnt;
-       unsigned long offset, sz;
+       unsigned long sz;
        const char *data;
        if (diff_populate_filespec(one, 0))
                return 0;
@@ -33,15 +33,13 @@ static unsigned int contains(struct diff_filespec *one,
                }
 
        } else { /* Classic exact string match */
-               /* Yes, I've heard of strstr(), but the thing is *data may
-                * not be NUL terminated.  Sue me.
-                */
-               for (offset = 0; offset + len <= sz; offset++) {
-                       /* we count non-overlapping occurrences of needle */
-                       if (!memcmp(needle, data + offset, len)) {
-                               offset += len - 1;
-                               cnt++;
-                       }
+               while (sz) {
+                       const char *found = memmem(data, sz, needle, len);
+                       if (!found)
+                               break;
+                       sz -= found - data + len;
+                       data = found + len;
+                       cnt++;
                }
        }
        diff_free_filespec_data(one);
diff --git a/dir.c b/dir.c
index 0131983dfbc143ce5dae77e067663bb2e7d5f126..2245749b3fe74f793bd1997d2238883abe61fc07 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -75,7 +75,7 @@ static int match_one(const char *match, const char *name, int namelen)
        for (;;) {
                unsigned char c1 = *match;
                unsigned char c2 = *name;
-               if (isspecial(c1))
+               if (c1 == '\0' || is_glob_special(c1))
                        break;
                if (c1 != c2)
                        return 0;
@@ -108,25 +108,28 @@ static int match_one(const char *match, const char *name, int namelen)
  * and a mark is left in seen[] array for pathspec element that
  * actually matched anything.
  */
-int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen)
+int match_pathspec(const char **pathspec, const char *name, int namelen,
+               int prefix, char *seen)
 {
-       int retval;
-       const char *match;
+       int i, retval = 0;
+
+       if (!pathspec)
+               return 1;
 
        name += prefix;
        namelen -= prefix;
 
-       for (retval = 0; (match = *pathspec++) != NULL; seen++) {
+       for (i = 0; pathspec[i] != NULL; i++) {
                int how;
-               if (retval && *seen == MATCHED_EXACTLY)
+               const char *match = pathspec[i] + prefix;
+               if (seen && seen[i] == MATCHED_EXACTLY)
                        continue;
-               match += prefix;
                how = match_one(match, name, namelen);
                if (how) {
                        if (retval < how)
                                retval = how;
-                       if (*seen < how)
-                               *seen = how;
+                       if (seen && seen[i] < how)
+                               seen[i] = how;
                }
        }
        return retval;
@@ -134,7 +137,7 @@ int match_pathspec(const char **pathspec, const char *name, int namelen, int pre
 
 static int no_wildcard(const char *string)
 {
-       return string[strcspn(string, "*?[{")] == '\0';
+       return string[strcspn(string, "*?[{\\")] == '\0';
 }
 
 void add_exclude(const char *string, const char *base,
@@ -585,10 +588,8 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
                        int len, dtype;
                        int exclude;
 
-                       if ((de->d_name[0] == '.') &&
-                           (de->d_name[1] == 0 ||
-                            !strcmp(de->d_name + 1, ".") ||
-                            !strcmp(de->d_name + 1, "git")))
+                       if (is_dot_or_dotdot(de->d_name) ||
+                            !strcmp(de->d_name, ".git"))
                                continue;
                        len = strlen(de->d_name);
                        /* Ignore overly long pathnames! */
@@ -680,7 +681,7 @@ static int simple_length(const char *match)
        for (;;) {
                unsigned char c = *match++;
                len++;
-               if (isspecial(c))
+               if (c == '\0' || is_glob_special(c))
                        return len;
        }
 }
@@ -779,6 +780,25 @@ int is_inside_dir(const char *dir)
        return get_relative_cwd(buffer, sizeof(buffer), dir) != NULL;
 }
 
+int is_empty_dir(const char *path)
+{
+       DIR *dir = opendir(path);
+       struct dirent *e;
+       int ret = 1;
+
+       if (!dir)
+               return 0;
+
+       while ((e = readdir(dir)) != NULL)
+               if (!is_dot_or_dotdot(e->d_name)) {
+                       ret = 0;
+                       break;
+               }
+
+       closedir(dir);
+       return ret;
+}
+
 int remove_dir_recursively(struct strbuf *path, int only_empty)
 {
        DIR *dir = opendir(path->buf);
@@ -793,10 +813,8 @@ int remove_dir_recursively(struct strbuf *path, int only_empty)
        len = path->len;
        while ((e = readdir(dir)) != NULL) {
                struct stat st;
-               if ((e->d_name[0] == '.') &&
-                   ((e->d_name[1] == 0) ||
-                    ((e->d_name[1] == '.') && e->d_name[2] == 0)))
-                       continue; /* "." and ".." */
+               if (is_dot_or_dotdot(e->d_name))
+                       continue;
 
                strbuf_setlen(path, len);
                strbuf_addstr(path, e->d_name);
diff --git a/dir.h b/dir.h
index 768425af0e7095a54edf161fe20400c4caf85b31..bdc2d47447c2ca406aac41d7a8382bf5928fbda8 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -77,6 +77,15 @@ extern int file_exists(const char *);
 extern char *get_relative_cwd(char *buffer, int size, const char *dir);
 extern int is_inside_dir(const char *dir);
 
+static inline int is_dot_or_dotdot(const char *name)
+{
+       return (name[0] == '.' &&
+               (name[1] == '\0' ||
+                (name[1] == '.' && name[2] == '\0')));
+}
+
+extern int is_empty_dir(const char *dir);
+
 extern void setup_standard_excludes(struct dir_struct *dir);
 extern int remove_dir_recursively(struct strbuf *path, int only_empty);
 
diff --git a/entry.c b/entry.c
index aa2ee46a84033585d8e07a585610c5a697af82c2..05aa58d34823258789ec9e32abc897b8e6777412 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "blob.h"
+#include "dir.h"
 
 static void create_directories(const char *path, const struct checkout *state)
 {
@@ -8,35 +9,25 @@ static void create_directories(const char *path, const struct checkout *state)
        const char *slash = path;
 
        while ((slash = strchr(slash+1, '/')) != NULL) {
-               struct stat st;
-               int stat_status;
-
                len = slash - path;
                memcpy(buf, path, len);
                buf[len] = 0;
 
-               if (len <= state->base_dir_len)
-                       /*
-                        * checkout-index --prefix=<dir>; <dir> is
-                        * allowed to be a symlink to an existing
-                        * directory.
-                        */
-                       stat_status = stat(buf, &st);
-               else
-                       /*
-                        * if there currently is a symlink, we would
-                        * want to replace it with a real directory.
-                        */
-                       stat_status = lstat(buf, &st);
-
-               if (!stat_status && S_ISDIR(st.st_mode))
+               /*
+                * For 'checkout-index --prefix=<dir>', <dir> is
+                * allowed to be a symlink to an existing directory,
+                * and we set 'state->base_dir_len' below, such that
+                * we test the path components of the prefix with the
+                * stat() function instead of the lstat() function.
+                */
+               if (has_dirs_only_path(len, buf, state->base_dir_len))
                        continue; /* ok, it is already a directory. */
 
                /*
-                * We know stat_status == 0 means something exists
-                * there and this mkdir would fail, but that is an
-                * error codepath; we do not care, as we unlink and
-                * mkdir again in such a case.
+                * If this mkdir() would fail, it could be that there
+                * is already a symlink or something else exists
+                * there, therefore we then try to unlink it and try
+                * one more time to create the directory.
                 */
                if (mkdir(buf, 0777)) {
                        if (errno == EEXIST && state->force &&
@@ -62,9 +53,7 @@ static void remove_subtree(const char *path)
        *name++ = '/';
        while ((de = readdir(dir)) != NULL) {
                struct stat st;
-               if ((de->d_name[0] == '.') &&
-                   ((de->d_name[1] == 0) ||
-                    ((de->d_name[1] == '.') && de->d_name[2] == 0)))
+               if (is_dot_or_dotdot(de->d_name))
                        continue;
                strcpy(name, de->d_name);
                if (lstat(pathbuf, &st))
index cdd35f91954bdc751455e1083a0612a21eeadc67..217c12577f52b8ff9d535a086ec75d54107ee01c 100644 (file)
@@ -9,17 +9,53 @@ static const char *argv0_path;
 
 const char *system_path(const char *path)
 {
-       if (!is_absolute_path(path) && argv0_path) {
-               struct strbuf d = STRBUF_INIT;
-               strbuf_addf(&d, "%s/%s", argv0_path, path);
-               path = strbuf_detach(&d, NULL);
+#ifdef RUNTIME_PREFIX
+       static const char *prefix;
+#else
+       static const char *prefix = PREFIX;
+#endif
+       struct strbuf d = STRBUF_INIT;
+
+       if (is_absolute_path(path))
+               return path;
+
+#ifdef RUNTIME_PREFIX
+       assert(argv0_path);
+       assert(is_absolute_path(argv0_path));
+
+       if (!prefix &&
+           !(prefix = strip_path_suffix(argv0_path, GIT_EXEC_PATH)) &&
+           !(prefix = strip_path_suffix(argv0_path, BINDIR)) &&
+           !(prefix = strip_path_suffix(argv0_path, "git"))) {
+               prefix = PREFIX;
+               fprintf(stderr, "RUNTIME_PREFIX requested, "
+                               "but prefix computation failed.  "
+                               "Using static fallback '%s'.\n", prefix);
        }
+#endif
+
+       strbuf_addf(&d, "%s/%s", prefix, path);
+       path = strbuf_detach(&d, NULL);
        return path;
 }
 
-void git_set_argv0_path(const char *path)
+const char *git_extract_argv0_path(const char *argv0)
 {
-       argv0_path = path;
+       const char *slash;
+
+       if (!argv0 || !*argv0)
+               return NULL;
+       slash = argv0 + strlen(argv0);
+
+       while (argv0 <= slash && !is_dir_sep(*slash))
+               slash--;
+
+       if (slash >= argv0) {
+               argv0_path = xstrndup(argv0, slash - argv0);
+               return slash + 1;
+       }
+
+       return argv0;
 }
 
 void git_set_argv_exec_path(const char *exec_path)
@@ -61,9 +97,7 @@ void setup_path(void)
        const char *old_path = getenv("PATH");
        struct strbuf new_path = STRBUF_INIT;
 
-       add_path(&new_path, argv_exec_path);
-       add_path(&new_path, getenv(EXEC_PATH_ENVIRONMENT));
-       add_path(&new_path, system_path(GIT_EXEC_PATH));
+       add_path(&new_path, git_exec_path());
        add_path(&new_path, argv0_path);
 
        if (old_path)
index 594f961387240c221020c9ea0bccd8a39ff69595..e2b546b615e2806bf7d733099ca0ac7bcfaef823 100644 (file)
@@ -2,8 +2,8 @@
 #define GIT_EXEC_CMD_H
 
 extern void git_set_argv_exec_path(const char *exec_path);
-extern void git_set_argv0_path(const char *path);
-extern const chargit_exec_path(void);
+extern const char *git_extract_argv0_path(const char *path);
+extern const char *git_exec_path(void);
 extern void setup_path(void);
 extern const char **prepare_git_cmd(const char **argv);
 extern int execv_git_cmd(const char **argv); /* NULL terminated */
index 23e970d581ad00c97ba8ea870c0e43b16258b219..beeac0d0049d920726befab88a2ffa4eeddcb163 100644 (file)
@@ -150,6 +150,7 @@ Format of STDIN stream:
 #include "refs.h"
 #include "csum-file.h"
 #include "quote.h"
+#include "exec_cmd.h"
 
 #define PACK_ID_BITS 16
 #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
@@ -816,9 +817,8 @@ static void start_packfile(void)
        struct pack_header hdr;
        int pack_fd;
 
-       snprintf(tmpfile, sizeof(tmpfile),
-               "%s/pack/tmp_pack_XXXXXX", get_object_directory());
-       pack_fd = xmkstemp(tmpfile);
+       pack_fd = odb_mkstemp(tmpfile, sizeof(tmpfile),
+                             "pack/tmp_pack_XXXXXX");
        p = xcalloc(1, sizeof(*p) + strlen(tmpfile) + 2);
        strcpy(p->pack_name, tmpfile);
        p->pack_fd = pack_fd;
@@ -868,7 +868,7 @@ static char *create_index(void)
        /* Generate the fan-out array. */
        c = idx;
        for (i = 0; i < 256; i++) {
-               struct object_entry **next = c;;
+               struct object_entry **next = c;
                while (next < last) {
                        if ((*next)->sha1[0] != i)
                                break;
@@ -878,9 +878,8 @@ static char *create_index(void)
                c = next;
        }
 
-       snprintf(tmpfile, sizeof(tmpfile),
-               "%s/pack/tmp_idx_XXXXXX", get_object_directory());
-       idx_fd = xmkstemp(tmpfile);
+       idx_fd = odb_mkstemp(tmpfile, sizeof(tmpfile),
+                            "pack/tmp_idx_XXXXXX");
        f = sha1fd(idx_fd, tmpfile);
        sha1write(f, array, 256 * sizeof(int));
        git_SHA1_Init(&ctx);
@@ -906,9 +905,7 @@ static char *keep_pack(char *curr_index_name)
        chmod(pack_data->pack_name, 0444);
        chmod(curr_index_name, 0444);
 
-       snprintf(name, sizeof(name), "%s/pack/pack-%s.keep",
-                get_object_directory(), sha1_to_hex(pack_data->sha1));
-       keep_fd = open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
+       keep_fd = odb_pack_keep(name, sizeof(name), pack_data->sha1);
        if (keep_fd < 0)
                die("cannot create keep file");
        write_or_die(keep_fd, keep_msg, strlen(keep_msg));
@@ -1748,21 +1745,19 @@ static void parse_data(struct strbuf *sb)
 static int validate_raw_date(const char *src, char *result, int maxlen)
 {
        const char *orig_src = src;
-       char *endp, sign;
-       unsigned long date;
+       char *endp;
 
        errno = 0;
 
-       date = strtoul(src, &endp, 10);
+       strtoul(src, &endp, 10);
        if (errno || endp == src || *endp != ' ')
                return -1;
 
        src = endp + 1;
        if (*src != '-' && *src != '+')
                return -1;
-       sign = *src;
 
-       date = strtoul(src + 1, &endp, 10);
+       strtoul(src + 1, &endp, 10);
        if (errno || endp == src || *endp || (endp - orig_src) >= maxlen)
                return -1;
 
@@ -2407,6 +2402,8 @@ int main(int argc, const char **argv)
 {
        unsigned int i, show_stats = 1;
 
+       git_extract_argv0_path(argv[0]);
+
        setup_git_directory();
        git_config(git_pack_config, NULL);
        if (!pack_compression_seen && core_compression_seen)
diff --git a/fsck.c b/fsck.c
index 97f76c58155249412d7c59e965b564dfc0f75181..511b82cba9977c14d6dcba5efbd6d38591d3aacc 100644 (file)
--- a/fsck.c
+++ b/fsck.c
@@ -148,20 +148,17 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func)
        struct tree_desc desc;
        unsigned o_mode;
        const char *o_name;
-       const unsigned char *o_sha1;
 
        init_tree_desc(&desc, item->buffer, item->size);
 
        o_mode = 0;
        o_name = NULL;
-       o_sha1 = NULL;
 
        while (desc.size) {
                unsigned mode;
                const char *name;
-               const unsigned char *sha1;
 
-               sha1 = tree_entry_extract(&desc, &name, &mode);
+               tree_entry_extract(&desc, &name, &mode);
 
                if (strchr(name, '/'))
                        has_full_path = 1;
@@ -207,7 +204,6 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func)
 
                o_mode = mode;
                o_name = name;
-               o_sha1 = sha1;
        }
 
        retval = 0;
index b0223c3419301132032fb67519a275e57707df22..def062a9e2cbc67b78522aa47151d47cd50f0f33 100755 (executable)
@@ -3,6 +3,8 @@
 use strict;
 use Git;
 
+binmode(STDOUT, ":raw");
+
 my $repo = Git->repository();
 
 my $menu_use_color = $repo->get_colorbool('color.interactive');
                $repo->get_color('color.interactive.header', 'bold'),
                $repo->get_color('color.interactive.help', 'red bold'),
        ) : ();
+my $error_color = ();
+if ($menu_use_color) {
+       my $help_color_spec = ($repo->config('color.interactive.help') or
+                               'red bold');
+       $error_color = $repo->get_color('color.interactive.error',
+                                       $help_color_spec);
+}
 
 my $diff_use_color = $repo->get_colorbool('color.diff');
 my ($fraginfo_color) =
 
 my $normal_color = $repo->get_color("", "reset");
 
+my $use_readkey = 0;
+sub ReadMode;
+sub ReadKey;
+if ($repo->config_bool("interactive.singlekey")) {
+       eval {
+               require Term::ReadKey;
+               Term::ReadKey->import;
+               $use_readkey = 1;
+       };
+}
+
 sub colored {
        my $color = shift;
        my $string = join("", @_);
@@ -73,6 +93,47 @@ sub run_cmd_pipe {
 }
 chomp($GIT_DIR);
 
+my %cquote_map = (
+ "b" => chr(8),
+ "t" => chr(9),
+ "n" => chr(10),
+ "v" => chr(11),
+ "f" => chr(12),
+ "r" => chr(13),
+ "\\" => "\\",
+ "\042" => "\042",
+);
+
+sub unquote_path {
+       local ($_) = @_;
+       my ($retval, $remainder);
+       if (!/^\042(.*)\042$/) {
+               return $_;
+       }
+       ($_, $retval) = ($1, "");
+       while (/^([^\\]*)\\(.*)$/) {
+               $remainder = $2;
+               $retval .= $1;
+               for ($remainder) {
+                       if (/^([0-3][0-7][0-7])(.*)$/) {
+                               $retval .= chr(oct($1));
+                               $_ = $2;
+                               last;
+                       }
+                       if (/^([\\\042btnvfr])(.*)$/) {
+                               $retval .= $cquote_map{$1};
+                               $_ = $2;
+                               last;
+                       }
+                       # This is malformed -- just return it as-is for now.
+                       return $_[0];
+               }
+               $_ = $remainder;
+       }
+       $retval .= $_;
+       return $retval;
+}
+
 sub refresh {
        my $fh;
        open $fh, 'git update-index --refresh |'
@@ -86,7 +147,7 @@ sub refresh {
 sub list_untracked {
        map {
                chomp $_;
-               $_;
+               unquote_path($_);
        }
        run_cmd_pipe(qw(git ls-files --others --exclude-standard --), @ARGV);
 }
@@ -123,7 +184,8 @@ sub list_modified {
 
        if (@ARGV) {
                @tracked = map {
-                       chomp $_; $_;
+                       chomp $_;
+                       unquote_path($_);
                } run_cmd_pipe(qw(git ls-files --exclude-standard --), @ARGV);
                return if (!@tracked);
        }
@@ -135,6 +197,7 @@ sub list_modified {
                if (($add, $del, $file) =
                    /^([-\d]+)  ([-\d]+)        (.*)/) {
                        my ($change, $bin);
+                       $file = unquote_path($file);
                        if ($add eq '-' && $del eq '-') {
                                $change = 'binary';
                                $bin = 1;
@@ -150,6 +213,7 @@ sub list_modified {
                }
                elsif (($adddel, $file) =
                       /^ (create|delete) mode [0-7]+ (.*)$/) {
+                       $file = unquote_path($file);
                        $data{$file}{INDEX_ADDDEL} = $adddel;
                }
        }
@@ -157,6 +221,7 @@ sub list_modified {
        for (run_cmd_pipe(qw(git diff-files --numstat --summary --), @tracked)) {
                if (($add, $del, $file) =
                    /^([-\d]+)  ([-\d]+)        (.*)/) {
+                       $file = unquote_path($file);
                        if (!exists $data{$file}) {
                                $data{$file} = +{
                                        INDEX => 'unchanged',
@@ -178,6 +243,7 @@ sub list_modified {
                }
                elsif (($adddel, $file) =
                       /^ (create|delete) mode [0-7]+ (.*)$/) {
+                       $file = unquote_path($file);
                        $data{$file}{FILE_ADDDEL} = $adddel;
                }
        }
@@ -284,7 +350,8 @@ sub find_unique_prefixes {
                        }
                        %search = %{$search{$letter}};
                }
-               if ($soft_limit && $j + 1 > $soft_limit) {
+               if (ord($letters[0]) > 127 ||
+                   ($soft_limit && $j + 1 > $soft_limit)) {
                        $prefix = undef;
                        $remainder = $ret;
                }
@@ -325,6 +392,10 @@ sub highlight_prefix {
        return "$prompt_color$prefix$normal_color$remainder";
 }
 
+sub error_msg {
+       print STDERR colored $error_color, @_;
+}
+
 sub list_and_choose {
        my ($opts, @stuff) = @_;
        my (@chosen, @return);
@@ -420,12 +491,12 @@ sub list_and_choose {
                        else {
                                $bottom = $top = find_unique($choice, @stuff);
                                if (!defined $bottom) {
-                                       print "Huh ($choice)?\n";
+                                       error_msg "Huh ($choice)?\n";
                                        next TOPLOOP;
                                }
                        }
                        if ($opts->{SINGLETON} && $bottom != $top) {
-                               print "Huh ($choice)?\n";
+                               error_msg "Huh ($choice)?\n";
                                next TOPLOOP;
                        }
                        for ($i = $bottom-1; $i <= $top-1; $i++) {
@@ -731,6 +802,10 @@ sub edit_hunk_manually {
                || $ENV{VISUAL} || $ENV{EDITOR} || "vi";
        system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
 
+       if ($? != 0) {
+               return undef;
+       }
+
        open $fh, '<', $hunkfile
                or die "failed to open hunk edit file for reading: " . $!;
        my @newtext = grep { !/^#/ } <$fh>;
@@ -758,11 +833,32 @@ sub diff_applies {
        return close $fh;
 }
 
+sub _restore_terminal_and_die {
+       ReadMode 'restore';
+       print "\n";
+       exit 1;
+}
+
+sub prompt_single_character {
+       if ($use_readkey) {
+               local $SIG{TERM} = \&_restore_terminal_and_die;
+               local $SIG{INT} = \&_restore_terminal_and_die;
+               ReadMode 'cbreak';
+               my $key = ReadKey 0;
+               ReadMode 'restore';
+               print "$key" if defined $key;
+               print "\n";
+               return $key;
+       } else {
+               return <STDIN>;
+       }
+}
+
 sub prompt_yesno {
        my ($prompt) = @_;
        while (1) {
                print colored $prompt_color, $prompt;
-               my $line = <STDIN>;
+               my $line = prompt_single_character;
                return 0 if $line =~ /^n/i;
                return 1 if $line =~ /^y/i;
        }
@@ -800,6 +896,8 @@ sub help_patch_cmd {
 n - do not stage this hunk
 a - stage this and all the remaining hunks in the file
 d - do not stage this hunk nor any of the remaining hunks in the file
+g - select a hunk to go to
+/ - search for a hunk matching the given regex
 j - leave this hunk undecided, see next undecided hunk
 J - leave this hunk undecided, see next hunk
 k - leave this hunk undecided, see previous undecided hunk
@@ -836,6 +934,47 @@ sub patch_update_cmd {
        }
 }
 
+# Generate a one line summary of a hunk.
+sub summarize_hunk {
+       my $rhunk = shift;
+       my $summary = $rhunk->{TEXT}[0];
+
+       # Keep the line numbers, discard extra context.
+       $summary =~ s/@@(.*?)@@.*/$1 /s;
+       $summary .= " " x (20 - length $summary);
+
+       # Add some user context.
+       for my $line (@{$rhunk->{TEXT}}) {
+               if ($line =~ m/^[+-].*\w/) {
+                       $summary .= $line;
+                       last;
+               }
+       }
+
+       chomp $summary;
+       return substr($summary, 0, 80) . "\n";
+}
+
+
+# Print a one-line summary of each hunk in the array ref in
+# the first argument, starting wih the index in the 2nd.
+sub display_hunks {
+       my ($hunks, $i) = @_;
+       my $ctr = 0;
+       $i ||= 0;
+       for (; $i < @$hunks && $ctr < 20; $i++, $ctr++) {
+               my $status = " ";
+               if (defined $hunks->[$i]{USE}) {
+                       $status = $hunks->[$i]{USE} ? "+" : "-";
+               }
+               printf "%s%2d: %s",
+                       $status,
+                       $i + 1,
+                       summarize_hunk($hunks->[$i]);
+       }
+       return $i;
+}
+
 sub patch_update_file {
        my ($ix, $num);
        my $path = shift;
@@ -850,7 +989,7 @@ sub patch_update_file {
                        print @{$mode->{DISPLAY}};
                        print colored $prompt_color,
                                "Stage mode change [y/n/a/d/?]? ";
-                       my $line = <STDIN>;
+                       my $line = prompt_single_character;
                        if ($line =~ /^y/i) {
                                $mode->{USE} = 1;
                                last;
@@ -887,22 +1026,25 @@ sub patch_update_file {
                for ($i = 0; $i < $ix; $i++) {
                        if (!defined $hunk[$i]{USE}) {
                                $prev = 1;
-                               $other .= '/k';
+                               $other .= ',k';
                                last;
                        }
                }
                if ($ix) {
-                       $other .= '/K';
+                       $other .= ',K';
                }
                for ($i = $ix + 1; $i < $num; $i++) {
                        if (!defined $hunk[$i]{USE}) {
                                $next = 1;
-                               $other .= '/j';
+                               $other .= ',j';
                                last;
                        }
                }
                if ($ix < $num - 1) {
-                       $other .= '/J';
+                       $other .= ',J';
+               }
+               if ($num > 1) {
+                       $other .= ',g';
                }
                for ($i = 0; $i < $num; $i++) {
                        if (!defined $hunk[$i]{USE}) {
@@ -913,14 +1055,14 @@ sub patch_update_file {
                last if (!$undecided);
 
                if (hunk_splittable($hunk[$ix]{TEXT})) {
-                       $other .= '/s';
+                       $other .= ',s';
                }
-               $other .= '/e';
+               $other .= ',e';
                for (@{$hunk[$ix]{DISPLAY}}) {
                        print;
                }
-               print colored $prompt_color, "Stage this hunk [y/n/a/d$other/?]? ";
-               my $line = <STDIN>;
+               print colored $prompt_color, "Stage this hunk [y,n,a,d,/$other,?]? ";
+               my $line = prompt_single_character;
                if ($line) {
                        if ($line =~ /^y/i) {
                                $hunk[$ix]{USE} = 1;
@@ -937,6 +1079,31 @@ sub patch_update_file {
                                }
                                next;
                        }
+                       elsif ($other =~ /g/ && $line =~ /^g(.*)/) {
+                               my $response = $1;
+                               my $no = $ix > 10 ? $ix - 10 : 0;
+                               while ($response eq '') {
+                                       my $extra = "";
+                                       $no = display_hunks(\@hunk, $no);
+                                       if ($no < $num) {
+                                               $extra = " (<ret> to see more)";
+                                       }
+                                       print "go to which hunk$extra? ";
+                                       $response = <STDIN>;
+                                       if (!defined $response) {
+                                               $response = '';
+                                       }
+                                       chomp $response;
+                               }
+                               if ($response !~ /^\s*\d+\s*$/) {
+                                       error_msg "Invalid number: '$response'\n";
+                               } elsif (0 < $response && $response <= $num) {
+                                       $ix = $response - 1;
+                               } else {
+                                       error_msg "Sorry, only $num hunks available.\n";
+                               }
+                               next;
+                       }
                        elsif ($line =~ /^d/i) {
                                while ($ix < $num) {
                                        if (!defined $hunk[$ix]{USE}) {
@@ -946,30 +1113,76 @@ sub patch_update_file {
                                }
                                next;
                        }
-                       elsif ($other =~ /K/ && $line =~ /^K/) {
-                               $ix--;
-                               next;
-                       }
-                       elsif ($other =~ /J/ && $line =~ /^J/) {
-                               $ix++;
+                       elsif ($line =~ m|^/(.*)|) {
+                               my $regex = $1;
+                               if ($1 eq "") {
+                                       print colored $prompt_color, "search for regex? ";
+                                       $regex = <STDIN>;
+                                       if (defined $regex) {
+                                               chomp $regex;
+                                       }
+                               }
+                               my $search_string;
+                               eval {
+                                       $search_string = qr{$regex}m;
+                               };
+                               if ($@) {
+                                       my ($err,$exp) = ($@, $1);
+                                       $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
+                                       error_msg "Malformed search regexp $exp: $err\n";
+                                       next;
+                               }
+                               my $iy = $ix;
+                               while (1) {
+                                       my $text = join ("", @{$hunk[$iy]{TEXT}});
+                                       last if ($text =~ $search_string);
+                                       $iy++;
+                                       $iy = 0 if ($iy >= $num);
+                                       if ($ix == $iy) {
+                                               error_msg "No hunk matches the given pattern\n";
+                                               last;
+                                       }
+                               }
+                               $ix = $iy;
                                next;
                        }
-                       elsif ($other =~ /k/ && $line =~ /^k/) {
-                               while (1) {
+                       elsif ($line =~ /^K/) {
+                               if ($other =~ /K/) {
                                        $ix--;
-                                       last if (!$ix ||
-                                                !defined $hunk[$ix]{USE});
+                               }
+                               else {
+                                       error_msg "No previous hunk\n";
                                }
                                next;
                        }
-                       elsif ($other =~ /j/ && $line =~ /^j/) {
-                               while (1) {
+                       elsif ($line =~ /^J/) {
+                               if ($other =~ /J/) {
                                        $ix++;
-                                       last if ($ix >= $num ||
-                                                !defined $hunk[$ix]{USE});
+                               }
+                               else {
+                                       error_msg "No next hunk\n";
+                               }
+                               next;
+                       }
+                       elsif ($line =~ /^k/) {
+                               if ($other =~ /k/) {
+                                       while (1) {
+                                               $ix--;
+                                               last if (!$ix ||
+                                                        !defined $hunk[$ix]{USE});
+                                       }
+                               }
+                               else {
+                                       error_msg "No previous hunk\n";
                                }
                                next;
                        }
+                       elsif ($line =~ /^j/) {
+                               if ($other !~ /j/) {
+                                       error_msg "No next hunk\n";
+                                       next;
+                               }
+                       }
                        elsif ($other =~ /s/ && $line =~ /^s/) {
                                my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY});
                                if (1 < @split) {
index 4b157fe5d536fdbdbf85e0e06c419b5927a90867..d3390755fc687a611e89320a7bbfb4ead512c863 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -8,21 +8,24 @@ OPTIONS_SPEC="\
 git am [options] [<mbox>|<Maildir>...]
 git am [options] (--resolved | --skip | --abort)
 --
-d,dotest=       (removed -- do not use)
 i,interactive   run interactively
-b,binary        (historical option -- no-op)
+b,binary*       (historical option -- no-op)
 3,3way          allow fall back on 3way merging if needed
 s,signoff       add a Signed-off-by line to the commit message
 u,utf8          recode into utf8 (default)
 k,keep          pass -k flag to git-mailinfo
 whitespace=     pass it through git-apply
+directory=      pass it through git-apply
 C=              pass it through git-apply
 p=              pass it through git-apply
+reject          pass it through git-apply
 resolvemsg=     override error message when patch failure occurs
 r,resolved      to be used after a patch failure
 skip            skip the current patch
 abort           restore the original branch and abort the patching operation.
-rebasing        (internal use for git-rebase)"
+committer-date-is-author-date    lie about committer date
+ignore-date     use current timestamp for author date
+rebasing*       (internal use for git-rebase)"
 
 . git-sh-setup
 prefix=$(git rev-parse --show-prefix)
@@ -33,6 +36,14 @@ cd_to_toplevel
 git var GIT_COMMITTER_IDENT >/dev/null ||
        die "You need to set your committer info first"
 
+sq () {
+       for sqarg
+       do
+               printf "%s" "$sqarg" |
+               sed -e 's/'\''/'\''\\'\'''\''/g' -e 's/.*/ '\''&'\''/'
+       done
+}
+
 stop_here () {
     echo "$1" >"$dotest/next"
     exit 1
@@ -124,6 +135,8 @@ dotest="$GIT_DIR/rebase-apply"
 sign= utf8=t keep= skip= interactive= resolved= rebasing= abort=
 resolvemsg= resume=
 git_apply_opt=
+committer_date_is_author_date=
+ignore_date=
 
 while test $# != 0
 do
@@ -155,10 +168,16 @@ do
                ;;
        --resolvemsg)
                shift; resolvemsg=$1 ;;
-       --whitespace)
-               git_apply_opt="$git_apply_opt $1=$2"; shift ;;
+       --whitespace|--directory)
+               git_apply_opt="$git_apply_opt $(sq "$1=$2")"; shift ;;
        -C|-p)
-               git_apply_opt="$git_apply_opt $1$2"; shift ;;
+               git_apply_opt="$git_apply_opt $(sq "$1$2")"; shift ;;
+       --reject)
+               git_apply_opt="$git_apply_opt $1" ;;
+       --committer-date-is-author-date)
+               committer_date_is_author_date=t ;;
+       --ignore-date)
+               ignore_date=t ;;
        --)
                shift; break ;;
        *)
@@ -192,7 +211,7 @@ then
                # unreliable -- stdin could be /dev/null for example
                # and the caller did not intend to feed us a patch but
                # wanted to continue unattended.
-               tty -s
+               test -t 0
                ;;
        *)
                false
@@ -202,6 +221,9 @@ then
        resume=yes
 
        case "$skip,$abort" in
+       t,t)
+               die "Please make up your mind. --skip or --abort?"
+               ;;
        t,)
                git rerere clear
                git read-tree --reset -u HEAD HEAD
@@ -210,12 +232,19 @@ then
                git update-ref ORIG_HEAD $orig_head
                ;;
        ,t)
+               if test -f "$dotest/rebasing"
+               then
+                       exec git rebase --abort
+               fi
                git rerere clear
-               git read-tree --reset -u HEAD ORIG_HEAD
-               git reset ORIG_HEAD
+               test -f "$dotest/dirtyindex" || {
+                       git read-tree --reset -u HEAD ORIG_HEAD
+                       git reset ORIG_HEAD
+               }
                rm -fr "$dotest"
                exit ;;
        esac
+       rm -f "$dotest/dirtyindex"
 else
        # Make sure we are not given --skip, --resolved, nor --abort
        test "$skip$resolved$abort" = "" ||
@@ -268,9 +297,10 @@ fi
 case "$resolved" in
 '')
        files=$(git diff-index --cached --name-only HEAD --) || exit
-       if [ "$files" ]; then
-          echo "Dirty index: cannot apply patches (dirty: $files)" >&2
-          exit 1
+       if test "$files"
+       then
+               : >"$dotest/dirtyindex"
+               die "Dirty index: cannot apply patches (dirty: $files)"
        fi
 esac
 
@@ -459,7 +489,7 @@ do
 
        case "$resolved" in
        '')
-               git apply $git_apply_opt --index "$dotest/patch"
+               eval 'git apply '"$git_apply_opt"' --index "$dotest/patch"'
                apply_status=$?
                ;;
        t)
@@ -501,7 +531,7 @@ do
        fi
        if test $apply_status != 0
        then
-               echo Patch failed at $msgnum.
+               printf 'Patch failed at %s %s\n' "$msgnum" "$FIRSTLINE"
                stop_here_user_resolve $this
        fi
 
@@ -512,7 +542,18 @@ do
 
        tree=$(git write-tree) &&
        parent=$(git rev-parse --verify HEAD) &&
-       commit=$(git commit-tree $tree -p $parent <"$dotest/final-commit") &&
+       commit=$(
+               if test -n "$ignore_date"
+               then
+                       GIT_AUTHOR_DATE=
+               fi
+               if test -n "$committer_date_is_author_date"
+               then
+                       GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
+                       export GIT_COMMITTER_DATE
+               fi &&
+               git commit-tree $tree -p $parent <"$dotest/final-commit"
+       ) &&
        git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent ||
        stop_here $this
 
index 85db4ba40022e3a9e5790879d6d21fa59475b316..e313bdea70d0a765106aa42a17a66f01d3d0f7d8 100755 (executable)
@@ -284,62 +284,74 @@ filter_skipped() {
        _skip="$2"
 
        if [ -z "$_skip" ]; then
-               eval "$_eval"
+               eval "$_eval" | {
+                       while read line
+                       do
+                               echo "$line &&"
+                       done
+                       echo ':'
+               }
                return
        fi
 
        # Let's parse the output of:
        # "git rev-list --bisect-vars --bisect-all ..."
-       eval "$_eval" | while read hash line
-       do
-               case "$VARS,$FOUND,$TRIED,$hash" in
-                       # We display some vars.
-                       1,*,*,*) echo "$hash $line" ;;
-
-                       # Split line.
-                       ,*,*,---*) ;;
-
-                       # We had nothing to search.
+       eval "$_eval" | {
+               VARS= FOUND= TRIED=
+               while read hash line
+               do
+                       case "$VARS,$FOUND,$TRIED,$hash" in
+                       1,*,*,*)
+                               # "bisect_foo=bar" read from rev-list output.
+                               echo "$hash &&"
+                               ;;
+                       ,*,*,---*)
+                               # Separator
+                               ;;
                        ,,,bisect_rev*)
-                               echo "bisect_rev="
+                               # We had nothing to search.
+                               echo "bisect_rev= &&"
                                VARS=1
                                ;;
-
-                       # We did not find a good bisect rev.
-                       # This should happen only if the "bad"
-                       # commit is also a "skip" commit.
                        ,,*,bisect_rev*)
-                               echo "bisect_rev=$TRIED"
+                               # We did not find a good bisect rev.
+                               # This should happen only if the "bad"
+                               # commit is also a "skip" commit.
+                               echo "bisect_rev='$TRIED' &&"
                                VARS=1
                                ;;
-
-                       # We are searching.
                        ,,*,*)
+                               # We are searching.
                                TRIED="${TRIED:+$TRIED|}$hash"
                                case "$_skip" in
                                *$hash*) ;;
                                *)
-                                       echo "bisect_rev=$hash"
-                                       echo "bisect_tried=\"$TRIED\""
+                                       echo "bisect_rev=$hash &&"
+                                       echo "bisect_tried='$TRIED' &&"
                                        FOUND=1
                                        ;;
                                esac
                                ;;
-
-                       # We have already found a rev to be tested.
-                       ,1,*,bisect_rev*) VARS=1 ;;
-                       ,1,*,*) ;;
-
-                       # ???
-                       *) die "filter_skipped error " \
-                           "VARS: '$VARS' " \
-                           "FOUND: '$FOUND' " \
-                           "TRIED: '$TRIED' " \
-                           "hash: '$hash' " \
-                           "line: '$line'"
-                       ;;
-               esac
-       done
+                       ,1,*,bisect_rev*)
+                               # We have already found a rev to be tested.
+                               VARS=1
+                               ;;
+                       ,1,*,*)
+                               ;;
+                       *)
+                               # Unexpected input
+                               echo "die 'filter_skipped error'"
+                               die "filter_skipped error " \
+                                   "VARS: '$VARS' " \
+                                   "FOUND: '$FOUND' " \
+                                   "TRIED: '$TRIED' " \
+                                   "hash: '$hash' " \
+                                   "line: '$line'"
+                               ;;
+                       esac
+               done
+               echo ':'
+       }
 }
 
 exit_if_skipped_commits () {
@@ -500,7 +512,7 @@ bisect_next() {
        # commit is also a "skip" commit (see above).
        exit_if_skipped_commits "$bisect_rev"
 
-       bisect_checkout "$bisect_rev" "$bisect_nr revisions left to test after this"
+       bisect_checkout "$bisect_rev" "$bisect_nr revisions left to test after this (roughly $bisect_steps steps)"
 }
 
 bisect_visualize() {
index e20b1e858cc715d1840da11c6198a1d6e6bec2a4..878d83dd0863570042af80919899b4d6aa57e35b 100644 (file)
@@ -303,6 +303,8 @@ extern ssize_t xwrite(int fd, const void *buf, size_t len);
 extern int xdup(int fd);
 extern FILE *xfdopen(int fd, const char *mode);
 extern int xmkstemp(char *template);
+extern int odb_mkstemp(char *template, size_t limit, const char *pattern);
+extern int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1);
 
 static inline size_t xsize_t(off_t len)
 {
@@ -317,6 +319,7 @@ static inline int has_extension(const char *filename, const char *ext)
 }
 
 /* Sane ctype - no locale, and works with signed chars */
+#undef isascii
 #undef isspace
 #undef isdigit
 #undef isalpha
@@ -327,13 +330,16 @@ extern unsigned char sane_ctype[256];
 #define GIT_SPACE 0x01
 #define GIT_DIGIT 0x02
 #define GIT_ALPHA 0x04
-#define GIT_SPECIAL 0x08
+#define GIT_GLOB_SPECIAL 0x08
+#define GIT_REGEX_SPECIAL 0x10
 #define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0)
+#define isascii(x) (((x) & ~0x7f) == 0)
 #define isspace(x) sane_istest(x,GIT_SPACE)
 #define isdigit(x) sane_istest(x,GIT_DIGIT)
 #define isalpha(x) sane_istest(x,GIT_ALPHA)
 #define isalnum(x) sane_istest(x,GIT_ALPHA | GIT_DIGIT)
-#define isspecial(x) sane_istest(x,GIT_SPECIAL)
+#define is_glob_special(x) sane_istest(x,GIT_GLOB_SPECIAL)
+#define is_regex_special(x) sane_istest(x,GIT_GLOB_SPECIAL | GIT_REGEX_SPECIAL)
 #define tolower(x) sane_case((unsigned char)(x), 0x20)
 #define toupper(x) sane_case((unsigned char)(x), 0)
 
index b0a805c688f59af29e1f25b514d73f3991285dee..ab6cea3e538047c30a5d728c7a16ee3c935c070b 100755 (executable)
@@ -76,6 +76,7 @@
     'history'         => \&req_CATCHALL,
     'watchers'        => \&req_EMPTY,
     'editors'         => \&req_EMPTY,
+    'noop'            => \&req_EMPTY,
     'annotate'        => \&req_annotate,
     'Global_option'   => \&req_Globaloption,
     #'annotate'        => \&req_CATCHALL,
@@ -1358,7 +1359,13 @@ sub req_ci
     # write our commit message out if we have one ...
     my ( $msg_fh, $msg_filename ) = tempfile( DIR => $TEMP_DIR );
     print $msg_fh $state->{opt}{m};# if ( exists ( $state->{opt}{m} ) );
-    print $msg_fh "\n\nvia git-CVS emulator\n";
+    if ( defined ( $cfg->{gitcvs}{commitmsgannotation} ) ) {
+        if ($cfg->{gitcvs}{commitmsgannotation} !~ /^\s*$/ ) {
+            print $msg_fh "\n\n".$cfg->{gitcvs}{commitmsgannotation}."\n"
+        }
+    } else {
+        print $msg_fh "\n\nvia git-CVS emulator\n";
+    }
     close $msg_fh;
 
     my $commithash = `git-commit-tree $treehash -p $parenthash < $msg_filename`;
@@ -1407,14 +1414,14 @@ sub req_ci
                close $pipe || die "bad pipe: $! $?";
        }
 
+    $updater->update();
+
        ### Then hooks/post-update
        $hook = $ENV{GIT_DIR}.'hooks/post-update';
        if (-x $hook) {
                system($hook, "refs/heads/$state->{module}");
        }
 
-    $updater->update();
-
     # foreach file specified on the command line ...
     foreach my $filename ( @committedfiles )
     {
@@ -2527,12 +2534,18 @@ sub open_blob_or_die
     return $fh;
 }
 
-# Generate a CVS author name from Git author information, by taking
-# the first eight characters of the user part of the email address.
+# Generate a CVS author name from Git author information, by taking the local
+# part of the email address and replacing characters not in the Portable
+# Filename Character Set (see IEEE Std 1003.1-2001, 3.276) by underscores. CVS
+# Login names are Unix login names, which should be restricted to this
+# character set.
 sub cvs_author
 {
     my $author_line = shift;
-    (my $author) = $author_line =~ /<([^>@]{1,8})/;
+    (my $author) = $author_line =~ /<([^@>]*)/;
+
+    $author =~ s/[^-a-zA-Z0-9_.]/_/g;
+    $author =~ s/^-/_/;
 
     $author;
 }
index c106f45af73446d26630030f77107efa520aa296..20f6f5175070f62ab74eda4839c637afe1004d49 100755 (executable)
@@ -40,6 +40,16 @@ skip_commit()
        done;
 }
 
+# if you run 'git_commit_non_empty_tree "$@"' in a commit filter,
+# it will skip commits that leave the tree untouched, commit the other.
+git_commit_non_empty_tree()
+{
+       if test $# = 3 && test "$1" = $(git rev-parse "$3^{tree}"); then
+               map "$3"
+       else
+               git commit-tree "$@"
+       fi
+}
 # override die(): this version puts in an extra line break, so that
 # the progress is still visible
 
@@ -98,7 +108,7 @@ OPTIONS_SPEC=
 . git-sh-setup
 
 if [ "$(is_bare_repository)" = false ]; then
-       git diff-files --quiet &&
+       git diff-files --ignore-submodules --quiet &&
        git diff-index --cached --quiet HEAD -- ||
        die "Cannot rewrite branch(es) with a dirty working directory."
 fi
@@ -109,11 +119,12 @@ filter_tree=
 filter_index=
 filter_parent=
 filter_msg=cat
-filter_commit='git commit-tree "$@"'
+filter_commit=
 filter_tag_name=
 filter_subdir=
 orig_namespace=refs/original/
 force=
+prune_empty=
 while :
 do
        case "$1" in
@@ -126,6 +137,11 @@ do
                force=t
                continue
                ;;
+       --prune-empty)
+               shift
+               prune_empty=t
+               continue
+               ;;
        -*)
                ;;
        *)
@@ -176,6 +192,17 @@ do
        esac
 done
 
+case "$prune_empty,$filter_commit" in
+,)
+       filter_commit='git commit-tree "$@"';;
+t,)
+       filter_commit="$functions;"' git_commit_non_empty_tree "$@"';;
+,*)
+       ;;
+*)
+       die "Cannot set --prune-empty and --filter-commit at the same time"
+esac
+
 case "$force" in
 t)
        rm -rf "$tempdir"
@@ -193,13 +220,21 @@ die ""
 # Remove tempdir on exit
 trap 'cd ../..; rm -rf "$tempdir"' 0
 
+ORIG_GIT_DIR="$GIT_DIR"
+ORIG_GIT_WORK_TREE="$GIT_WORK_TREE"
+ORIG_GIT_INDEX_FILE="$GIT_INDEX_FILE"
+GIT_WORK_TREE=.
+export GIT_DIR GIT_WORK_TREE
+
 # Make sure refs/original is empty
-git for-each-ref > "$tempdir"/backup-refs
+git for-each-ref > "$tempdir"/backup-refs || exit
 while read sha1 type name
 do
        case "$force,$name" in
        ,$orig_namespace*)
-               die "Namespace $orig_namespace not empty"
+               die "Cannot create a new backup.
+A previous backup already exists in $orig_namespace
+Force overwriting the backup with -f"
        ;;
        t,$orig_namespace*)
                git update-ref -d "$name" $sha1
@@ -207,15 +242,10 @@ do
        esac
 done < "$tempdir"/backup-refs
 
-ORIG_GIT_DIR="$GIT_DIR"
-ORIG_GIT_WORK_TREE="$GIT_WORK_TREE"
-ORIG_GIT_INDEX_FILE="$GIT_INDEX_FILE"
-GIT_WORK_TREE=.
-export GIT_DIR GIT_WORK_TREE
-
 # The refs should be updated if their heads were rewritten
-git rev-parse --no-flags --revs-only --symbolic-full-name --default HEAD "$@" |
-sed -e '/^^/d' >"$tempdir"/heads
+git rev-parse --no-flags --revs-only --symbolic-full-name \
+       --default HEAD "$@" > "$tempdir"/raw-heads || exit
+sed -e '/^^/d' "$tempdir"/raw-heads >"$tempdir"/heads
 
 test -s "$tempdir"/heads ||
        die "Which ref do you want to rewrite?"
@@ -224,8 +254,6 @@ GIT_INDEX_FILE="$(pwd)/../index"
 export GIT_INDEX_FILE
 git read-tree || die "Could not seed the index"
 
-ret=0
-
 # map old->new commit ids for rewriting parents
 mkdir ../map || die "Could not create map/ directory"
 
@@ -288,10 +316,11 @@ while read commit parents; do
                        die "tree filter failed: $filter_tree"
 
                (
-                       git diff-index -r --name-only $commit
+                       git diff-index -r --name-only $commit &&
                        git ls-files --others
-               ) |
-               git update-index --add --replace --remove --stdin
+               ) > "$tempdir"/tree-state || exit
+               git update-index --add --replace --remove --stdin \
+                       < "$tempdir"/tree-state || exit
        fi
 
        eval "$filter_index" < /dev/null ||
@@ -312,7 +341,8 @@ while read commit parents; do
                eval "$filter_msg" > ../message ||
                        die "msg filter failed: $filter_msg"
        @SHELL_PATH@ -c "$filter_commit" "git commit-tree" \
-               $(git write-tree) $parentstr < ../message > ../map/$commit
+               $(git write-tree) $parentstr < ../message > ../map/$commit ||
+                       die "could not write rewritten commit"
 done <../revs
 
 # In case of a subdirectory filter, it is possible that a specified head
@@ -380,7 +410,8 @@ do
                        die "Could not rewrite $ref"
        ;;
        esac
-       git update-ref -m "filter-branch: backup" "$orig_namespace$ref" $sha1
+       git update-ref -m "filter-branch: backup" "$orig_namespace$ref" $sha1 ||
+                exit
 done < "$tempdir"/heads
 
 # TODO: This should possibly go, with the semantics that all positive given
@@ -442,20 +473,21 @@ rm -rf "$tempdir"
 
 trap - 0
 
+unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE
+test -z "$ORIG_GIT_DIR" || {
+       GIT_DIR="$ORIG_GIT_DIR" && export GIT_DIR
+}
+test -z "$ORIG_GIT_WORK_TREE" || {
+       GIT_WORK_TREE="$ORIG_GIT_WORK_TREE" &&
+       export GIT_WORK_TREE
+}
+test -z "$ORIG_GIT_INDEX_FILE" || {
+       GIT_INDEX_FILE="$ORIG_GIT_INDEX_FILE" &&
+       export GIT_INDEX_FILE
+}
+
 if [ "$(is_bare_repository)" = false ]; then
-       unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE
-       test -z "$ORIG_GIT_DIR" || {
-               GIT_DIR="$ORIG_GIT_DIR" && export GIT_DIR
-       }
-       test -z "$ORIG_GIT_WORK_TREE" || {
-               GIT_WORK_TREE="$ORIG_GIT_WORK_TREE" &&
-               export GIT_WORK_TREE
-       }
-       test -z "$ORIG_GIT_INDEX_FILE" || {
-               GIT_INDEX_FILE="$ORIG_GIT_INDEX_FILE" &&
-               export GIT_INDEX_FILE
-       }
-       git read-tree -u -m HEAD
+       git read-tree -u -m HEAD || exit
 fi
 
-exit $ret
+exit 0
index 0843372b57371b62cd68f2818f634209f55d5395..5f4419b69b58769e8fee7a60109520a095a831c5 100755 (executable)
@@ -49,7 +49,7 @@ resolve_full_httpd () {
        esac
 
        httpd_only="$(echo $httpd | cut -f1 -d' ')"
-       if case "$httpd_only" in /*) : ;; *) which $httpd_only >/dev/null;; esac
+       if case "$httpd_only" in /*) : ;; *) which $httpd_only >/dev/null 2>&1;; esac
        then
                full_httpd=$httpd
        else
@@ -179,11 +179,74 @@ lighttpd_conf () {
        cat > "$conf" <<EOF
 server.document-root = "$fqgitdir/gitweb"
 server.port = $port
-server.modules = ( "mod_cgi" )
+server.modules = ( "mod_setenv", "mod_cgi" )
 server.indexfiles = ( "gitweb.cgi" )
 server.pid-file = "$fqgitdir/pid"
+server.errorlog = "$fqgitdir/gitweb/error.log"
+
+# to enable, add "mod_access", "mod_accesslog" to server.modules
+# variable above and uncomment this
+#accesslog.filename = "$fqgitdir/gitweb/access.log"
+
+setenv.add-environment = ( "PATH" => "/usr/local/bin:/usr/bin:/bin" )
+
 cgi.assign = ( ".cgi" => "" )
-mimetype.assign = ( ".css" => "text/css" )
+
+# mimetype mapping
+mimetype.assign             = (
+  ".pdf"          =>      "application/pdf",
+  ".sig"          =>      "application/pgp-signature",
+  ".spl"          =>      "application/futuresplash",
+  ".class"        =>      "application/octet-stream",
+  ".ps"           =>      "application/postscript",
+  ".torrent"      =>      "application/x-bittorrent",
+  ".dvi"          =>      "application/x-dvi",
+  ".gz"           =>      "application/x-gzip",
+  ".pac"          =>      "application/x-ns-proxy-autoconfig",
+  ".swf"          =>      "application/x-shockwave-flash",
+  ".tar.gz"       =>      "application/x-tgz",
+  ".tgz"          =>      "application/x-tgz",
+  ".tar"          =>      "application/x-tar",
+  ".zip"          =>      "application/zip",
+  ".mp3"          =>      "audio/mpeg",
+  ".m3u"          =>      "audio/x-mpegurl",
+  ".wma"          =>      "audio/x-ms-wma",
+  ".wax"          =>      "audio/x-ms-wax",
+  ".ogg"          =>      "application/ogg",
+  ".wav"          =>      "audio/x-wav",
+  ".gif"          =>      "image/gif",
+  ".jpg"          =>      "image/jpeg",
+  ".jpeg"         =>      "image/jpeg",
+  ".png"          =>      "image/png",
+  ".xbm"          =>      "image/x-xbitmap",
+  ".xpm"          =>      "image/x-xpixmap",
+  ".xwd"          =>      "image/x-xwindowdump",
+  ".css"          =>      "text/css",
+  ".html"         =>      "text/html",
+  ".htm"          =>      "text/html",
+  ".js"           =>      "text/javascript",
+  ".asc"          =>      "text/plain",
+  ".c"            =>      "text/plain",
+  ".cpp"          =>      "text/plain",
+  ".log"          =>      "text/plain",
+  ".conf"         =>      "text/plain",
+  ".text"         =>      "text/plain",
+  ".txt"          =>      "text/plain",
+  ".dtd"          =>      "text/xml",
+  ".xml"          =>      "text/xml",
+  ".mpeg"         =>      "video/mpeg",
+  ".mpg"          =>      "video/mpeg",
+  ".mov"          =>      "video/quicktime",
+  ".qt"           =>      "video/quicktime",
+  ".avi"          =>      "video/x-msvideo",
+  ".asf"          =>      "video/x-ms-asf",
+  ".asx"          =>      "video/x-ms-asf",
+  ".wmv"          =>      "video/x-ms-wmv",
+  ".bz2"          =>      "application/x-bzip",
+  ".tbz"          =>      "application/x-bzip-compressed-tar",
+  ".tar.bz2"      =>      "application/x-bzip-compressed-tar",
+  ""              =>      "text/plain"
+ )
 EOF
        test x"$local" = xtrue && echo 'server.bind = "127.0.0.1"' >> "$conf"
 }
index d4078a6affd9b4c1fa52e6dba0fe6c151fa452dc..87fa88af5526c8e27b823a65ca15bee4085f8ef2 100755 (executable)
@@ -8,12 +8,11 @@
 # at the discretion of Junio C Hamano.
 #
 
-USAGE='[--tool=tool] [file to merge] ...'
+USAGE='[--tool=tool] [-y|--no-prompt|--prompt] [file to merge] ...'
 SUBDIRECTORY_OK=Yes
 OPTIONS_SPEC=
 . git-sh-setup
 require_work_tree
-prefix=$(git rev-parse --show-prefix)
 
 # Returns true if the mode reflects a symlink
 is_symlink () {
@@ -70,16 +69,16 @@ resolve_symlink_merge () {
                git checkout-index -f --stage=2 -- "$MERGED"
                git add -- "$MERGED"
                cleanup_temp_files --save-backup
-               return
+               return 0
                ;;
            [rR]*)
                git checkout-index -f --stage=3 -- "$MERGED"
                git add -- "$MERGED"
                cleanup_temp_files --save-backup
-               return
+               return 0
                ;;
            [aA]*)
-               exit 1
+               return 1
                ;;
            esac
        done
@@ -97,15 +96,15 @@ resolve_deleted_merge () {
            [mMcC]*)
                git add -- "$MERGED"
                cleanup_temp_files --save-backup
-               return
+               return 0
                ;;
            [dD]*)
                git rm -- "$MERGED" > /dev/null
                cleanup_temp_files
-               return
+               return 0
                ;;
            [aA]*)
-               exit 1
+               return 1
                ;;
            esac
        done
@@ -127,6 +126,14 @@ check_unchanged () {
     fi
 }
 
+checkout_staged_file () {
+    tmpfile=$(expr "$(git checkout-index --temp --stage="$1" "$2")" : '\([^    ]*\)    ')
+
+    if test $? -eq 0 -a -n "$tmpfile" ; then
+       mv -- "$(git rev-parse --show-cdup)$tmpfile" "$3"
+    fi
+}
+
 merge_file () {
     MERGED="$1"
 
@@ -137,7 +144,7 @@ merge_file () {
        else
            echo "$MERGED: file does not need merging"
        fi
-       exit 1
+       return 1
     fi
 
     ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')"
@@ -153,9 +160,9 @@ merge_file () {
     local_mode=`git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $1;}'`
     remote_mode=`git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $1;}'`
 
-    base_present   && git cat-file blob ":1:$prefix$MERGED" >"$BASE" 2>/dev/null
-    local_present  && git cat-file blob ":2:$prefix$MERGED" >"$LOCAL" 2>/dev/null
-    remote_present && git cat-file blob ":3:$prefix$MERGED" >"$REMOTE" 2>/dev/null
+    base_present   && checkout_staged_file 1 "$MERGED" "$BASE"
+    local_present  && checkout_staged_file 2 "$MERGED" "$LOCAL"
+    remote_present && checkout_staged_file 3 "$MERGED" "$REMOTE"
 
     if test -z "$local_mode" -o -z "$remote_mode"; then
        echo "Deleted merge conflict for '$MERGED':"
@@ -176,8 +183,10 @@ merge_file () {
     echo "Normal merge conflict for '$MERGED':"
     describe_file "$local_mode" "local" "$LOCAL"
     describe_file "$remote_mode" "remote" "$REMOTE"
-    printf "Hit return to start merge resolution tool (%s): " "$merge_tool"
-    read ans
+    if "$prompt" = true; then
+       printf "Hit return to start merge resolution tool (%s): " "$merge_tool"
+       read ans
+    fi
 
     case "$merge_tool" in
        kdiff3)
@@ -198,14 +207,19 @@ merge_file () {
            fi
            status=$?
            ;;
-       meld|vimdiff)
+       meld)
            touch "$BACKUP"
            "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"
            check_unchanged
            ;;
+       vimdiff)
+           touch "$BACKUP"
+           "$merge_tool_path" -c "wincmd l" "$LOCAL" "$MERGED" "$REMOTE"
+           check_unchanged
+           ;;
        gvimdiff)
            touch "$BACKUP"
-           "$merge_tool_path" -f "$LOCAL" "$MERGED" "$REMOTE"
+           "$merge_tool_path" -c "wincmd l" -f "$LOCAL" "$MERGED" "$REMOTE"
            check_unchanged
            ;;
        xxdiff)
@@ -267,7 +281,12 @@ merge_file () {
     if test "$status" -ne 0; then
        echo "merge of $MERGED failed" 1>&2
        mv -- "$BACKUP" "$MERGED"
-       exit 1
+
+       if test "$merge_keep_temporaries" = "false"; then
+           cleanup_temp_files
+       fi
+
+       return 1
     fi
 
     if test "$merge_keep_backup" = "true"; then
@@ -278,8 +297,11 @@ merge_file () {
 
     git add -- "$MERGED"
     cleanup_temp_files
+    return 0
 }
 
+prompt=$(git config --bool mergetool.prompt || echo true)
+
 while test $# != 0
 do
     case "$1" in
@@ -295,6 +317,12 @@ do
                    shift ;;
            esac
            ;;
+       -y|--no-prompt)
+           prompt=false
+           ;;
+       --prompt)
+           prompt=true
+           ;;
        --)
            shift
            break
@@ -341,6 +369,22 @@ init_merge_tool_path() {
        fi
 }
 
+prompt_after_failed_merge() {
+    while true; do
+       printf "Continue merging other unresolved paths (y/n) ? "
+       read ans
+       case "$ans" in
+
+           [yY]*)
+               return 0
+               ;;
+
+           [nN]*)
+               return 1
+               ;;
+       esac
+    done
+}
 
 if test -z "$merge_tool"; then
     merge_tool=`git config merge.tool`
@@ -353,21 +397,19 @@ fi
 
 if test -z "$merge_tool" ; then
     if test -n "$DISPLAY"; then
-        merge_tool_candidates="kdiff3 tkdiff xxdiff meld gvimdiff"
         if test -n "$GNOME_DESKTOP_SESSION_ID" ; then
-            merge_tool_candidates="meld $merge_tool_candidates"
-        fi
-        if test "$KDE_FULL_SESSION" = "true"; then
-            merge_tool_candidates="kdiff3 $merge_tool_candidates"
+            merge_tool_candidates="meld kdiff3 tkdiff xxdiff gvimdiff"
+        else
+            merge_tool_candidates="kdiff3 tkdiff xxdiff meld gvimdiff"
         fi
     fi
     if echo "${VISUAL:-$EDITOR}" | grep 'emacs' > /dev/null 2>&1; then
-        merge_tool_candidates="$merge_tool_candidates emerge"
-    fi
-    if echo "${VISUAL:-$EDITOR}" | grep 'vim' > /dev/null 2>&1; then
-        merge_tool_candidates="$merge_tool_candidates vimdiff"
+        merge_tool_candidates="$merge_tool_candidates emerge opendiff vimdiff"
+    elif echo "${VISUAL:-$EDITOR}" | grep 'vim' > /dev/null 2>&1; then
+        merge_tool_candidates="$merge_tool_candidates vimdiff opendiff emerge"
+    else
+        merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff"
     fi
-    merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff"
     echo "merge tool candidates: $merge_tool_candidates"
     for i in $merge_tool_candidates; do
         init_merge_tool_path $i
@@ -389,6 +431,7 @@ else
     init_merge_tool_path "$merge_tool"
 
     merge_keep_backup="$(git config --bool merge.keepBackup || echo true)"
+    merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo false)"
 
     if test -z "$merge_tool_cmd" && ! type "$merge_tool_path" > /dev/null 2>&1; then
         echo "The merge tool $merge_tool is not available as '$merge_tool_path'"
@@ -400,27 +443,44 @@ else
     fi
 fi
 
+last_status=0
+rollup_status=0
 
 if test $# -eq 0 ; then
-       files=`git ls-files -u | sed -e 's/^[^  ]*      //' | sort -u`
-       if test -z "$files" ; then
-               echo "No files need merging"
-               exit 0
+    files=`git ls-files -u | sed -e 's/^[^     ]*      //' | sort -u`
+    if test -z "$files" ; then
+       echo "No files need merging"
+       exit 0
+    fi
+    echo Merging the files: "$files"
+    git ls-files -u |
+    sed -e 's/^[^      ]*      //' |
+    sort -u |
+    while IFS= read i
+    do
+       if test $last_status -ne 0; then
+           prompt_after_failed_merge < /dev/tty || exit 1
        fi
-       echo Merging the files: "$files"
-       git ls-files -u |
-       sed -e 's/^[^   ]*      //' |
-       sort -u |
-       while IFS= read i
-       do
-               printf "\n"
-               merge_file "$i" < /dev/tty > /dev/tty
-       done
+       printf "\n"
+       merge_file "$i" < /dev/tty > /dev/tty
+       last_status=$?
+       if test $last_status -ne 0; then
+           rollup_status=1
+       fi
+    done
 else
-       while test $# -gt 0; do
-               printf "\n"
-               merge_file "$1"
-               shift
-       done
+    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
-exit 0
+
+exit $rollup_status
index 2c7f432dc04254dcb9906f4d078eb16d195848ca..8a26763206dbd852aacb15db7f5319eb35678a29 100755 (executable)
@@ -16,7 +16,7 @@ cd_to_toplevel
 test -z "$(git ls-files -u)" ||
        die "You are in the middle of a conflicted merge."
 
-strategy_args= no_stat= no_commit= squash= no_ff= log_arg= verbosity=
+strategy_args= diffstat= no_commit= squash= no_ff= log_arg= verbosity=
 curr_branch=$(git symbolic-ref -q HEAD)
 curr_branch_short=$(echo "$curr_branch" | sed "s|refs/heads/||")
 rebase=$(git config --bool branch.$curr_branch_short.rebase)
@@ -28,9 +28,9 @@ do
        -v|--verbose)
                verbosity="$verbosity -v" ;;
        -n|--no-stat|--no-summary)
-               no_stat=-n ;;
+               diffstat=--no-stat ;;
        --stat|--summary)
-               no_stat=$1 ;;
+               diffstat=--stat ;;
        --log|--no-log)
                log_arg=$1 ;;
        --no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit)
@@ -171,6 +171,11 @@ case "$merge_head" in
                echo >&2 "Cannot merge multiple branches into empty head"
                exit 1
        fi
+       if test true = "$rebase"
+       then
+               echo >&2 "Cannot rebase onto multiple branches"
+               exit 1
+       fi
        ;;
 esac
 
@@ -183,7 +188,7 @@ fi
 
 merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit
 test true = "$rebase" &&
-       exec git-rebase $strategy_args --onto $merge_head \
+       exec git-rebase $diffstat $strategy_args --onto $merge_head \
        ${oldremoteref:-$merge_head}
-exec git-merge $no_stat $no_commit $squash $no_ff $log_arg $strategy_args \
+exec git-merge $diffstat $no_commit $squash $no_ff $log_arg $strategy_args \
        "$merge_name" HEAD $merge_head $verbosity
index cebaee1cc9dfc28d80173583b144a480be2f9bfd..9a6ba2b9874e12d31adeba354d8d44436ceadaf3 100755 (executable)
@@ -63,7 +63,7 @@ tmp_info="$tmp_dir/info"
 commit=$(git rev-parse HEAD)
 
 mkdir $tmp_dir || exit 2
-while read patch_name level garbage
+while read patch_name level garbage <&3
 do
        case "$patch_name" in ''|'#'*) continue;; esac
        case "$level" in
@@ -134,5 +134,5 @@ do
                commit=$( (echo "$SUBJECT"; echo; cat "$tmp_msg") | git commit-tree $tree -p $commit) &&
                git update-ref -m "quiltimport: $patch_name" HEAD $commit || exit 4
        fi
-done <"$QUILT_PATCHES/series"
+done 3<"$QUILT_PATCHES/series"
 rm -rf $tmp_dir || exit 5
index 8ed2244819d0950ddf1ffaa151b4d46bdc8b6db4..3dc659dd5896ce6c887eb786718919aef82f4ac6 100755 (executable)
@@ -27,6 +27,7 @@ continue           continue rebasing process
 abort              abort rebasing process and restore original branch
 skip               skip current patch and continue rebasing process
 no-verify          override pre-rebase hook from stopping the operation
+root               rebase all reachable commmits up to the root(s)
 "
 
 . git-sh-setup
@@ -44,6 +45,7 @@ STRATEGY=
 ONTO=
 VERBOSE=
 OK_TO_SKIP_PRE_REBASE=
+REBASE_ROOT=
 
 GIT_CHERRY_PICK_HELP="  After resolving the conflicts,
 mark the corrected paths with 'git add <paths>', and
@@ -154,6 +156,11 @@ pick_one () {
        output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
        test -d "$REWRITTEN" &&
                pick_one_preserving_merges "$@" && return
+       if test ! -z "$REBASE_ROOT"
+       then
+               output git cherry-pick "$@"
+               return
+       fi
        parent_sha1=$(git rev-parse --verify $sha1^) ||
                die "Could not get the parent of $sha1"
        current_sha1=$(git rev-parse --verify HEAD)
@@ -197,7 +204,11 @@ pick_one_preserving_merges () {
 
        # rewrite parents; if none were rewritten, we can fast-forward.
        new_parents=
-       pend=" $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-)"
+       pend=" $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)"
+       if test "$pend" = " "
+       then
+               pend=" root"
+       fi
        while [ "$pend" != "" ]
        do
                p=$(expr "$pend" : ' \([^ ]*\)')
@@ -227,7 +238,9 @@ pick_one_preserving_merges () {
                        if test -f "$DROPPED"/$p
                        then
                                fast_forward=f
-                               pend=" $(cat "$DROPPED"/$p)$pend"
+                               replacement="$(cat "$DROPPED"/$p)"
+                               test -z "$replacement" && replacement=root
+                               pend=" $replacement$pend"
                        else
                                new_parents="$new_parents $p"
                        fi
@@ -360,17 +373,15 @@ do_next () {
                pick_one -n $sha1 || failed=t
                case "$(peek_next_command)" in
                squash|s)
-                       EDIT_COMMIT=
                        USE_OUTPUT=output
                        MSG_OPT=-F
-                       MSG_FILE="$MSG"
+                       EDIT_OR_FILE="$MSG"
                        cp "$MSG" "$SQUASH_MSG"
                        ;;
                *)
-                       EDIT_COMMIT=-e
                        USE_OUTPUT=
                        MSG_OPT=
-                       MSG_FILE=
+                       EDIT_OR_FILE=-e
                        rm -f "$SQUASH_MSG" || exit
                        cp "$MSG" "$GIT_DIR"/SQUASH_MSG
                        rm -f "$GIT_DIR"/MERGE_MSG || exit
@@ -384,7 +395,8 @@ do_next () {
                        GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
                        GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
                        GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
-                       $USE_OUTPUT git commit --no-verify $MSG_OPT "$MSG_FILE" $EDIT_COMMIT || failed=t
+                       $USE_OUTPUT git commit --no-verify \
+                               $MSG_OPT "$EDIT_OR_FILE" || failed=t
                fi
                if test $failed = t
                then
@@ -443,6 +455,7 @@ get_saved_options () {
        test -d "$REWRITTEN" && PRESERVE_MERGES=t
        test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)"
        test -f "$DOTEST"/verbose && VERBOSE=t
+       test -f "$DOTEST"/rebase-root && REBASE_ROOT=t
 }
 
 while test $# != 0
@@ -547,6 +560,9 @@ first and then run 'git rebase --continue' again."
        -i)
                # yeah, we know
                ;;
+       --root)
+               REBASE_ROOT=t
+               ;;
        --onto)
                shift
                ONTO=$(git rev-parse --verify "$1") ||
@@ -554,27 +570,38 @@ first and then run 'git rebase --continue' again."
                ;;
        --)
                shift
-               run_pre_rebase_hook ${1+"$@"}
-               test $# -eq 1 -o $# -eq 2 || usage
+               test -z "$REBASE_ROOT" -a $# -ge 1 -a $# -le 2 ||
+               test ! -z "$REBASE_ROOT" -a $# -le 1 || usage
                test -d "$DOTEST" &&
                        die "Interactive rebase already started"
 
                git var GIT_COMMITTER_IDENT >/dev/null ||
                        die "You need to set your committer info first"
 
+               if test -z "$REBASE_ROOT"
+               then
+                       UPSTREAM_ARG="$1"
+                       UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
+                       test -z "$ONTO" && ONTO=$UPSTREAM
+                       shift
+               else
+                       UPSTREAM=
+                       UPSTREAM_ARG=--root
+                       test -z "$ONTO" &&
+                               die "You must specify --onto when using --root"
+               fi
+               run_pre_rebase_hook "$UPSTREAM_ARG" "$@"
+
                comment_for_reflog start
 
                require_clean_work_tree
 
-               UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
-               test -z "$ONTO" && ONTO=$UPSTREAM
-
-               if test ! -z "$2"
+               if test ! -z "$1"
                then
-                       output git show-ref --verify --quiet "refs/heads/$2" ||
-                               die "Invalid branchname: $2"
-                       output git checkout "$2" ||
-                               die "Could not checkout $2"
+                       output git show-ref --verify --quiet "refs/heads/$1" ||
+                               die "Invalid branchname: $1"
+                       output git checkout "$1" ||
+                               die "Could not checkout $1"
                fi
 
                HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?"
@@ -585,7 +612,12 @@ first and then run 'git rebase --continue' again."
                        echo "detached HEAD" > "$DOTEST"/head-name
 
                echo $HEAD > "$DOTEST"/head
-               echo $UPSTREAM > "$DOTEST"/upstream
+               case "$REBASE_ROOT" in
+               '')
+                       rm -f "$DOTEST"/rebase-root ;;
+               *)
+                       : >"$DOTEST"/rebase-root ;;
+               esac
                echo $ONTO > "$DOTEST"/onto
                test -z "$STRATEGY" || echo "$STRATEGY" > "$DOTEST"/strategy
                test t = "$VERBOSE" && : > "$DOTEST"/verbose
@@ -598,12 +630,19 @@ first and then run 'git rebase --continue' again."
                        # This ensures that commits on merged, but otherwise
                        # unrelated side branches are left alone. (Think "X"
                        # in the man page's example.)
-                       mkdir "$REWRITTEN" &&
-                       for c in $(git merge-base --all $HEAD $UPSTREAM)
-                       do
-                               echo $ONTO > "$REWRITTEN"/$c ||
+                       if test -z "$REBASE_ROOT"
+                       then
+                               mkdir "$REWRITTEN" &&
+                               for c in $(git merge-base --all $HEAD $UPSTREAM)
+                               do
+                                       echo $ONTO > "$REWRITTEN"/$c ||
+                                               die "Could not init rewritten commits"
+                               done
+                       else
+                               mkdir "$REWRITTEN" &&
+                               echo $ONTO > "$REWRITTEN"/root ||
                                        die "Could not init rewritten commits"
-                       done
+                       fi
                        # No cherry-pick because our first pass is to determine
                        # parents to rewrite and skipping dropped commits would
                        # prematurely end our probe
@@ -613,12 +652,21 @@ first and then run 'git rebase --continue' again."
                        MERGES_OPTION="--no-merges --cherry-pick"
                fi
 
-               SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM)
                SHORTHEAD=$(git rev-parse --short $HEAD)
                SHORTONTO=$(git rev-parse --short $ONTO)
+               if test -z "$REBASE_ROOT"
+                       # this is now equivalent to ! -z "$UPSTREAM"
+               then
+                       SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM)
+                       REVISIONS=$UPSTREAM...$HEAD
+                       SHORTREVISIONS=$SHORTUPSTREAM..$SHORTHEAD
+               else
+                       REVISIONS=$ONTO...$HEAD
+                       SHORTREVISIONS=$SHORTHEAD
+               fi
                git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \
                        --abbrev=7 --reverse --left-right --topo-order \
-                       $UPSTREAM...$HEAD | \
+                       $REVISIONS | \
                        sed -n "s/^>//p" | while read shortsha1 rest
                do
                        if test t != "$PRESERVE_MERGES"
@@ -626,14 +674,19 @@ first and then run 'git rebase --continue' again."
                                echo "pick $shortsha1 $rest" >> "$TODO"
                        else
                                sha1=$(git rev-parse $shortsha1)
-                               preserve=t
-                               for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-)
-                               do
-                                       if test -f "$REWRITTEN"/$p -a \( $p != $UPSTREAM -o $sha1 = $first_after_upstream \)
-                                       then
-                                               preserve=f
-                                       fi
-                               done
+                               if test -z "$REBASE_ROOT"
+                               then
+                                       preserve=t
+                                       for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)
+                                       do
+                                               if test -f "$REWRITTEN"/$p -a \( $p != $UPSTREAM -o $sha1 = $first_after_upstream \)
+                                               then
+                                                       preserve=f
+                                               fi
+                                       done
+                               else
+                                       preserve=f
+                               fi
                                if test f = "$preserve"
                                then
                                        touch "$REWRITTEN"/$sha1
@@ -647,11 +700,11 @@ first and then run 'git rebase --continue' again."
                then
                        mkdir "$DROPPED"
                        # Save all non-cherry-picked changes
-                       git rev-list $UPSTREAM...$HEAD --left-right --cherry-pick | \
+                       git rev-list $REVISIONS --left-right --cherry-pick | \
                                sed -n "s/^>//p" > "$DOTEST"/not-cherry-picks
                        # Now all commits and note which ones are missing in
                        # not-cherry-picks and hence being dropped
-                       git rev-list $UPSTREAM..$HEAD |
+                       git rev-list $REVISIONS |
                        while read rev
                        do
                                if test -f "$REWRITTEN"/$rev -a "$(grep "$rev" "$DOTEST"/not-cherry-picks)" = ""
@@ -660,17 +713,18 @@ first and then run 'git rebase --continue' again."
                                        # not worthwhile, we don't want to track its multiple heads,
                                        # just the history of its first-parent for others that will
                                        # be rebasing on top of it
-                                       git rev-list --parents -1 $rev | cut -d' ' -f2 > "$DROPPED"/$rev
+                                       git rev-list --parents -1 $rev | cut -d' ' -s -f2 > "$DROPPED"/$rev
                                        short=$(git rev-list -1 --abbrev-commit --abbrev=7 $rev)
                                        grep -v "^[a-z][a-z]* $short" <"$TODO" > "${TODO}2" ; mv "${TODO}2" "$TODO"
                                        rm "$REWRITTEN"/$rev
                                fi
                        done
                fi
+
                test -s "$TODO" || echo noop >> "$TODO"
                cat >> "$TODO" << EOF
 
-# Rebase $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO
+# Rebase $SHORTREVISIONS onto $SHORTONTO
 #
 # Commands:
 #  p, pick = use commit
index ebd4df3a0e821ddcfd1eabfcaac17f854e172a85..d38ab0b83fd46e4b3f7c0e81bc97a4b313925786 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano.
 #
 
-USAGE='[--interactive | -i] [-v] [--onto <newbase>] <upstream> [<branch>]'
+USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--onto <newbase>] [<upstream>|--root] [<branch>]'
 LONG_USAGE='git-rebase replaces <branch> with a new branch of the
 same name.  When the --onto option is provided the new branch starts
 out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
@@ -46,7 +46,10 @@ do_merge=
 dotest="$GIT_DIR"/rebase-merge
 prec=4
 verbose=
+diffstat=$(git config --bool rebase.stat)
 git_am_opt=
+rebase_root=
+force_rebase=
 
 continue_merge () {
        test -n "$prev_head" || die "prev_head must be defined"
@@ -288,15 +291,33 @@ do
                esac
                do_merge=t
                ;;
+       -n|--no-stat)
+               diffstat=
+               ;;
+       --stat)
+               diffstat=t
+               ;;
        -v|--verbose)
                verbose=t
+               diffstat=t
                ;;
        --whitespace=*)
                git_am_opt="$git_am_opt $1"
+               case "$1" in
+               --whitespace=fix|--whitespace=strip)
+                       force_rebase=t
+                       ;;
+               esac
                ;;
        -C*)
                git_am_opt="$git_am_opt $1"
                ;;
+       --root)
+               rebase_root=t
+               ;;
+       -f|--f|--fo|--for|--forc|force|--force-r|--force-re|--force-reb|--force-reba|--force_rebas|--force-rebase)
+               force_rebase=t
+               ;;
        -*)
                usage
                ;;
@@ -306,6 +327,7 @@ do
        esac
        shift
 done
+test $# -gt 2 && usage
 
 # Make sure we do not have $GIT_DIR/rebase-apply
 if test -z "$do_merge"
@@ -344,17 +366,29 @@ case "$diff" in
        ;;
 esac
 
-# The upstream head must be given.  Make sure it is valid.
-upstream_name="$1"
-upstream=`git rev-parse --verify "${upstream_name}^0"` ||
-    die "invalid upstream $upstream_name"
+if test -z "$rebase_root"
+then
+       # The upstream head must be given.  Make sure it is valid.
+       upstream_name="$1"
+       shift
+       upstream=`git rev-parse --verify "${upstream_name}^0"` ||
+       die "invalid upstream $upstream_name"
+       unset root_flag
+       upstream_arg="$upstream_name"
+else
+       test -z "$newbase" && die "--root must be used with --onto"
+       unset upstream_name
+       unset upstream
+       root_flag="--root"
+       upstream_arg="$root_flag"
+fi
 
 # Make sure the branch to rebase onto is valid.
 onto_name=${newbase-"$upstream_name"}
 onto=$(git rev-parse --verify "${onto_name}^0") || exit
 
 # If a hook exists, give it a chance to interrupt
-run_pre_rebase_hook ${1+"$@"}
+run_pre_rebase_hook "$upstream_arg" "$@"
 
 # If the branch to rebase is given, that is the branch we will rebase
 # $branch_name -- branch being rebased, or HEAD (already detached)
@@ -362,16 +396,16 @@ run_pre_rebase_hook ${1+"$@"}
 # $head_name -- refs/heads/<that-branch> or "detached HEAD"
 switch_to=
 case "$#" in
-2)
+1)
        # Is it "rebase other $branchname" or "rebase other $commit"?
-       branch_name="$2"
-       switch_to="$2"
+       branch_name="$1"
+       switch_to="$1"
 
-       if git show-ref --verify --quiet -- "refs/heads/$2" &&
-          branch=$(git rev-parse -q --verify "refs/heads/$2")
+       if git show-ref --verify --quiet -- "refs/heads/$1" &&
+          branch=$(git rev-parse -q --verify "refs/heads/$1")
        then
-               head_name="refs/heads/$2"
-       elif branch=$(git rev-parse -q --verify "$2")
+               head_name="refs/heads/$1"
+       elif branch=$(git rev-parse -q --verify "$1")
        then
                head_name="detached HEAD"
        else
@@ -393,7 +427,8 @@ case "$#" in
 esac
 orig_head=$branch
 
-# Now we are rebasing commits $upstream..$branch on top of $onto
+# Now we are rebasing commits $upstream..$branch (or with --root,
+# everything leading up to $branch) on top of $onto
 
 # Check if we are already based on $onto with linear history,
 # but this should be done only when upstream and onto are the same.
@@ -402,17 +437,15 @@ if test "$upstream" = "$onto" && test "$mb" = "$onto" &&
        # linear history?
        ! (git rev-list --parents "$onto".."$branch" | grep " .* ") > /dev/null
 then
-       # Lazily switch to the target branch if needed...
-       test -z "$switch_to" || git checkout "$switch_to"
-       echo >&2 "Current branch $branch_name is up to date."
-       exit 0
-fi
-
-if test -n "$verbose"
-then
-       echo "Changes from $mb to $onto:"
-       # We want color (if set), but no pager
-       GIT_PAGER='' git diff --stat --summary "$mb" "$onto"
+       if test -z "$force_rebase"
+       then
+               # Lazily switch to the target branch if needed...
+               test -z "$switch_to" || git checkout "$switch_to"
+               echo >&2 "Current branch $branch_name is up to date."
+               exit 0
+       else
+               echo "Current branch $branch_name is up to date, rebase forced."
+       fi
 fi
 
 # Detach HEAD and reset the tree
@@ -420,6 +453,16 @@ echo "First, rewinding head to replay your work on top of it..."
 git checkout -q "$onto^0" || die "could not detach HEAD"
 git update-ref ORIG_HEAD $branch
 
+if test -n "$diffstat"
+then
+       if test -n "$verbose"
+       then
+               echo "Changes from $mb to $onto:"
+       fi
+       # We want color (if set), but no pager
+       GIT_PAGER='' git diff --stat --summary "$mb" "$onto"
+fi
+
 # If the $onto is a proper descendant of the tip of the branch, then
 # we just fast forwarded.
 if test "$mb" = "$branch"
@@ -429,10 +472,17 @@ then
        exit 0
 fi
 
+if test -n "$rebase_root"
+then
+       revisions="$onto..$orig_head"
+else
+       revisions="$upstream..$orig_head"
+fi
+
 if test -z "$do_merge"
 then
        git format-patch -k --stdout --full-index --ignore-if-in-upstream \
-               "$upstream..$orig_head" |
+               $root_flag "$revisions" |
        git am $git_am_opt --rebasing --resolvemsg="$RESOLVEMSG" &&
        move_to_original_branch
        ret=$?
@@ -455,7 +505,7 @@ echo "$orig_head" > "$dotest/orig-head"
 echo "$head_name" > "$dotest/head-name"
 
 msgnum=0
-for cmt in `git rev-list --reverse --no-merges "$upstream..$orig_head"`
+for cmt in `git rev-list --reverse --no-merges "$revisions"`
 do
        msgnum=$(($msgnum + 1))
        echo "$cmt" > "$dotest/cmt.$msgnum"
index be6db5e805d62e0440f7de3c1f6ee3fcd16a2ed3..0144c2d7b95d5393b7993c95e6aff46f18f01e8b 100755 (executable)
@@ -60,6 +60,7 @@ case ",$all_into_one," in
        args='--unpacked --incremental'
        ;;
 ,t,)
+       args= existing=
        if [ -d "$PACKDIR" ]; then
                for e in `cd "$PACKDIR" && find . -type f -name '*.pack' \
                        | sed -e 's/^\.\///' -e 's/\.pack$//'`
@@ -67,10 +68,13 @@ case ",$all_into_one," in
                        if [ -e "$PACKDIR/$e.keep" ]; then
                                : keep
                        else
-                               args="$args --unpacked=$e.pack"
                                existing="$existing $e"
                        fi
                done
+               if test -n "$existing"
+               then
+                       args="--kept-pack-only"
+               fi
                if test -n "$args" -a -n "$unpack_unreachable" -a \
                        -n "$remove_redundant"
                then
index 77ca8fe8803f102b877c4d63ed1ffa41920cbd54..57127aa823833f75fb546e738fcb19381fc331f7 100755 (executable)
@@ -23,7 +23,7 @@
 use Text::ParseWords;
 use Data::Dumper;
 use Term::ANSIColor;
-use File::Temp qw/ tempdir /;
+use File::Temp qw/ tempdir tempfile /;
 use Error qw(:try);
 use Git;
 
@@ -68,14 +68,15 @@ sub usage {
   Automating:
     --identity              <str>  * Use the sendemail.<id> options.
     --cc-cmd                <str>  * Email Cc: via `<str> \$patch_path`
-    --suppress-cc           <str>  * author, self, sob, cccmd, all.
-    --[no-]signed-off-by-cc        * Send to Cc: and Signed-off-by:
-                                     addresses. Default on.
+    --suppress-cc           <str>  * author, self, sob, cc, cccmd, body, bodycc, all.
+    --[no-]signed-off-by-cc        * Send to Signed-off-by: addresses. Default on.
     --[no-]suppress-from           * Send to self. Default off.
     --[no-]chain-reply-to          * Chain In-Reply-To: fields. Default on.
     --[no-]thread                  * Use In-Reply-To: field. Default on.
 
   Administering:
+    --confirm               <str>  * Confirm recipients before sending;
+                                     auto, cc, compose, always, or never.
     --quiet                        * Output one line of info per email.
     --dry-run                      * Don't actually send the emails.
     --[no-]validate                * Perform patch sanity checks. Default on.
@@ -126,6 +127,7 @@ sub format_2822_time {
 }
 
 my $have_email_valid = eval { require Email::Valid; 1 };
+my $have_mail_address = eval { require Mail::Address; 1 };
 my $smtp;
 my $auth;
 
@@ -156,7 +158,7 @@ sub format_2822_time {
 # Behavior modification variables
 my ($quiet, $dry_run) = (0, 0);
 my $format_patch;
-my $compose_filename = $repo->repo_path() . "/.gitsendemail.msg.$$";
+my $compose_filename;
 
 # Handle interactive edition of files.
 my $multiedit;
@@ -181,7 +183,7 @@ sub do_edit {
 my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc, $cc_cmd);
 my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_encryption);
 my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts);
-my ($validate);
+my ($validate, $confirm);
 my (@suppress_cc);
 
 my %config_bool_settings = (
@@ -207,6 +209,7 @@ sub do_edit {
     "suppresscc" => \@suppress_cc,
     "envelopesender" => \$envelope_sender,
     "multiedit" => \$multiedit,
+    "confirm"   => \$confirm,
 );
 
 # Handle Uncouth Termination
@@ -219,11 +222,13 @@ sub signal_handler {
        system "stty echo";
 
        # tmp files from --compose
-       if (-e $compose_filename) {
-               print "'$compose_filename' contains an intermediate version of the email you were composing.\n";
-       }
-       if (-e ($compose_filename . ".final")) {
-               print "'$compose_filename.final' contains the composed email.\n"
+       if (defined $compose_filename) {
+               if (-e $compose_filename) {
+                       print "'$compose_filename' contains an intermediate version of the email you were composing.\n";
+               }
+               if (-e ($compose_filename . ".final")) {
+                       print "'$compose_filename.final' contains the composed email.\n"
+               }
        }
 
        exit;
@@ -256,6 +261,7 @@ sub signal_handler {
                    "suppress-from!" => \$suppress_from,
                    "suppress-cc=s" => \@suppress_cc,
                    "signed-off-cc|signed-off-by-cc!" => \$signed_off_by_cc,
+                   "confirm=s" => \$confirm,
                    "dry-run" => \$dry_run,
                    "envelope-sender=s" => \$envelope_sender,
                    "thread!" => \$thread,
@@ -267,6 +273,9 @@ sub signal_handler {
     usage();
 }
 
+die "Cannot run git format-patch from outside a repository\n"
+       if $format_patch and not $repo;
+
 # Now, let's fill any that aren't set in with defaults:
 
 sub read_config {
@@ -318,13 +327,13 @@ sub read_config {
 if (@suppress_cc) {
        foreach my $entry (@suppress_cc) {
                die "Unknown --suppress-cc field: '$entry'\n"
-                       unless $entry =~ /^(all|cccmd|cc|author|self|sob)$/;
+                       unless $entry =~ /^(all|cccmd|cc|author|self|sob|body|bodycc)$/;
                $suppress_cc{$entry} = 1;
        }
 }
 
 if ($suppress_cc{'all'}) {
-       foreach my $entry (qw (ccmd cc author self sob)) {
+       foreach my $entry (qw (ccmd cc author self sob body bodycc)) {
                $suppress_cc{$entry} = 1;
        }
        delete $suppress_cc{'all'};
@@ -334,6 +343,21 @@ sub read_config {
 $suppress_cc{'self'} = $suppress_from if defined $suppress_from;
 $suppress_cc{'sob'} = !$signed_off_by_cc if defined $signed_off_by_cc;
 
+if ($suppress_cc{'body'}) {
+       foreach my $entry (qw (sob bodycc)) {
+               $suppress_cc{$entry} = 1;
+       }
+       delete $suppress_cc{'body'};
+}
+
+# Set confirm's default value
+my $confirm_unconfigured = !defined $confirm;
+if ($confirm_unconfigured) {
+       $confirm = scalar %suppress_cc ? 'compose' : 'auto';
+};
+die "Unknown --confirm setting: '$confirm'\n"
+       unless $confirm =~ /^(?:auto|cc|compose|always|never)/;
+
 # Debugging, print out the suppressions.
 if (0) {
        print "suppressions:\n";
@@ -360,6 +384,14 @@ sub read_config {
        die "Comma in --bcclist entry: $entry'\n" unless $entry !~ m/,/;
 }
 
+sub parse_address_line {
+       if ($have_mail_address) {
+               return map { $_->format } Mail::Address->parse($_[0]);
+       } else {
+               return split_addrs($_[0]);
+       }
+}
+
 sub split_addrs {
        return quotewords('\s*,\s*', 1, @_);
 }
@@ -404,6 +436,7 @@ sub split_addrs {
 
 # returns 1 if the conflict must be solved using it as a format-patch argument
 sub check_file_rev_conflict($) {
+       return unless $repo;
        my $f = shift;
        try {
                $repo->command('rev-parse', '--verify', '--quiet', $f);
@@ -445,6 +478,8 @@ ($)
 }
 
 if (@rev_list_opts) {
+       die "Cannot run git format-patch from outside a repository\n"
+               unless $repo;
        push @files, $repo->command('format-patch', '-o', tempdir(CLEANUP => 1), @rev_list_opts);
 }
 
@@ -481,6 +516,9 @@ ($)
 if ($compose) {
        # Note that this does not need to be secure, but we will make a small
        # effort to have it be unique
+       $compose_filename = ($repo ?
+               tempfile(".gitsendemail.msg.XXXXXX", DIR => $repo->repo_path()) :
+               tempfile(".gitsendemail.msg.XXXXXX", DIR => "."))[1];
        open(C,">",$compose_filename)
                or die "Failed to open for writing $compose_filename: $!";
 
@@ -593,7 +631,7 @@ ($)
        }
 
        my $to = $_;
-       push @to, split_addrs($to);
+       push @to, parse_address_line($to);
        $prompting++;
 }
 
@@ -637,25 +675,13 @@ sub expand_aliases {
        $smtp_server ||= 'localhost'; # could be 127.0.0.1, too... *shrug*
 }
 
-if ($compose) {
-       while (1) {
-               $_ = $term->readline("Send this email? (y|n) ");
-               last if defined $_;
-               print "\n";
-       }
-
-       if (uc substr($_,0,1) ne 'Y') {
-               cleanup_compose_files();
-               exit(0);
-       }
-
-       if ($compose > 0) {
-               @files = ($compose_filename . ".final", @files);
-       }
+if ($compose && $compose > 0) {
+       @files = ($compose_filename . ".final", @files);
 }
 
 # Variables we set as part of the loop over files
-our ($message_id, %mail, $subject, $reply_to, $references, $message);
+our ($message_id, %mail, $subject, $reply_to, $references, $message,
+       $needs_confirm, $message_num);
 
 sub extract_valid_address {
        my $address = shift;
@@ -811,6 +837,37 @@ sub send_message
        unshift (@sendmail_parameters,
                        '-f', $raw_from) if(defined $envelope_sender);
 
+       if ($needs_confirm && !$dry_run) {
+               print "\n$header\n";
+               if ($needs_confirm eq "inform") {
+                       $confirm_unconfigured = 0; # squelch this message for the rest of this run
+                       print "    The Cc list above has been expanded by additional\n";
+                       print "    addresses found in the patch commit message. By default\n";
+                       print "    send-email prompts before sending whenever this occurs.\n";
+                       print "    This behavior is controlled by the sendemail.confirm\n";
+                       print "    configuration setting.\n";
+                       print "\n";
+                       print "    For additional information, run 'git send-email --help'.\n";
+                       print "    To retain the current behavior, but squelch this message,\n";
+                       print "    run 'git config --global sendemail.confirm auto'.\n\n";
+               }
+               while (1) {
+                       chomp ($_ = $term->readline(
+                               "Send this email? ([y]es|[n]o|[q]uit|[a]ll): "
+                       ));
+                       last if /^(?:yes|y|no|n|quit|q|all|a)/i;
+                       print "\n";
+               }
+               if (/^n/i) {
+                       return;
+               } elsif (/^q/i) {
+                       cleanup_compose_files();
+                       exit(0);
+               } elsif (/^a/i) {
+                       $confirm = 'never';
+               }
+       }
+
        if ($dry_run) {
                # We don't want to send the email.
        } elsif ($smtp_server =~ m#^/#) {
@@ -909,6 +966,7 @@ sub send_message
 $reply_to = $initial_reply_to;
 $references = $initial_reply_to || '';
 $subject = $initial_subject;
+$message_num = 0;
 
 foreach my $t (@files) {
        open(F,"<",$t) or die "can't open file $t";
@@ -917,91 +975,106 @@ sub send_message
        my $author_encoding;
        my $has_content_type;
        my $body_encoding;
-       @cc = @initial_cc;
+       @cc = ();
        @xh = ();
        my $input_format = undef;
-       my $header_done = 0;
+       my @header = ();
        $message = "";
+       $message_num++;
+       # First unfold multiline header fields
        while(<F>) {
-               if (!$header_done) {
-                       if (/^From /) {
-                               $input_format = 'mbox';
-                               next;
+               last if /^\s*$/;
+               if (/^\s+\S/ and @header) {
+                       chomp($header[$#header]);
+                       s/^\s+/ /;
+                       $header[$#header] .= $_;
+           } else {
+                       push(@header, $_);
+               }
+       }
+       # Now parse the header
+       foreach(@header) {
+               if (/^From /) {
+                       $input_format = 'mbox';
+                       next;
+               }
+               chomp;
+               if (!defined $input_format && /^[-A-Za-z]+:\s/) {
+                       $input_format = 'mbox';
+               }
+
+               if (defined $input_format && $input_format eq 'mbox') {
+                       if (/^Subject:\s+(.*)$/) {
+                               $subject = $1;
                        }
-                       chomp;
-                       if (!defined $input_format && /^[-A-Za-z]+:\s/) {
-                               $input_format = 'mbox';
+                       elsif (/^From:\s+(.*)$/) {
+                               ($author, $author_encoding) = unquote_rfc2047($1);
+                               next if $suppress_cc{'author'};
+                               next if $suppress_cc{'self'} and $author eq $sender;
+                               printf("(mbox) Adding cc: %s from line '%s'\n",
+                                       $1, $_) unless $quiet;
+                               push @cc, $1;
                        }
-
-                       if (defined $input_format && $input_format eq 'mbox') {
-                               if (/^Subject:\s+(.*)$/) {
-                                       $subject = $1;
-
-                               } elsif (/^(Cc|From):\s+(.*)$/) {
-                                       if (unquote_rfc2047($2) eq $sender) {
+                       elsif (/^Cc:\s+(.*)$/) {
+                               foreach my $addr (parse_address_line($1)) {
+                                       if (unquote_rfc2047($addr) eq $sender) {
                                                next if ($suppress_cc{'self'});
-                                       }
-                                       elsif ($1 eq 'From') {
-                                               ($author, $author_encoding)
-                                                 = unquote_rfc2047($2);
-                                               next if ($suppress_cc{'author'});
                                        } else {
                                                next if ($suppress_cc{'cc'});
                                        }
                                        printf("(mbox) Adding cc: %s from line '%s'\n",
-                                               $2, $_) unless $quiet;
-                                       push @cc, $2;
+                                               $addr, $_) unless $quiet;
+                                       push @cc, $addr;
                                }
-                               elsif (/^Content-type:/i) {
-                                       $has_content_type = 1;
-                                       if (/charset="?([^ "]+)/) {
-                                               $body_encoding = $1;
-                                       }
-                                       push @xh, $_;
-                               }
-                               elsif (/^Message-Id: (.*)/i) {
-                                       $message_id = $1;
-                               }
-                               elsif (!/^Date:\s/ && /^[-A-Za-z]+:\s+\S/) {
-                                       push @xh, $_;
-                               }
-
-                       } else {
-                               # In the traditional
-                               # "send lots of email" format,
-                               # line 1 = cc
-                               # line 2 = subject
-                               # So let's support that, too.
-                               $input_format = 'lots';
-                               if (@cc == 0 && !$suppress_cc{'cc'}) {
-                                       printf("(non-mbox) Adding cc: %s from line '%s'\n",
-                                               $_, $_) unless $quiet;
-
-                                       push @cc, $_;
-
-                               } elsif (!defined $subject) {
-                                       $subject = $_;
+                       }
+                       elsif (/^Content-type:/i) {
+                               $has_content_type = 1;
+                               if (/charset="?([^ "]+)/) {
+                                       $body_encoding = $1;
                                }
+                               push @xh, $_;
                        }
-
-                       # A whitespace line will terminate the headers
-                       if (m/^\s*$/) {
-                               $header_done = 1;
+                       elsif (/^Message-Id: (.*)/i) {
+                               $message_id = $1;
+                       }
+                       elsif (!/^Date:\s/ && /^[-A-Za-z]+:\s+\S/) {
+                               push @xh, $_;
                        }
+
                } else {
-                       $message .=  $_;
-                       if (/^(Signed-off-by|Cc): (.*)$/i) {
-                               next if ($suppress_cc{'sob'});
-                               chomp;
-                               my $c = $2;
-                               chomp $c;
-                               next if ($c eq $sender and $suppress_cc{'self'});
-                               push @cc, $c;
-                               printf("(sob) Adding cc: %s from line '%s'\n",
-                                       $c, $_) unless $quiet;
+                       # In the traditional
+                       # "send lots of email" format,
+                       # line 1 = cc
+                       # line 2 = subject
+                       # So let's support that, too.
+                       $input_format = 'lots';
+                       if (@cc == 0 && !$suppress_cc{'cc'}) {
+                               printf("(non-mbox) Adding cc: %s from line '%s'\n",
+                                       $_, $_) unless $quiet;
+                               push @cc, $_;
+                       } elsif (!defined $subject) {
+                               $subject = $_;
                        }
                }
        }
+       # Now parse the message body
+       while(<F>) {
+               $message .=  $_;
+               if (/^(Signed-off-by|Cc): (.*)$/i) {
+                       chomp;
+                       my ($what, $c) = ($1, $2);
+                       chomp $c;
+                       if ($c eq $sender) {
+                               next if ($suppress_cc{'self'});
+                       } else {
+                               next if $suppress_cc{'sob'} and $what =~ /Signed-off-by/i;
+                               next if $suppress_cc{'bodycc'} and $what =~ /Cc/i;
+                       }
+                       push @cc, $c;
+                       printf("(body) Adding cc: %s from line '%s'\n",
+                               $c, $_) unless $quiet;
+               }
+       }
        close F;
 
        if (defined $cc_cmd && !$suppress_cc{'cccmd'}) {
@@ -1020,7 +1093,7 @@ sub send_message
                        or die "(cc-cmd) failed to close pipe to '$cc_cmd'";
        }
 
-       if (defined $author) {
+       if (defined $author and $author ne $sender) {
                $message = "From: $author\n\n$message";
                if (defined $author_encoding) {
                        if ($has_content_type) {
@@ -1040,6 +1113,14 @@ sub send_message
                }
        }
 
+       $needs_confirm = (
+               $confirm eq "always" or
+               ($confirm =~ /^(?:auto|cc)$/ && @cc) or
+               ($confirm =~ /^(?:auto|compose)$/ && $compose && $message_num == 1));
+       $needs_confirm = "inform" if ($needs_confirm && $confirm_unconfigured && @cc);
+
+       @cc = (@initial_cc, @cc);
+
        send_message();
 
        # set up for the next message
@@ -1054,13 +1135,10 @@ sub send_message
        $message_id = undef;
 }
 
-if ($compose) {
-       cleanup_compose_files();
-}
+cleanup_compose_files();
 
 sub cleanup_compose_files() {
-       unlink($compose_filename, $compose_filename . ".final");
-
+       unlink($compose_filename, $compose_filename . ".final") if $compose;
 }
 
 $smtp->quit if $smtp;
index 2142308bcc6d2e2c4962859d18e12070cd4c1b1d..838233926f7ed07f781f2eb2e7547a2a5a086071 100755 (executable)
@@ -85,27 +85,14 @@ cd_to_toplevel () {
        cdup=$(git rev-parse --show-cdup)
        if test ! -z "$cdup"
        then
-               case "$cdup" in
-               /*)
-                       # Not quite the same as if we did "cd -P '$cdup'" when
-                       # $cdup contains ".." after symlink path components.
-                       # Don't fix that case at least until Git switches to
-                       # "cd -P" across the board.
-                       phys="$cdup"
-                       ;;
-               ..|../*|*/..|*/../*)
-                       # Interpret $cdup relative to the physical, not logical, cwd.
-                       # Probably /bin/pwd is more portable than passing -P to cd or pwd.
-                       phys="$(unset PWD; /bin/pwd)/$cdup"
-                       ;;
-               *)
-                       # There's no "..", so no need to make things absolute.
-                       phys="$cdup"
-                       ;;
-               esac
-
-               cd "$phys" || {
-                       echo >&2 "Cannot chdir to $phys, the toplevel of the working tree"
+               # The "-P" option says to follow "physical" directory
+               # structure instead of following symbolic links.  When cdup is
+               # "../", this means following the ".." entry in the current
+               # directory instead textually removing a symlink path element
+               # from the PWD shell variable.  The "-P" behavior is more
+               # consistent with the C-style chdir used by most of Git.
+               cd -P "$cdup" || {
+                       echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree"
                        exit 1
                }
        fi
index 68d6afd15c94844c2435b65a197d678aa52314c9..0a27232b90456b5471ee64d5dcf9965ad73f28af 100755 (executable)
@@ -5,7 +5,7 @@
 # Copyright (c) 2007 Lars Hjemli
 
 USAGE="[--quiet] [--cached] \
-[add <repo> [-b branch] <path>]|[status|init|update [-i|--init]|summary [-n|--summary-limit <n>] [<commit>]] \
+[add <repo> [-b branch] <path>]|[status|init|update [-i|--init] [-N|--no-fetch]|summary [-n|--summary-limit <n>] [<commit>]] \
 [--] [<path>...]|[foreach <command>]|[sync [--] [<path>...]]"
 OPTIONS_SPEC=
 . git-sh-setup
@@ -16,6 +16,7 @@ command=
 branch=
 quiet=
 cached=
+nofetch=
 
 #
 # print stuff on stdout unless -q was specified
@@ -59,7 +60,7 @@ resolve_relative_url ()
 #
 module_list()
 {
-       git ls-files --stage -- "$@" | grep '^160000 '
+       git ls-files --error-unmatch --stage -- "$@" | grep '^160000 '
 }
 
 #
@@ -309,6 +310,10 @@ cmd_update()
                        shift
                        cmd_init "$@" || return
                        ;;
+               -N|--no-fetch)
+                       shift
+                       nofetch=1
+                       ;;
                --)
                        shift
                        break
@@ -354,8 +359,16 @@ cmd_update()
                        then
                                force="-f"
                        fi
-                       (unset GIT_DIR; cd "$path" && git-fetch &&
-                               git-checkout $force -q "$sha1") ||
+
+                       if test -z "$nofetch"
+                       then
+                               (unset GIT_DIR; cd "$path" &&
+                                       git-fetch) ||
+                               die "Unable to fetch in submodule path '$path'"
+                       fi
+
+                       (unset GIT_DIR; cd "$path" &&
+                                 git-checkout $force -q "$sha1") ||
                        die "Unable to checkout '$sha1' in submodule path '$path'"
 
                        say "Submodule path '$path': checked out '$sha1'"
index ad01e182df8abbeac7efa3db0d84d55ab1563e40..8be6be00c6cc76bcf2fc2d9b1fe331ec5e81fd2e 100755 (executable)
@@ -70,7 +70,8 @@ BEGIN
 $Git::SVN::_follow_parent = 1;
 my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
                     'config-dir=s' => \$Git::SVN::Ra::config_dir,
-                    'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache );
+                    'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache,
+                    'ignore-paths=s' => \$SVN::Git::Fetcher::_ignore_regex );
 my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent,
                'authors-file|A=s' => \$_authors,
                'repack:i' => \$Git::SVN::_repack,
@@ -84,6 +85,7 @@ BEGIN
                   \$Git::SVN::_repack_flags,
                'use-log-author' => \$Git::SVN::_use_log_author,
                'add-author-from' => \$Git::SVN::_add_author_from,
+               'localtime' => \$Git::SVN::_localtime,
                %remote_opts );
 
 my ($_trunk, $_tags, $_branches, $_stdlayout);
@@ -436,7 +438,17 @@ sub cmd_dcommit {
                die "Unable to determine upstream SVN information from ",
                    "$head history.\nPerhaps the repository is empty.";
        }
-       $url = defined $_commit_url ? $_commit_url : $gs->full_url;
+
+       if (defined $_commit_url) {
+               $url = $_commit_url;
+       } else {
+               $url = eval { command_oneline('config', '--get',
+                             "svn-remote.$gs->{repo_id}.commiturl") };
+               if (!$url) {
+                       $url = $gs->full_url
+               }
+       }
+
        my $last_rev = $_revision if defined $_revision;
        if ($url) {
                print "Committing to $url ...\n";
@@ -668,7 +680,11 @@ sub cmd_create_ignore {
        $gs->prop_walk($gs->{path}, $r, sub {
                my ($gs, $path, $props) = @_;
                # $path is of the form /path/to/dir/
-               my $ignore = '.' . $path . '.gitignore';
+               $path = '.' . $path;
+               # SVN can have attributes on empty directories,
+               # which git won't track
+               mkpath([$path]) unless -d $path;
+               my $ignore = $path . '.gitignore';
                my $s = $props->{'svn:ignore'} or return;
                open(GITIGNORE, '>', $ignore)
                  or fatal("Failed to open `$ignore' for writing: $!");
@@ -911,7 +927,8 @@ sub cmd_info {
        if ($@) {
                $result .= "Repository Root: (offline)\n";
        }
-       $result .= "Repository UUID: $uuid\n" unless $diff_status eq "A";
+       $result .= "Repository UUID: $uuid\n" unless $diff_status eq "A" &&
+               ($SVN::Core::VERSION le '1.5.4' || $file_type ne "dir");
        $result .= "Revision: " . ($diff_status eq "A" ? 0 : $rev) . "\n";
 
        $result .= "Node Kind: " .
@@ -1364,7 +1381,7 @@ package Git::SVN;
 use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent
             $_repack $_repack_flags $_use_svm_props $_head
             $_use_svnsync_props $no_reuse_existing $_minimize_url
-           $_use_log_author $_add_author_from/;
+           $_use_log_author $_add_author_from $_localtime/;
 use Carp qw/croak/;
 use File::Path qw/mkpath/;
 use File::Copy qw/copy/;
@@ -1690,6 +1707,7 @@ sub find_by_url { # repos_root and, path are optional
                        my $prefix = '';
                        if ($rwr) {
                                $z = $rwr;
+                               remove_username($z);
                        } elsif (defined $svm) {
                                $z = $svm->{source};
                                $prefix = $svm->{replace};
@@ -2333,7 +2351,10 @@ sub match_paths {
        if (my $path = $paths->{"/$self->{path}"}) {
                return ($path->{action} eq 'D') ? 0 : 1;
        }
-       $self->{path_regex} ||= qr/^\/\Q$self->{path}\E\//;
+       my $repos_root = $self->ra->{repos_root};
+       my $extended_path = $self->{url} . '/' . $self->{path};
+       $extended_path =~ s#^\Q$repos_root\E(/|$)##;
+       $self->{path_regex} ||= qr/^\/\Q$extended_path\E\//;
        if (grep /$self->{path_regex}/, keys %$paths) {
                return 1;
        }
@@ -2386,22 +2407,8 @@ sub find_parent_branch {
        print STDERR  "Found possible branch point: ",
                      "$new_url => ", $self->full_url, ", $r\n";
        $branch_from =~ s#^/##;
-       my $gs = Git::SVN->find_by_url($new_url, $repos_root, $branch_from);
-       unless ($gs) {
-               my $ref_id = $self->{ref_id};
-               $ref_id =~ s/\@\d+$//;
-               $ref_id .= "\@$r";
-               # just grow a tail if we're not unique enough :x
-               $ref_id .= '-' while find_ref($ref_id);
-               print STDERR "Initializing parent: $ref_id\n";
-               my ($u, $p, $repo_id) = ($new_url, '', $ref_id);
-               if ($u =~ s#^\Q$url\E(/|$)##) {
-                       $p = $u;
-                       $u = $url;
-                       $repo_id = $self->{repo_id};
-               }
-               $gs = Git::SVN->init($u, $p, $repo_id, $ref_id, 1);
-       }
+       my $gs = $self->other_gs($new_url, $url, $repos_root,
+                                $branch_from, $r, $self->{ref_id});
        my ($r0, $parent) = $gs->find_rev_before($r, 1);
        {
                my ($base, $head);
@@ -2427,8 +2434,9 @@ sub find_parent_branch {
                        # do_switch works with svn/trunk >= r22312, but that
                        # is not included with SVN 1.4.3 (the latest version
                        # at the moment), so we can't rely on it
+                       $self->{last_rev} = $r0;
                        $self->{last_commit} = $parent;
-                       $ed = SVN::Git::Fetcher->new($self);
+                       $ed = SVN::Git::Fetcher->new($self, $gs->{path});
                        $gs->ra->gs_do_switch($r0, $rev, $gs,
                                              $self->full_url, $ed)
                          or die "SVN connection failed somewhere...\n";
@@ -2526,12 +2534,83 @@ sub get_untracked {
        \@out;
 }
 
+# parse_svn_date(DATE)
+# --------------------
+# Given a date (in UTC) from Subversion, return a string in the format
+# "<TZ Offset> <local date/time>" that Git will use.
+#
+# By default the parsed date will be in UTC; if $Git::SVN::_localtime
+# is true we'll convert it to the local timezone instead.
 sub parse_svn_date {
        my $date = shift || return '+0000 1970-01-01 00:00:00';
        my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T
-                                           (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x) or
+                                           (\d\d)\:(\d\d)\:(\d\d)\.\d*Z$/x) or
                                         croak "Unable to parse date: $date\n";
-       "+0000 $Y-$m-$d $H:$M:$S";
+       my $parsed_date;    # Set next.
+
+       if ($Git::SVN::_localtime) {
+               # Translate the Subversion datetime to an epoch time.
+               # Begin by switching ourselves to $date's timezone, UTC.
+               my $old_env_TZ = $ENV{TZ};
+               $ENV{TZ} = 'UTC';
+
+               my $epoch_in_UTC =
+                   POSIX::strftime('%s', $S, $M, $H, $d, $m - 1, $Y - 1900);
+
+               # Determine our local timezone (including DST) at the
+               # time of $epoch_in_UTC.  $Git::SVN::Log::TZ stored the
+               # value of TZ, if any, at the time we were run.
+               if (defined $Git::SVN::Log::TZ) {
+                       $ENV{TZ} = $Git::SVN::Log::TZ;
+               } else {
+                       delete $ENV{TZ};
+               }
+
+               my $our_TZ =
+                   POSIX::strftime('%Z', $S, $M, $H, $d, $m - 1, $Y - 1900);
+
+               # This converts $epoch_in_UTC into our local timezone.
+               my ($sec, $min, $hour, $mday, $mon, $year,
+                   $wday, $yday, $isdst) = localtime($epoch_in_UTC);
+
+               $parsed_date = sprintf('%s %04d-%02d-%02d %02d:%02d:%02d',
+                                      $our_TZ, $year + 1900, $mon + 1,
+                                      $mday, $hour, $min, $sec);
+
+               # Reset us to the timezone in effect when we entered
+               # this routine.
+               if (defined $old_env_TZ) {
+                       $ENV{TZ} = $old_env_TZ;
+               } else {
+                       delete $ENV{TZ};
+               }
+       } else {
+               $parsed_date = "+0000 $Y-$m-$d $H:$M:$S";
+       }
+
+       return $parsed_date;
+}
+
+sub other_gs {
+       my ($self, $new_url, $url, $repos_root,
+           $branch_from, $r, $old_ref_id) = @_;
+       my $gs = Git::SVN->find_by_url($new_url, $repos_root, $branch_from);
+       unless ($gs) {
+               my $ref_id = $old_ref_id;
+               $ref_id =~ s/\@\d+$//;
+               $ref_id .= "\@$r";
+               # just grow a tail if we're not unique enough :x
+               $ref_id .= '-' while find_ref($ref_id);
+               print STDERR "Initializing parent: $ref_id\n";
+               my ($u, $p, $repo_id) = ($new_url, '', $ref_id);
+               if ($u =~ s#^\Q$url\E(/|$)##) {
+                       $p = $u;
+                       $u = $url;
+                       $repo_id = $self->{repo_id};
+               }
+               $gs = Git::SVN->init($u, $p, $repo_id, $ref_id, 1);
+       }
+       $gs
 }
 
 sub check_author {
@@ -3194,13 +3273,18 @@ package SVN::Git::Fetcher;
 use Carp qw/croak/;
 use File::Temp qw/tempfile/;
 use IO::File qw//;
+use vars qw/$_ignore_regex/;
 
 # file baton members: path, mode_a, mode_b, pool, fh, blob, base
 sub new {
-       my ($class, $git_svn) = @_;
+       my ($class, $git_svn, $switch_path) = @_;
        my $self = SVN::Delta::Editor->new;
        bless $self, $class;
-       $self->{c} = $git_svn->{last_commit} if exists $git_svn->{last_commit};
+       if (exists $git_svn->{last_commit}) {
+               $self->{c} = $git_svn->{last_commit};
+               $self->{empty_symlinks} =
+                                 _mark_empty_symlinks($git_svn, $switch_path);
+       }
        $self->{empty} = {};
        $self->{dir_prop} = {};
        $self->{file_prop} = {};
@@ -3210,6 +3294,68 @@ sub new {
        $self;
 }
 
+# this uses the Ra object, so it must be called before do_{switch,update},
+# not inside them (when the Git::SVN::Fetcher object is passed) to
+# do_{switch,update}
+sub _mark_empty_symlinks {
+       my ($git_svn, $switch_path) = @_;
+       my $bool = Git::config_bool('svn.brokenSymlinkWorkaround');
+       return {} if (!defined($bool)) || (defined($bool) && ! $bool);
+
+       my %ret;
+       my ($rev, $cmt) = $git_svn->last_rev_commit;
+       return {} unless ($rev && $cmt);
+
+       # allow the warning to be printed for each revision we fetch to
+       # ensure the user sees it.  The user can also disable the workaround
+       # on the repository even while git svn is running and the next
+       # revision fetched will skip this expensive function.
+       my $printed_warning;
+       chomp(my $empty_blob = `git hash-object -t blob --stdin < /dev/null`);
+       my ($ls, $ctx) = command_output_pipe(qw/ls-tree -r -z/, $cmt);
+       local $/ = "\0";
+       my $pfx = defined($switch_path) ? $switch_path : $git_svn->{path};
+       $pfx .= '/' if length($pfx);
+       while (<$ls>) {
+               chomp;
+               s/\A100644 blob $empty_blob\t//o or next;
+               unless ($printed_warning) {
+                       print STDERR "Scanning for empty symlinks, ",
+                                    "this may take a while if you have ",
+                                    "many empty files\n",
+                                    "You may disable this with `",
+                                    "git config svn.brokenSymlinkWorkaround ",
+                                    "false'.\n",
+                                    "This may be done in a different ",
+                                    "terminal without restarting ",
+                                    "git svn\n";
+                       $printed_warning = 1;
+               }
+               my $path = $_;
+               my (undef, $props) =
+                              $git_svn->ra->get_file($pfx.$path, $rev, undef);
+               if ($props->{'svn:special'}) {
+                       $ret{$path} = 1;
+               }
+       }
+       command_close_pipe($ls, $ctx);
+       \%ret;
+}
+
+# returns true if a given path is inside a ".git" directory
+sub in_dot_git {
+       $_[0] =~ m{(?:^|/)\.git(?:/|$)};
+}
+
+# return value: 0 -- don't ignore, 1 -- ignore
+sub is_path_ignored {
+       my ($path) = @_;
+       return 1 if in_dot_git($path);
+       return 0 unless defined($_ignore_regex);
+       return 1 if $path =~ m!$_ignore_regex!o;
+       return 0;
+}
+
 sub set_path_strip {
        my ($self, $path) = @_;
        $self->{path_strip} = qr/^\Q$path\E(\/|$)/ if length $path;
@@ -3235,6 +3381,7 @@ sub git_path {
 
 sub delete_entry {
        my ($self, $path, $rev, $pb) = @_;
+       return undef if is_path_ignored($path);
 
        my $gpath = $self->git_path($path);
        return undef if ($gpath eq '');
@@ -3262,26 +3409,40 @@ sub delete_entry {
 
 sub open_file {
        my ($self, $path, $pb, $rev) = @_;
+       my ($mode, $blob);
+
+       goto out if is_path_ignored($path);
+
        my $gpath = $self->git_path($path);
-       my ($mode, $blob) = (command('ls-tree', $self->{c}, '--', $gpath)
+       ($mode, $blob) = (command('ls-tree', $self->{c}, '--', $gpath)
                             =~ /^(\d{6}) blob ([a-f\d]{40})\t/);
        unless (defined $mode && defined $blob) {
                die "$path was not found in commit $self->{c} (r$rev)\n";
        }
+       if ($mode eq '100644' && $self->{empty_symlinks}->{$path}) {
+               $mode = '120000';
+       }
+out:
        { path => $path, mode_a => $mode, mode_b => $mode, blob => $blob,
          pool => SVN::Pool->new, action => 'M' };
 }
 
 sub add_file {
        my ($self, $path, $pb, $cp_path, $cp_rev) = @_;
-       my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
-       delete $self->{empty}->{$dir};
-       { path => $path, mode_a => 100644, mode_b => 100644,
+       my $mode;
+
+       if (!is_path_ignored($path)) {
+               my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
+               delete $self->{empty}->{$dir};
+               $mode = '100644';
+       }
+       { path => $path, mode_a => $mode, mode_b => $mode,
          pool => SVN::Pool->new, action => 'A' };
 }
 
 sub add_directory {
        my ($self, $path, $cp_path, $cp_rev) = @_;
+       goto out if is_path_ignored($path);
        my $gpath = $self->git_path($path);
        if ($gpath eq '') {
                my ($ls, $ctx) = command_output_pipe(qw/ls-tree
@@ -3299,11 +3460,13 @@ sub add_directory {
        my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
        delete $self->{empty}->{$dir};
        $self->{empty}->{$path} = 1;
+out:
        { path => $path };
 }
 
 sub change_dir_prop {
        my ($self, $db, $prop, $value) = @_;
+       return undef if is_path_ignored($db->{path});
        $self->{dir_prop}->{$db->{path}} ||= {};
        $self->{dir_prop}->{$db->{path}}->{$prop} = $value;
        undef;
@@ -3311,6 +3474,7 @@ sub change_dir_prop {
 
 sub absent_directory {
        my ($self, $path, $pb) = @_;
+       return undef if is_path_ignored($path);
        $self->{absent_dir}->{$pb->{path}} ||= [];
        push @{$self->{absent_dir}->{$pb->{path}}}, $path;
        undef;
@@ -3318,6 +3482,7 @@ sub absent_directory {
 
 sub absent_file {
        my ($self, $path, $pb) = @_;
+       return undef if is_path_ignored($path);
        $self->{absent_file}->{$pb->{path}} ||= [];
        push @{$self->{absent_file}->{$pb->{path}}}, $path;
        undef;
@@ -3325,6 +3490,7 @@ sub absent_file {
 
 sub change_file_prop {
        my ($self, $fb, $prop, $value) = @_;
+       return undef if is_path_ignored($fb->{path});
        if ($prop eq 'svn:executable') {
                if ($fb->{mode_b} != 120000) {
                        $fb->{mode_b} = defined $value ? 100755 : 100644;
@@ -3340,22 +3506,43 @@ sub change_file_prop {
 
 sub apply_textdelta {
        my ($self, $fb, $exp) = @_;
+       return undef if is_path_ignored($fb->{path});
        my $fh = $::_repository->temp_acquire('svn_delta');
        # $fh gets auto-closed() by SVN::TxDelta::apply(),
        # (but $base does not,) so dup() it for reading in close_file
        open my $dup, '<&', $fh or croak $!;
        my $base = $::_repository->temp_acquire('git_blob');
+
        if ($fb->{blob}) {
-               print $base 'link ' if ($fb->{mode_a} == 120000);
-               my $size = $::_repository->cat_blob($fb->{blob}, $base);
+               my ($base_is_link, $size);
+
+               if ($fb->{mode_a} eq '120000' &&
+                   ! $self->{empty_symlinks}->{$fb->{path}}) {
+                       print $base 'link ' or die "print $!\n";
+                       $base_is_link = 1;
+               }
+       retry:
+               $size = $::_repository->cat_blob($fb->{blob}, $base);
                die "Failed to read object $fb->{blob}" if ($size < 0);
 
                if (defined $exp) {
                        seek $base, 0, 0 or croak $!;
                        my $got = ::md5sum($base);
-                       die "Checksum mismatch: $fb->{path} $fb->{blob}\n",
-                           "expected: $exp\n",
-                           "     got: $got\n" if ($got ne $exp);
+                       if ($got ne $exp) {
+                               my $err = "Checksum mismatch: ".
+                                      "$fb->{path} $fb->{blob}\n" .
+                                      "expected: $exp\n" .
+                                      "     got: $got\n";
+                               if ($base_is_link) {
+                                       warn $err,
+                                            "Retrying... (possibly ",
+                                            "a bad symlink from SVN)\n";
+                                       $::_repository->temp_reset($base);
+                                       $base_is_link = 0;
+                                       goto retry;
+                               }
+                               die $err;
+                       }
                }
        }
        seek $base, 0, 0 or croak $!;
@@ -3366,6 +3553,8 @@ sub apply_textdelta {
 
 sub close_file {
        my ($self, $fb, $exp) = @_;
+       return undef if is_path_ignored($fb->{path});
+
        my $hash;
        my $path = $self->git_path($fb->{path});
        if (my $fh = $fb->{fh}) {
@@ -3379,11 +3568,19 @@ sub close_file {
                }
                if ($fb->{mode_b} == 120000) {
                        sysseek($fh, 0, 0) or croak $!;
-                       sysread($fh, my $buf, 5) == 5 or croak $!;
+                       my $rd = sysread($fh, my $buf, 5);
 
-                       unless ($buf eq 'link ') {
+                       if (!defined $rd) {
+                               croak "sysread: $!\n";
+                       } elsif ($rd == 0) {
                                warn "$path has mode 120000",
-                                               " but is not a link\n";
+                                    " but it points to nothing\n",
+                                    "converting to an empty file with mode",
+                                    " 100644\n";
+                               $fb->{mode_b} = '100644';
+                       } elsif ($buf ne 'link ') {
+                               warn "$path has mode 120000",
+                                    " but is not a link\n";
                        } else {
                                my $tmp_fh = $::_repository->temp_acquire(
                                        'svn_hash');
@@ -3883,7 +4080,8 @@ package Git::SVN::Ra;
 BEGIN {
        # enforce temporary pool usage for some simple functions
        no strict 'refs';
-       for my $f (qw/rev_proplist get_latest_revnum get_uuid get_repos_root/) {
+       for my $f (qw/rev_proplist get_latest_revnum get_uuid get_repos_root
+                     get_file/) {
                my $SUPER = "SUPER::$f";
                *$f = sub {
                        my $self = shift;
@@ -4019,10 +4217,23 @@ sub DESTROY {
        # do not call the real DESTROY since we store ourselves in $RA
 }
 
+# get_log(paths, start, end, limit,
+#         discover_changed_paths, strict_node_history, receiver)
 sub get_log {
        my ($self, @args) = @_;
        my $pool = SVN::Pool->new;
-       splice(@args, 3, 1) if ($SVN::Core::VERSION le '1.2.0');
+
+       # the limit parameter was not supported in SVN 1.1.x, so we
+       # drop it.  Therefore, the receiver callback passed to it
+       # is made aware of this limitation by being wrapped if
+       # the limit passed to is being wrapped.
+       if ($SVN::Core::VERSION le '1.2.0') {
+               my $limit = splice(@args, 3, 1);
+               if ($limit > 0) {
+                       my $receiver = pop @args;
+                       push(@args, sub { &$receiver(@_) if (--$limit >= 0) });
+               }
+       }
        my $ret = $self->SUPER::get_log(@args, $pool);
        $pool->clear;
        $ret;
@@ -4185,6 +4396,9 @@ sub gs_fetch_loop_common {
                }
                $self->get_log([$longest_path], $min, $max, 0, 1, 1,
                               sub { $revs{$_[1]} = _cb(@_) });
+               if ($err) {
+                       print "Checked through r$max\r";
+               }
                if ($err && $max >= $head) {
                        print STDERR "Path '$longest_path' ",
                                     "was probably deleted:\n",
@@ -4419,6 +4633,7 @@ package Git::SVN::Log;
 use strict;
 use warnings;
 use POSIX qw/strftime/;
+use Time::Local;
 use constant commit_log_separator => ('-' x 72) . "\n";
 use vars qw/$TZ $limit $color $pager $non_recursive $verbose $oneline
             %rusers $show_commit $incremental/;
@@ -4525,7 +4740,12 @@ sub run_pager {
 }
 
 sub format_svn_date {
-       return strftime("%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)", localtime(shift));
+       # some systmes don't handle or mishandle %z, so be creative.
+       my $t = shift || time;
+       my $gm = timelocal(gmtime($t));
+       my $sign = qw( + + - )[ $t <=> $gm ];
+       my $gmoff = sprintf("%s%02d%02d", $sign, (gmtime(abs($t - $gm)))[2,1]);
+       return strftime("%Y-%m-%d %H:%M:%S $gmoff (%a, %d %b %Y)", localtime($t));
 }
 
 sub parse_git_date {
index 78d236b77f6e2b52d89bf7b579762723ede86d15..7ed0faddcd75e024ce18d8b80994d41a9207a7ca 100755 (executable)
@@ -115,7 +115,7 @@ if test -z "$browser" ; then
        browser_candidates="open $browser_candidates"
     fi
     # /bin/start indicates MinGW
-    if test -n /bin/start; then
+    if test -x /bin/start; then
        browser_candidates="start $browser_candidates"
     fi
 
diff --git a/git.c b/git.c
index 940a498962ceb06a1dba85547d61ad62bb81a499..c2b181ed78daa4510f5cfb7bbff5b78f449f872a 100644 (file)
--- a/git.c
+++ b/git.c
@@ -2,6 +2,7 @@
 #include "exec_cmd.h"
 #include "cache.h"
 #include "quote.h"
+#include "run-command.h"
 
 const char git_usage_string[] =
        "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate|--no-pager] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]";
@@ -158,7 +159,7 @@ static int handle_alias(int *argcp, const char ***argv)
                        if (ret >= 0 && WIFEXITED(ret) &&
                            WEXITSTATUS(ret) != 127)
                                exit(WEXITSTATUS(ret));
-                       die("Failed to run '%s' when expanding alias '%s'\n",
+                       die("Failed to run '%s' when expanding alias '%s'",
                            alias_string + 1, alias_command);
                }
                count = split_cmdline(alias_string, &new_argv);
@@ -219,7 +220,7 @@ struct cmd_struct {
        int option;
 };
 
-static int run_command(struct cmd_struct *p, int argc, const char **argv)
+static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
 {
        int status;
        struct stat st;
@@ -384,7 +385,7 @@ static void handle_internal_command(int argc, const char **argv)
                struct cmd_struct *p = commands+i;
                if (strcmp(p->cmd, cmd))
                        continue;
-               exit(run_command(p, argc, argv));
+               exit(run_builtin(p, argc, argv));
        }
 }
 
@@ -392,6 +393,7 @@ static void execv_dashed_external(const char **argv)
 {
        struct strbuf cmd = STRBUF_INIT;
        const char *tmp;
+       int status;
 
        strbuf_addf(&cmd, "git-%s", argv[0]);
 
@@ -406,37 +408,55 @@ static void execv_dashed_external(const char **argv)
 
        trace_argv_printf(argv, "trace: exec:");
 
-       /* execvp() can only ever return if it fails */
-       execvp(cmd.buf, (char **)argv);
-
-       trace_printf("trace: exec failed: %s\n", strerror(errno));
+       /*
+        * if we fail because the command is not found, it is
+        * OK to return. Otherwise, we just pass along the status code.
+        */
+       status = run_command_v_opt(argv, 0);
+       if (status != -ERR_RUN_COMMAND_EXEC) {
+               if (IS_RUN_COMMAND_ERR(status))
+                       die("unable to run '%s'", argv[0]);
+               exit(-status);
+       }
+       errno = ENOENT; /* as if we called execvp */
 
        argv[0] = tmp;
 
        strbuf_release(&cmd);
 }
 
-
-int main(int argc, const char **argv)
+static int run_argv(int *argcp, const char ***argv)
 {
-       const char *cmd = argv[0] && *argv[0] ? argv[0] : "git-help";
-       char *slash = (char *)cmd + strlen(cmd);
        int done_alias = 0;
 
-       /*
-        * Take the basename of argv[0] as the command
-        * name, and the dirname as the default exec_path
-        * if we don't have anything better.
-        */
-       do
-               --slash;
-       while (cmd <= slash && !is_dir_sep(*slash));
-       if (cmd <= slash) {
-               *slash++ = 0;
-               git_set_argv0_path(cmd);
-               cmd = slash;
+       while (1) {
+               /* See if it's an internal command */
+               handle_internal_command(*argcp, *argv);
+
+               /* .. then try the external ones */
+               execv_dashed_external(*argv);
+
+               /* It could be an alias -- this works around the insanity
+                * of overriding "git log" with "git show" by having
+                * alias.log = show
+                */
+               if (done_alias || !handle_alias(argcp, argv))
+                       break;
+               done_alias = 1;
        }
 
+       return done_alias;
+}
+
+
+int main(int argc, const char **argv)
+{
+       const char *cmd;
+
+       cmd = git_extract_argv0_path(argv[0]);
+       if (!cmd)
+               cmd = "git-help";
+
        /*
         * "git-xxxx" is the same as "git xxxx", but we obviously:
         *
@@ -480,31 +500,22 @@ int main(int argc, const char **argv)
        setup_path();
 
        while (1) {
-               /* See if it's an internal command */
-               handle_internal_command(argc, argv);
-
-               /* .. then try the external ones */
-               execv_dashed_external(argv);
-
-               /* It could be an alias -- this works around the insanity
-                * of overriding "git log" with "git show" by having
-                * alias.log = show
-                */
-               if (done_alias || !handle_alias(&argc, &argv))
+               static int done_help = 0;
+               static int was_alias = 0;
+               was_alias = run_argv(&argc, &argv);
+               if (errno != ENOENT)
                        break;
-               done_alias = 1;
-       }
-
-       if (errno == ENOENT) {
-               if (done_alias) {
+               if (was_alias) {
                        fprintf(stderr, "Expansion of alias '%s' failed; "
                                "'%s' is not a git-command\n",
                                cmd, argv[0]);
                        exit(1);
                }
-               argv[0] = help_unknown_cmd(cmd);
-               handle_internal_command(argc, argv);
-               execv_dashed_external(argv);
+               if (!done_help) {
+                       cmd = argv[0] = help_unknown_cmd(cmd);
+                       done_help = 1;
+               } else
+                       break;
        }
 
        fprintf(stderr, "Failed to run command '%s': %s\n",
index dc2a439618ffd92a92d9ca954a8597ba31875dab..1773ae63eb54f3b602b782a3633034aab2f828ae 100644 (file)
@@ -701,16 +701,17 @@ proc newvarc {view id} {
 }
 
 proc splitvarc {p v} {
-    global varcid varcstart varccommits varctok
+    global varcid varcstart varccommits varctok vtokmod
     global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
 
     set oa $varcid($v,$p)
+    set otok [lindex $varctok($v) $oa]
     set ac $varccommits($v,$oa)
     set i [lsearch -exact $varccommits($v,$oa) $p]
     if {$i <= 0} return
     set na [llength $varctok($v)]
     # "%" sorts before "0"...
-    set tok "[lindex $varctok($v) $oa]%[strrep $i]"
+    set tok "$otok%[strrep $i]"
     lappend varctok($v) $tok
     lappend varcrow($v) {}
     lappend varcix($v) {}
@@ -730,6 +731,9 @@ proc splitvarc {p v} {
     for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
        lset vupptr($v) $b $na
     }
+    if {[string compare $otok $vtokmod($v)] <= 0} {
+       modify_arc $v $oa
+    }
 }
 
 proc renumbervarc {a v} {
@@ -3363,7 +3367,6 @@ proc external_blame {parent_idx {line {}}} {
     # being given an absolute path...
     set f [make_relative $f]
     lappend cmdline $base_commit $f
-    puts "cmdline={$cmdline}"
     if {[catch {eval exec $cmdline &} err]} {
        error_popup "[mc "git gui blame: command failed:"] $err"
     }
@@ -5731,7 +5734,6 @@ proc drawcommits {row {endrow {}}} {
     optimize_rows $ro1 0 $r2
     if {$need_redisplay || $nrows_drawn > 2000} {
        clear_display
-       drawvisible
     }
 
     # make the lines join to already-drawn rows either side
index 19ae28ef9b5de046e003c02b9258dd576d8064f7..8433dd1d45780b3947cc4a0b31ee2059ea6e65bf 100644 (file)
@@ -162,14 +162,12 @@ not include variables usually directly set during build):
    $GITWEB_LIST during installation.  If empty, $projectroot is used
    to scan for repositories.
  * $my_url, $my_uri
-   URL and absolute URL of gitweb script; you might need to set those
-   variables if you are using 'pathinfo' feature: see also below.
+   Full URL and absolute URL of gitweb script;
+   in earlier versions of gitweb you might have need to set those
+   variables, now there should be no need to do it.
  * $home_link
    Target of the home link on top of all pages (the first part of view
-   "breadcrumbs").  By default set to absolute URI of a page; you might
-   need to set it up to [base] gitweb URI if you use 'pathinfo' feature
-   (alternative format of the URLs, with project name embedded directly
-   in the path part of URL).
+   "breadcrumbs").  By default set to absolute URI of a page ($my_uri).
  * @stylesheets
    List of URIs of stylesheets (relative to base URI of a page). You
    might specify more than one stylesheet, for example use gitweb.css
@@ -329,6 +327,82 @@ something like the following in your gitweb.conf (or gitweb_config.perl) file:
   $home_link = "/";
 
 
+PATH_INFO usage
+-----------------------
+If you enable PATH_INFO usage in gitweb by putting
+
+   $feature{'pathinfo'}{'default'} = [1];
+
+in your gitweb.conf, it is possible to set up your server so that it
+consumes and produces URLs in the form
+
+http://git.example.com/project.git/shortlog/sometag
+
+by using a configuration such as the following, that assumes that
+/var/www/gitweb is the DocumentRoot of your webserver, and that it
+contains the gitweb.cgi script and complementary static files
+(stylesheet, favicon):
+
+<VirtualHost *:80>
+       ServerAlias git.example.com
+
+       DocumentRoot /var/www/gitweb
+
+       <Directory /var/www/gitweb>
+               Options ExecCGI
+               AddHandler cgi-script cgi
+
+               DirectoryIndex gitweb.cgi
+
+               RewriteEngine On
+               RewriteCond %{REQUEST_FILENAME} !-f
+               RewriteCond %{REQUEST_FILENAME} !-d
+               RewriteRule ^.* /gitweb.cgi/$0 [L,PT]
+       </Directory>
+</VirtualHost>
+
+The rewrite rule guarantees that existing static files will be properly
+served, whereas any other URL will be passed to gitweb as PATH_INFO
+parameter.
+
+Notice that in this case you don't need special settings for
+@stylesheets, $my_uri and $home_link, but you lose "dumb client" access
+to your project .git dirs. A possible workaround for the latter is the
+following: in your project root dir (e.g. /pub/git) have the projects
+named without a .git extension (e.g. /pub/git/project instead of
+/pub/git/project.git) and configure Apache as follows:
+
+<VirtualHost *:80>
+       ServerAlias git.example.com
+
+       DocumentRoot /var/www/gitweb
+
+       AliasMatch ^(/.*?)(\.git)(/.*)? /pub/git$1$3
+       <Directory /var/www/gitweb>
+               Options ExecCGI
+               AddHandler cgi-script cgi
+
+               DirectoryIndex gitweb.cgi
+
+               RewriteEngine On
+               RewriteCond %{REQUEST_FILENAME} !-f
+               RewriteCond %{REQUEST_FILENAME} !-d
+               RewriteRule ^.* /gitweb.cgi/$0 [L,PT]
+       </Directory>
+</VirtualHost>
+
+The additional AliasMatch makes it so that
+
+http://git.example.com/project.git
+
+will give raw access to the project's git dir (so that the project can
+be cloned), while
+
+http://git.example.com/project
+
+will provide human-friendly gitweb access.
+
+
 Originally written by:
   Kay Sievers <kay.sievers@vrfy.org>
 
index bdaa4e9463460a149a5c7f13881e5373257bc4e5..33ef190ceb99565f804783ba9fe76987ad6bea89 100755 (executable)
@@ -27,13 +27,29 @@ BEGIN
 our $my_url = $cgi->url();
 our $my_uri = $cgi->url(-absolute => 1);
 
-# if we're called with PATH_INFO, we have to strip that
-# from the URL to find our real URL
-# we make $path_info global because it's also used later on
+# Base URL for relative URLs in gitweb ($logo, $favicon, ...),
+# needed and used only for URLs with nonempty PATH_INFO
+our $base_url = $my_url;
+
+# When the script is used as DirectoryIndex, the URL does not contain the name
+# of the script file itself, and $cgi->url() fails to strip PATH_INFO, so we
+# have to do it ourselves. We make $path_info global because it's also used
+# later on.
+#
+# Another issue with the script being the DirectoryIndex is that the resulting
+# $my_url data is not the full script URL: this is good, because we want
+# generated links to keep implying the script name if it wasn't explicitly
+# indicated in the URL we're handling, but it means that $my_url cannot be used
+# as base URL.
+# Therefore, if we needed to strip PATH_INFO, then we know that we have
+# to build the base URL ourselves:
 our $path_info = $ENV{"PATH_INFO"};
 if ($path_info) {
-       $my_url =~ s,\Q$path_info\E$,,;
-       $my_uri =~ s,\Q$path_info\E$,,;
+       if ($my_url =~ s,\Q$path_info\E$,, &&
+           $my_uri =~ s,\Q$path_info\E$,, &&
+           defined $ENV{'SCRIPT_NAME'}) {
+               $base_url = $cgi->url(-base => 1) . $ENV{'SCRIPT_NAME'};
+       }
 }
 
 # core git executable to use
@@ -207,7 +223,7 @@ BEGIN
        # $feature{'blame'}{'override'} = 1;
        # and in project config gitweb.blame = 0|1;
        'blame' => {
-               'sub' => \&feature_blame,
+               'sub' => sub { feature_bool('blame', @_) },
                'override' => 0,
                'default' => [0]},
 
@@ -245,7 +261,7 @@ BEGIN
        # $feature{'grep'}{'override'} = 1;
        # and in project config gitweb.grep = 0|1;
        'grep' => {
-               'sub' => \&feature_grep,
+               'sub' => sub { feature_bool('grep', @_) },
                'override' => 0,
                'default' => [1]},
 
@@ -259,7 +275,7 @@ BEGIN
        # $feature{'pickaxe'}{'override'} = 1;
        # and in project config gitweb.pickaxe = 0|1;
        'pickaxe' => {
-               'sub' => \&feature_pickaxe,
+               'sub' => sub { feature_bool('pickaxe', @_) },
                'override' => 0,
                'default' => [1]},
 
@@ -334,6 +350,21 @@ BEGIN
        'ctags' => {
                'override' => 0,
                'default' => [0]},
+
+       # The maximum number of patches in a patchset generated in patch
+       # view. Set this to 0 or undef to disable patch view, or to a
+       # negative number to remove any limit.
+
+       # To disable system wide have in $GITWEB_CONFIG
+       # $feature{'patches'}{'default'} = [0];
+       # To have project specific config enable override in $GITWEB_CONFIG
+       # $feature{'patches'}{'override'} = 1;
+       # and in project config gitweb.patches = 0|n;
+       # where n is the maximum number of patches allowed in a patchset.
+       'patches' => {
+               'sub' => \&feature_patches,
+               'override' => 0,
+               'default' => [16]},
 );
 
 sub gitweb_get_feature {
@@ -367,16 +398,17 @@ sub gitweb_check_feature {
 }
 
 
-sub feature_blame {
-       my ($val) = git_get_project_config('blame', '--bool');
+sub feature_bool {
+       my $key = shift;
+       my ($val) = git_get_project_config($key, '--bool');
 
-       if ($val eq 'true') {
-               return 1;
+       if (!defined $val) {
+               return ($_[0]);
+       } elsif ($val eq 'true') {
+               return (1);
        } elsif ($val eq 'false') {
-               return 0;
+               return (0);
        }
-
-       return $_[0];
 }
 
 sub feature_snapshot {
@@ -391,25 +423,11 @@ sub feature_snapshot {
        return @fmts;
 }
 
-sub feature_grep {
-       my ($val) = git_get_project_config('grep', '--bool');
-
-       if ($val eq 'true') {
-               return (1);
-       } elsif ($val eq 'false') {
-               return (0);
-       }
-
-       return ($_[0]);
-}
-
-sub feature_pickaxe {
-       my ($val) = git_get_project_config('pickaxe', '--bool');
+sub feature_patches {
+       my @val = (git_get_project_config('patches', '--int'));
 
-       if ($val eq 'true') {
-               return (1);
-       } elsif ($val eq 'false') {
-               return (0);
+       if (@val) {
+               return @val;
        }
 
        return ($_[0]);
@@ -508,6 +526,8 @@ sub filter_snapshot_fmts {
        "heads" => \&git_heads,
        "history" => \&git_history,
        "log" => \&git_log,
+       "patch" => \&git_patch,
+       "patches" => \&git_patches,
        "rss" => \&git_rss,
        "atom" => \&git_atom,
        "search" => \&git_search,
@@ -834,7 +854,7 @@ (%)
        }
 
        my $use_pathinfo = gitweb_check_feature('pathinfo');
-       if ($use_pathinfo) {
+       if ($use_pathinfo and defined $params{'project'}) {
                # try to put as many parameters as possible in PATH_INFO:
                #   - project name
                #   - action
@@ -849,7 +869,7 @@ (%)
                $href =~ s,/$,,;
 
                # Then add the project name, if present
-               $href .= "/".esc_url($params{'project'}) if defined $params{'project'};
+               $href .= "/".esc_url($params{'project'});
                delete $params{'project'};
 
                # since we destructively absorb parameters, we keep this
@@ -1364,13 +1384,11 @@ sub format_log_line_html {
        my $line = shift;
 
        $line = esc_html($line, -nbsp=>1);
-       if ($line =~ m/([0-9a-fA-F]{8,40})/) {
-               my $hash_text = $1;
-               my $link =
-                       $cgi->a({-href => href(action=>"object", hash=>$hash_text),
-                               -class => "text"}, $hash_text);
-               $line =~ s/$hash_text/$link/;
-       }
+       $line =~ s{\b([0-9a-fA-F]{8,40})\b}{
+               $cgi->a({-href => href(action=>"object", hash=>$1),
+                                       -class => "text"}, $1);
+       }eg;
+
        return $line;
 }
 
@@ -1894,18 +1912,19 @@ sub git_parse_project_config {
        return %config;
 }
 
-# convert config value to boolean, 'true' or 'false'
+# convert config value to boolean: 'true' or 'false'
 # no value, number > 0, 'true' and 'yes' values are true
 # rest of values are treated as false (never as error)
 sub config_to_bool {
        my $val = shift;
 
+       return 1 if !defined $val;             # section.key
+
        # strip leading and trailing whitespace
        $val =~ s/^\s+//;
        $val =~ s/\s+$//;
 
-       return (!defined $val ||               # section.key
-               ($val =~ /^\d+$/ && $val) ||   # section.key = 1
+       return (($val =~ /^\d+$/ && $val) ||   # section.key = 1
                ($val =~ /^(?:true|yes)$/i));  # section.key = true
 }
 
@@ -1958,6 +1977,9 @@ sub git_get_project_config {
                $config_file = "$git_dir/config";
        }
 
+       # check if config variable (key) exists
+       return unless exists $config{"gitweb.$key"};
+
        # ensure given type
        if (!defined $type) {
                return $config{"gitweb.$key"};
@@ -2901,9 +2923,14 @@ sub git_header_html {
 <meta name="robots" content="index, nofollow"/>
 <title>$title</title>
 EOF
-# print out each stylesheet that exist
+       # the stylesheet, favicon etc urls won't work correctly with path_info
+       # unless we set the appropriate base URL
+       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) {
-#provides backwards capability for those people who define style sheet in a config file
                print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
        } else {
                foreach my $stylesheet (@stylesheets) {
@@ -4582,28 +4609,33 @@ sub git_tag {
 }
 
 sub git_blame {
-       my $fd;
-       my $ftype;
-
+       # permissions
        gitweb_check_feature('blame')
-           or die_error(403, "Blame view not allowed");
+               or die_error(403, "Blame view not allowed");
 
+       # error checking
        die_error(400, "No file name given") unless $file_name;
        $hash_base ||= git_get_head_hash($project);
-       die_error(404, "Couldn't find base commit") unless ($hash_base);
+       die_error(404, "Couldn't find base commit") unless $hash_base;
        my %co = parse_commit($hash_base)
                or die_error(404, "Commit not found");
+       my $ftype = "blob";
        if (!defined $hash) {
                $hash = git_get_hash_by_path($hash_base, $file_name, "blob")
                        or die_error(404, "Error looking up file");
+       } else {
+               $ftype = git_get_type($hash);
+               if ($ftype !~ "blob") {
+                       die_error(400, "Object is not a blob");
+               }
        }
-       $ftype = git_get_type($hash);
-       if ($ftype !~ "blob") {
-               die_error(400, "Object is not a blob");
-       }
-       open ($fd, "-|", git_cmd(), "blame", '-p', '--',
-             $file_name, $hash_base)
+
+       # run git-blame --porcelain
+       open my $fd, "-|", git_cmd(), "blame", '-p',
+               $hash_base, '--', $file_name
                or die_error(500, "Open git-blame failed");
+
+       # page header
        git_header_html();
        my $formats_nav =
                $cgi->a({-href => href(action=>"blob", -replay=>1)},
@@ -4617,42 +4649,46 @@ sub git_blame {
        git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
        git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
        git_print_page_path($file_name, $ftype, $hash_base);
-       my @rev_color = (qw(light2 dark2));
+
+       # page body
+       my @rev_color = qw(light2 dark2);
        my $num_colors = scalar(@rev_color);
        my $current_color = 0;
-       my $last_rev;
+       my %metainfo = ();
+
        print <<HTML;
 <div class="page_body">
 <table class="blame">
 <tr><th>Commit</th><th>Line</th><th>Data</th></tr>
 HTML
-       my %metainfo = ();
-       while (1) {
-               $_ = <$fd>;
-               last unless defined $_;
+ LINE:
+       while (my $line = <$fd>) {
+               chomp $line;
+               # the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>]
+               # no <lines in group> for subsequent lines in group of lines
                my ($full_rev, $orig_lineno, $lineno, $group_size) =
-                   /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/;
+                  ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/);
                if (!exists $metainfo{$full_rev}) {
                        $metainfo{$full_rev} = {};
                }
                my $meta = $metainfo{$full_rev};
-               while (<$fd>) {
-                       last if (s/^\t//);
-                       if (/^(\S+) (.*)$/) {
+               my $data;
+               while ($data = <$fd>) {
+                       chomp $data;
+                       last if ($data =~ s/^\t//); # contents of line
+                       if ($data =~ /^(\S+) (.*)$/) {
                                $meta->{$1} = $2;
                        }
                }
-               my $data = $_;
-               chomp $data;
-               my $rev = substr($full_rev, 0, 8);
+               my $short_rev = substr($full_rev, 0, 8);
                my $author = $meta->{'author'};
-               my %date = parse_date($meta->{'author-time'},
-                                     $meta->{'author-tz'});
+               my %date =
+                       parse_date($meta->{'author-time'}, $meta->{'author-tz'});
                my $date = $date{'iso-tz'};
                if ($group_size) {
-                       $current_color = ++$current_color % $num_colors;
+                       $current_color = ($current_color + 1) % $num_colors;
                }
-               print "<tr class=\"$rev_color[$current_color]\">\n";
+               print "<tr id=\"l$lineno\" class=\"$rev_color[$current_color]\">\n";
                if ($group_size) {
                        print "<td class=\"sha1\"";
                        print " title=\"". esc_html($author) . ", $date\"";
@@ -4661,20 +4697,25 @@ sub git_blame {
                        print $cgi->a({-href => href(action=>"commit",
                                                     hash=>$full_rev,
                                                     file_name=>$file_name)},
-                                     esc_html($rev));
+                                     esc_html($short_rev));
                        print "</td>\n";
                }
-               open (my $dd, "-|", git_cmd(), "rev-parse", "$full_rev^")
-                       or die_error(500, "Open git-rev-parse failed");
-               my $parent_commit = <$dd>;
-               close $dd;
-               chomp($parent_commit);
+               my $parent_commit;
+               if (!exists $meta->{'parent'}) {
+                       open (my $dd, "-|", git_cmd(), "rev-parse", "$full_rev^")
+                               or die_error(500, "Open git-rev-parse failed");
+                       $parent_commit = <$dd>;
+                       close $dd;
+                       chomp($parent_commit);
+                       $meta->{'parent'} = $parent_commit;
+               } else {
+                       $parent_commit = $meta->{'parent'};
+               }
                my $blamed = href(action => 'blame',
                                  file_name => $meta->{'filename'},
                                  hash_base => $parent_commit);
                print "<td class=\"linenr\">";
                print $cgi->a({ -href => "$blamed#l$orig_lineno",
-                               -id => "l$lineno",
                                -class => "linenr" },
                              esc_html($lineno));
                print "</td>";
@@ -4685,6 +4726,8 @@ sub git_blame {
        print "</div>";
        close $fd
                or print "Reading blob failed\n";
+
+       # page footer
        git_footer_html();
 }
 
@@ -5015,6 +5058,15 @@ sub git_log {
 
        my $paging_nav = format_paging_nav('log', $hash, $head, $page, $#commitlist >= 100);
 
+       my ($patch_max) = gitweb_get_feature('patches');
+       if ($patch_max) {
+               if ($patch_max < 0 || @commitlist <= $patch_max) {
+                       $paging_nav .= " &sdot; " .
+                               $cgi->a({-href => href(action=>"patches", -replay=>1)},
+                                       "patches");
+               }
+       }
+
        git_header_html();
        git_print_page_nav('log','', $hash,undef,undef, $paging_nav);
 
@@ -5094,6 +5146,11 @@ sub git_commit {
                        } @$parents ) .
                        ')';
        }
+       if (gitweb_check_feature('patches')) {
+               $formats_nav .= " | " .
+                       $cgi->a({-href => href(action=>"patch", -replay=>1)},
+                               "patch");
+       }
 
        if (!defined $parent) {
                $parent = "--root";
@@ -5370,7 +5427,14 @@ sub git_blobdiff_plain {
 }
 
 sub git_commitdiff {
-       my $format = shift || 'html';
+       my %params = @_;
+       my $format = $params{-format} || 'html';
+
+       my ($patch_max) = gitweb_get_feature('patches');
+       if ($format eq 'patch') {
+               die_error(403, "Patch view not allowed") unless $patch_max;
+       }
+
        $hash ||= $hash_base || "HEAD";
        my %co = parse_commit($hash)
            or die_error(404, "Unknown commit object");
@@ -5385,6 +5449,11 @@ sub git_commitdiff {
                $formats_nav =
                        $cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)},
                                "raw");
+               if ($patch_max) {
+                       $formats_nav .= " | " .
+                               $cgi->a({-href => href(action=>"patch", -replay=>1)},
+                                       "patch");
+               }
 
                if (defined $hash_parent &&
                    $hash_parent ne '-c' && $hash_parent ne '--cc') {
@@ -5468,7 +5537,31 @@ sub git_commitdiff {
                open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
                        '-p', $hash_parent_param, $hash, "--"
                        or die_error(500, "Open git-diff-tree failed");
-
+       } elsif ($format eq 'patch') {
+               # For commit ranges, we limit the output to the number of
+               # patches specified in the 'patches' feature.
+               # For single commits, we limit the output to a single patch,
+               # diverging from the git-format-patch default.
+               my @commit_spec = ();
+               if ($hash_parent) {
+                       if ($patch_max > 0) {
+                               push @commit_spec, "-$patch_max";
+                       }
+                       push @commit_spec, '-n', "$hash_parent..$hash";
+               } else {
+                       if ($params{-single}) {
+                               push @commit_spec, '-1';
+                       } else {
+                               if ($patch_max > 0) {
+                                       push @commit_spec, "-$patch_max";
+                               }
+                               push @commit_spec, "-n";
+                       }
+                       push @commit_spec, '--root', $hash;
+               }
+               open $fd, "-|", git_cmd(), "format-patch", '--encoding=utf8',
+                       '--stdout', @commit_spec
+                       or die_error(500, "Open git-format-patch failed");
        } else {
                die_error(400, "Unknown commitdiff format");
        }
@@ -5517,6 +5610,14 @@ sub git_commitdiff {
                        print to_utf8($line) . "\n";
                }
                print "---\n\n";
+       } elsif ($format eq 'patch') {
+               my $filename = basename($project) . "-$hash.patch";
+
+               print $cgi->header(
+                       -type => 'text/plain',
+                       -charset => 'utf-8',
+                       -expires => $expires,
+                       -content_disposition => 'inline; filename="' . "$filename" . '"');
        }
 
        # write patch
@@ -5538,11 +5639,25 @@ sub git_commitdiff {
                print <$fd>;
                close $fd
                        or print "Reading git-diff-tree failed\n";
+       } elsif ($format eq 'patch') {
+               local $/ = undef;
+               print <$fd>;
+               close $fd
+                       or print "Reading git-format-patch failed\n";
        }
 }
 
 sub git_commitdiff_plain {
-       git_commitdiff('plain');
+       git_commitdiff(-format => 'plain');
+}
+
+# format-patch-style patches
+sub git_patch {
+       git_commitdiff(-format => 'patch', -single=> 1);
+}
+
+sub git_patches {
+       git_commitdiff(-format => 'patch');
 }
 
 sub git_history {
@@ -5895,6 +6010,14 @@ sub git_shortlog {
                        $cgi->a({-href => href(-replay=>1, page=>$page+1),
                                 -accesskey => "n", -title => "Alt-n"}, "next");
        }
+       my $patch_max = gitweb_check_feature('patches');
+       if ($patch_max) {
+               if ($patch_max < 0 || @commitlist <= $patch_max) {
+                       $paging_nav .= " &sdot; " .
+                               $cgi->a({-href => href(action=>"patches", -replay=>1)},
+                                       "patches");
+               }
+       }
 
        git_header_html();
        git_print_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav);
@@ -5932,7 +6055,25 @@ sub git_feed {
        }
        if (defined($commitlist[0])) {
                %latest_commit = %{$commitlist[0]};
-               %latest_date   = parse_date($latest_commit{'author_epoch'});
+               my $latest_epoch = $latest_commit{'committer_epoch'};
+               %latest_date   = parse_date($latest_epoch);
+               my $if_modified = $cgi->http('IF_MODIFIED_SINCE');
+               if (defined $if_modified) {
+                       my $since;
+                       if (eval { require HTTP::Date; 1; }) {
+                               $since = HTTP::Date::str2time($if_modified);
+                       } elsif (eval { require Time::ParseDate; 1; }) {
+                               $since = Time::ParseDate::parsedate($if_modified, GMT => 1);
+                       }
+                       if (defined $since && $latest_epoch <= $since) {
+                               print $cgi->header(
+                                       -type => $content_type,
+                                       -charset => 'utf-8',
+                                       -last_modified => $latest_date{'rfc2822'},
+                                       -status => '304 Not Modified');
+                               return;
+                       }
+               }
                print $cgi->header(
                        -type => $content_type,
                        -charset => 'utf-8',
@@ -5991,7 +6132,24 @@ sub git_feed {
                print "<title>$title</title>\n" .
                      "<link>$alt_url</link>\n" .
                      "<description>$descr</description>\n" .
-                     "<language>en</language>\n";
+                     "<language>en</language>\n" .
+                     # project owner is responsible for 'editorial' content
+                     "<managingEditor>$owner</managingEditor>\n";
+               if (defined $logo || defined $favicon) {
+                       # prefer the logo to the favicon, since RSS
+                       # doesn't allow both
+                       my $img = esc_url($logo || $favicon);
+                       print "<image>\n" .
+                             "<url>$img</url>\n" .
+                             "<title>$title</title>\n" .
+                             "<link>$alt_url</link>\n" .
+                             "</image>\n";
+               }
+               if (%latest_date) {
+                       print "<pubDate>$latest_date{'rfc2822'}</pubDate>\n";
+                       print "<lastBuildDate>$latest_date{'rfc2822'}</lastBuildDate>\n";
+               }
+               print "<generator>gitweb v.$version/$git_version</generator>\n";
        } elsif ($format eq 'atom') {
                print <<XML;
 <feed xmlns="http://www.w3.org/2005/Atom">
@@ -6018,6 +6176,7 @@ sub git_feed {
                } else {
                        print "<updated>$latest_date{'iso-8601'}</updated>\n";
                }
+               print "<generator version='$version/$git_version'>gitweb</generator>\n";
        }
 
        # contents
@@ -6139,7 +6298,11 @@ sub git_atom {
 sub git_opml {
        my @list = git_get_projects_list();
 
-       print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
+       print $cgi->header(
+               -type => 'text/xml',
+               -charset => 'utf-8',
+               -content_disposition => 'inline; filename="opml.xml"');
+
        print <<XML;
 <?xml version="1.0" encoding="utf-8"?>
 <opml version="1.0">
@@ -6163,8 +6326,8 @@ sub git_opml {
                }
 
                my $path = esc_html(chop_str($proj{'path'}, 25, 5));
-               my $rss  = "$my_url?p=$proj{'path'};a=rss";
-               my $html = "$my_url?p=$proj{'path'};a=summary";
+               my $rss  = href('project' => $proj{'path'}, 'action' => 'rss', -full => 1);
+               my $html = href('project' => $proj{'path'}, 'action' => 'summary', -full => 1);
                print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n";
        }
        print <<XML;
diff --git a/grep.c b/grep.c
index 600f69f2fe2a0271f4bdf736f95f70c8f7381aa4..062b2b6f28f6332518240d2a474a7739735e1ecf 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -28,9 +28,25 @@ void append_grep_pattern(struct grep_opt *opt, const char *pat,
        p->next = NULL;
 }
 
+static int is_fixed(const char *s)
+{
+       while (*s && !is_regex_special(*s))
+               s++;
+       return !*s;
+}
+
 static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
 {
-       int err = regcomp(&p->regexp, p->pattern, opt->regflags);
+       int err;
+
+       if (opt->fixed || is_fixed(p->pattern))
+               p->fixed = 1;
+       if (opt->regflags & REG_ICASE)
+               p->fixed = 0;
+       if (p->fixed)
+               return;
+
+       err = regcomp(&p->regexp, p->pattern, opt->regflags);
        if (err) {
                char errbuf[1024];
                char where[1024];
@@ -159,8 +175,7 @@ void compile_grep_patterns(struct grep_opt *opt)
                case GREP_PATTERN: /* atom */
                case GREP_PATTERN_HEAD:
                case GREP_PATTERN_BODY:
-                       if (!opt->fixed)
-                               compile_regexp(p, opt);
+                       compile_regexp(p, opt);
                        break;
                default:
                        opt->extended = 1;
@@ -294,7 +309,6 @@ static struct {
 static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol, enum grep_context ctx)
 {
        int hit = 0;
-       int at_true_bol = 1;
        int saved_ch = 0;
        regmatch_t pmatch[10];
 
@@ -315,7 +329,7 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol
        }
 
  again:
-       if (!opt->fixed) {
+       if (!p->fixed) {
                regex_t *exp = &p->regexp;
                hit = !regexec(exp, bol, ARRAY_SIZE(pmatch),
                               pmatch, 0);
@@ -337,7 +351,7 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol
                 * either end of the line, or at word boundary
                 * (i.e. the next char must not be a word char).
                 */
-               if ( ((pmatch[0].rm_so == 0 && at_true_bol) ||
+               if ( ((pmatch[0].rm_so == 0) ||
                      !word_char(bol[pmatch[0].rm_so-1])) &&
                     ((pmatch[0].rm_eo == (eol-bol)) ||
                      !word_char(bol[pmatch[0].rm_eo])) )
@@ -349,10 +363,14 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol
                        /* There could be more than one match on the
                         * line, and the first match might not be
                         * strict word match.  But later ones could be!
+                        * Forward to the next possible start, i.e. the
+                        * next position following a non-word char.
                         */
                        bol = pmatch[0].rm_so + bol + 1;
-                       at_true_bol = 0;
-                       goto again;
+                       while (word_char(bol[-1]) && bol < eol)
+                               bol++;
+                       if (bol < eol)
+                               goto again;
                }
        }
        if (p->token == GREP_PATTERN_HEAD && saved_ch)
@@ -395,7 +413,7 @@ static int match_expr_eval(struct grep_opt *o,
                h |= match_expr_eval(o, x->u.binary.right, bol, eol, ctx, 1);
                break;
        default:
-               die("Unexpected node type (internal error) %d\n", x->node);
+               die("Unexpected node type (internal error) %d", x->node);
        }
        if (collect_hits)
                x->hit |= h;
diff --git a/grep.h b/grep.h
index 45a222d904b5898758c3c84f6ef1e0119c81f6b2..5102ce335d29811dd448e173f2e90e8d03b5f011 100644 (file)
--- a/grep.h
+++ b/grep.h
@@ -30,6 +30,7 @@ struct grep_pat {
        const char *pattern;
        enum grep_header_field field;
        regex_t regexp;
+       unsigned fixed:1;
 };
 
 enum grep_expr_node {
index 846e91a23126b747fbea8d9a8511f708c3d70e43..ebb3bedb074202a29e2356845012bd1ffae0dc19 100644 (file)
@@ -8,6 +8,7 @@
 #include "blob.h"
 #include "quote.h"
 #include "parse-options.h"
+#include "exec_cmd.h"
 
 static void hash_fd(int fd, const char *type, int write_object, const char *path)
 {
@@ -81,7 +82,7 @@ int main(int argc, const char **argv)
 
        type = blob_type;
 
-       git_config(git_default_config, NULL);
+       git_extract_argv0_path(argv[0]);
 
        argc = parse_options(argc, argv, hash_object_options, hash_object_usage, 0);
 
@@ -92,6 +93,8 @@ int main(int argc, const char **argv)
                        vpath = prefix_filename(prefix, prefix_length, vpath);
        }
 
+       git_config(git_default_config, NULL);
+
        if (stdin_paths) {
                if (hashstdin)
                        errstr = "Can't use --stdin-paths with --stdin";
index cb5bf95a736c571259aef51884cc20e4169bf707..30d2d340418f7f40b77823b1b58b307985347bdf 100644 (file)
@@ -10,6 +10,7 @@
 #include "exec_cmd.h"
 #include "remote.h"
 #include "list-objects.h"
+#include "sigchain.h"
 
 #include <expat.h>
 
@@ -152,6 +153,7 @@ struct remote_lock
        char *url;
        char *owner;
        char *token;
+       char tmpfile_suffix[41];
        time_t start_time;
        long timeout;
        int refreshing;
@@ -177,6 +179,47 @@ struct remote_ls_ctx
        struct remote_ls_ctx *parent;
 };
 
+/* get_dav_token_headers options */
+enum dav_header_flag {
+       DAV_HEADER_IF = (1u << 0),
+       DAV_HEADER_LOCK = (1u << 1),
+       DAV_HEADER_TIMEOUT = (1u << 2)
+};
+
+static struct curl_slist *get_dav_token_headers(struct remote_lock *lock, enum dav_header_flag options)
+{
+       struct strbuf buf = STRBUF_INIT;
+       struct curl_slist *dav_headers = NULL;
+
+       if (options & DAV_HEADER_IF) {
+               strbuf_addf(&buf, "If: (<%s>)", lock->token);
+               dav_headers = curl_slist_append(dav_headers, buf.buf);
+               strbuf_reset(&buf);
+       }
+       if (options & DAV_HEADER_LOCK) {
+               strbuf_addf(&buf, "Lock-Token: <%s>", lock->token);
+               dav_headers = curl_slist_append(dav_headers, buf.buf);
+               strbuf_reset(&buf);
+       }
+       if (options & DAV_HEADER_TIMEOUT) {
+               strbuf_addf(&buf, "Timeout: Second-%ld", lock->timeout);
+               dav_headers = curl_slist_append(dav_headers, buf.buf);
+               strbuf_reset(&buf);
+       }
+       strbuf_release(&buf);
+
+       return dav_headers;
+}
+
+static void append_remote_object_url(struct strbuf *buf, const char *url,
+                                    const char *hex,
+                                    int only_two_digit_prefix)
+{
+       strbuf_addf(buf, "%sobjects/%.*s/", url, 2, hex);
+       if (!only_two_digit_prefix)
+               strbuf_addf(buf, "%s", hex+2);
+}
+
 static void finish_request(struct transfer_request *request);
 static void release_request(struct transfer_request *request);
 
@@ -189,6 +232,15 @@ static void process_response(void *callback_data)
 }
 
 #ifdef USE_CURL_MULTI
+
+static char *get_remote_object_url(const char *url, const char *hex,
+                                  int only_two_digit_prefix)
+{
+       struct strbuf buf = STRBUF_INIT;
+       append_remote_object_url(&buf, url, hex, only_two_digit_prefix);
+       return strbuf_detach(&buf, NULL);
+}
+
 static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
                               void *data)
 {
@@ -223,7 +275,6 @@ static void start_fetch_loose(struct transfer_request *request)
        char *filename;
        char prevfile[PATH_MAX];
        char *url;
-       char *posn;
        int prevlocal;
        unsigned char prev_buf[PREV_BUF_SIZE];
        ssize_t prev_read = 0;
@@ -273,17 +324,8 @@ static void start_fetch_loose(struct transfer_request *request)
 
        git_SHA1_Init(&request->c);
 
-       url = xmalloc(strlen(remote->url) + 50);
-       request->url = xmalloc(strlen(remote->url) + 50);
-       strcpy(url, remote->url);
-       posn = url + strlen(remote->url);
-       strcpy(posn, "objects/");
-       posn += 8;
-       memcpy(posn, hex, 2);
-       posn += 2;
-       *(posn++) = '/';
-       strcpy(posn, hex + 2);
-       strcpy(request->url, url);
+       url = get_remote_object_url(remote->url, hex, 0);
+       request->url = xstrdup(url);
 
        /* If a previous temp file is present, process what was already
           fetched. */
@@ -356,16 +398,8 @@ static void start_mkcol(struct transfer_request *request)
 {
        char *hex = sha1_to_hex(request->obj->sha1);
        struct active_request_slot *slot;
-       char *posn;
 
-       request->url = xmalloc(strlen(remote->url) + 13);
-       strcpy(request->url, remote->url);
-       posn = request->url + strlen(remote->url);
-       strcpy(posn, "objects/");
-       posn += 8;
-       memcpy(posn, hex, 2);
-       posn += 2;
-       strcpy(posn, "/");
+       request->url = get_remote_object_url(remote->url, hex, 1);
 
        slot = get_active_slot();
        slot->callback_func = process_response;
@@ -480,7 +514,7 @@ static void start_put(struct transfer_request *request)
 {
        char *hex = sha1_to_hex(request->obj->sha1);
        struct active_request_slot *slot;
-       char *posn;
+       struct strbuf buf = STRBUF_INIT;
        enum object_type type;
        char hdr[50];
        void *unpacked;
@@ -519,21 +553,13 @@ static void start_put(struct transfer_request *request)
 
        request->buffer.buf.len = stream.total_out;
 
-       request->url = xmalloc(strlen(remote->url) +
-                              strlen(request->lock->token) + 51);
-       strcpy(request->url, remote->url);
-       posn = request->url + strlen(remote->url);
-       strcpy(posn, "objects/");
-       posn += 8;
-       memcpy(posn, hex, 2);
-       posn += 2;
-       *(posn++) = '/';
-       strcpy(posn, hex + 2);
-       request->dest = xmalloc(strlen(request->url) + 14);
-       sprintf(request->dest, "Destination: %s", request->url);
-       posn += 38;
-       *(posn++) = '_';
-       strcpy(posn, request->lock->token);
+       strbuf_addstr(&buf, "Destination: ");
+       append_remote_object_url(&buf, remote->url, hex, 0);
+       request->dest = strbuf_detach(&buf, NULL);
+
+       append_remote_object_url(&buf, remote->url, hex, 0);
+       strbuf_add(&buf, request->lock->tmpfile_suffix, 41);
+       request->url = strbuf_detach(&buf, NULL);
 
        slot = get_active_slot();
        slot->callback_func = process_response;
@@ -588,18 +614,12 @@ static int refresh_lock(struct remote_lock *lock)
 {
        struct active_request_slot *slot;
        struct slot_results results;
-       char *if_header;
-       char timeout_header[25];
-       struct curl_slist *dav_headers = NULL;
+       struct curl_slist *dav_headers;
        int rc = 0;
 
        lock->refreshing = 1;
 
-       if_header = xmalloc(strlen(lock->token) + 25);
-       sprintf(if_header, "If: (<%s>)", lock->token);
-       sprintf(timeout_header, "Timeout: Second-%ld", lock->timeout);
-       dav_headers = curl_slist_append(dav_headers, if_header);
-       dav_headers = curl_slist_append(dav_headers, timeout_header);
+       dav_headers = get_dav_token_headers(lock, DAV_HEADER_IF | DAV_HEADER_TIMEOUT);
 
        slot = get_active_slot();
        slot->results = &results;
@@ -622,7 +642,6 @@ static int refresh_lock(struct remote_lock *lock)
 
        lock->refreshing = 0;
        curl_slist_free_all(dav_headers);
-       free(if_header);
 
        return rc;
 }
@@ -1111,6 +1130,8 @@ static void handle_lockprop_ctx(struct xml_ctx *ctx, int tag_closed)
 static void handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed)
 {
        struct remote_lock *lock = (struct remote_lock *)ctx->userData;
+       git_SHA_CTX sha_ctx;
+       unsigned char lock_token_sha1[20];
 
        if (tag_closed && ctx->cdata) {
                if (!strcmp(ctx->name, DAV_ACTIVELOCK_OWNER)) {
@@ -1123,6 +1144,13 @@ static void handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed)
                } else if (!strcmp(ctx->name, DAV_ACTIVELOCK_TOKEN)) {
                        lock->token = xmalloc(strlen(ctx->cdata) + 1);
                        strcpy(lock->token, ctx->cdata);
+
+                       git_SHA1_Init(&sha_ctx);
+                       git_SHA1_Update(&sha_ctx, lock->token, strlen(lock->token));
+                       git_SHA1_Final(lock_token_sha1, &sha_ctx);
+
+                       lock->tmpfile_suffix[0] = '_';
+                       memcpy(lock->tmpfile_suffix + 1, sha1_to_hex(lock_token_sha1), 40);
                }
        }
 }
@@ -1303,14 +1331,10 @@ static int unlock_remote(struct remote_lock *lock)
        struct active_request_slot *slot;
        struct slot_results results;
        struct remote_lock *prev = remote->locks;
-       char *lock_token_header;
-       struct curl_slist *dav_headers = NULL;
+       struct curl_slist *dav_headers;
        int rc = 0;
 
-       lock_token_header = xmalloc(strlen(lock->token) + 31);
-       sprintf(lock_token_header, "Lock-Token: <%s>",
-               lock->token);
-       dav_headers = curl_slist_append(dav_headers, lock_token_header);
+       dav_headers = get_dav_token_headers(lock, DAV_HEADER_LOCK);
 
        slot = get_active_slot();
        slot->results = &results;
@@ -1331,7 +1355,6 @@ static int unlock_remote(struct remote_lock *lock)
        }
 
        curl_slist_free_all(dav_headers);
-       free(lock_token_header);
 
        if (remote->locks == lock) {
                remote->locks = lock->next;
@@ -1364,7 +1387,7 @@ static void remove_locks(void)
 static void remove_locks_on_signal(int signo)
 {
        remove_locks();
-       signal(signo, SIG_DFL);
+       sigchain_pop(signo);
        raise(signo);
 }
 
@@ -1731,13 +1754,10 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock)
 {
        struct active_request_slot *slot;
        struct slot_results results;
-       char *if_header;
        struct buffer out_buffer = { STRBUF_INIT, 0 };
-       struct curl_slist *dav_headers = NULL;
+       struct curl_slist *dav_headers;
 
-       if_header = xmalloc(strlen(lock->token) + 25);
-       sprintf(if_header, "If: (<%s>)", lock->token);
-       dav_headers = curl_slist_append(dav_headers, if_header);
+       dav_headers = get_dav_token_headers(lock, DAV_HEADER_IF);
 
        strbuf_addf(&out_buffer.buf, "%s\n", sha1_to_hex(sha1));
 
@@ -1756,7 +1776,6 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock)
        if (start_active_slot(slot)) {
                run_active_slot(slot);
                strbuf_release(&out_buffer.buf);
-               free(if_header);
                if (results.curl_result != CURLE_OK) {
                        fprintf(stderr,
                                "PUT error: curl result=%d, HTTP code=%ld\n",
@@ -1766,7 +1785,6 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock)
                }
        } else {
                strbuf_release(&out_buffer.buf);
-               free(if_header);
                fprintf(stderr, "Unable to start PUT request\n");
                return 0;
        }
@@ -1948,15 +1966,12 @@ static void update_remote_info_refs(struct remote_lock *lock)
        struct buffer buffer = { STRBUF_INIT, 0 };
        struct active_request_slot *slot;
        struct slot_results results;
-       char *if_header;
-       struct curl_slist *dav_headers = NULL;
+       struct curl_slist *dav_headers;
 
        remote_ls("refs/", (PROCESS_FILES | RECURSIVE),
                  add_remote_info_ref, &buffer.buf);
        if (!aborted) {
-               if_header = xmalloc(strlen(lock->token) + 25);
-               sprintf(if_header, "If: (<%s>)", lock->token);
-               dav_headers = curl_slist_append(dav_headers, if_header);
+               dav_headers = get_dav_token_headers(lock, DAV_HEADER_IF);
 
                slot = get_active_slot();
                slot->results = &results;
@@ -1978,7 +1993,6 @@ static void update_remote_info_refs(struct remote_lock *lock)
                                        results.curl_result, results.http_code);
                        }
                }
-               free(if_header);
        }
        strbuf_release(&buffer.buf);
 }
@@ -2184,6 +2198,8 @@ int main(int argc, char **argv)
        struct ref *ref;
        char *rewritten_url = NULL;
 
+       git_extract_argv0_path(argv[0]);
+
        setup_git_directory();
 
        remote = xcalloc(sizeof(*remote), 1);
@@ -2266,10 +2282,7 @@ int main(int argc, char **argv)
                goto cleanup;
        }
 
-       signal(SIGINT, remove_locks_on_signal);
-       signal(SIGHUP, remove_locks_on_signal);
-       signal(SIGQUIT, remove_locks_on_signal);
-       signal(SIGTERM, remove_locks_on_signal);
+       sigchain_push_common(remove_locks_on_signal);
 
        /* Check whether the remote has server info files */
        remote->can_update_info_refs = 0;
diff --git a/http.c b/http.c
index ee58799ca8cd433218ca7d40946580732a9010e7..56f18f1b03984ac3a81cb357751e2ae4fce269e0 100644 (file)
--- a/http.c
+++ b/http.c
@@ -573,31 +573,21 @@ static inline int hex(int v)
 
 static char *quote_ref_url(const char *base, const char *ref)
 {
+       struct strbuf buf = STRBUF_INIT;
        const char *cp;
-       char *dp, *qref;
-       int len, baselen, ch;
+       int ch;
 
-       baselen = strlen(base);
-       len = baselen + 2; /* '/' after base and terminating NUL */
-       for (cp = ref; (ch = *cp) != 0; cp++, len++)
+       strbuf_addstr(&buf, base);
+       if (buf.len && buf.buf[buf.len - 1] != '/' && *ref != '/')
+               strbuf_addstr(&buf, "/");
+
+       for (cp = ref; (ch = *cp) != 0; cp++)
                if (needs_quote(ch))
-                       len += 2; /* extra two hex plus replacement % */
-       qref = xmalloc(len);
-       memcpy(qref, base, baselen);
-       dp = qref + baselen;
-       *(dp++) = '/';
-       for (cp = ref; (ch = *cp) != 0; cp++) {
-               if (needs_quote(ch)) {
-                       *dp++ = '%';
-                       *dp++ = hex((ch >> 4) & 0xF);
-                       *dp++ = hex(ch & 0xF);
-               }
+                       strbuf_addf(&buf, "%%%02x", ch);
                else
-                       *dp++ = ch;
-       }
-       *dp = 0;
+                       strbuf_addch(&buf, *cp);
 
-       return qref;
+       return strbuf_detach(&buf, NULL);
 }
 
 int http_fetch_ref(const char *base, struct ref *ref)
index 3703dbd1af65c30806a98f0d2d3dc14b8b0e9798..cb518eb613fdce7482a243ad9075df64ab1a4321 100644 (file)
@@ -23,6 +23,7 @@
  */
 
 #include "cache.h"
+#include "exec_cmd.h"
 #ifdef NO_OPENSSL
 typedef void *SSL;
 #endif
@@ -115,9 +116,9 @@ static int nfvasprintf(char **strp, const char *fmt, va_list ap)
 
        len = vsnprintf(tmp, sizeof(tmp), fmt, ap);
        if (len < 0)
-               die("Fatal: Out of memory\n");
+               die("Fatal: Out of memory");
        if (len >= sizeof(tmp))
-               die("imap command overflow !\n");
+               die("imap command overflow!");
        *strp = xmemdupz(tmp, len);
        return len;
 }
@@ -134,6 +135,7 @@ struct imap_server_conf {
        char *pass;
        int use_ssl;
        int ssl_verify;
+       int use_html;
 };
 
 struct imap_store_conf {
@@ -482,7 +484,7 @@ static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
 
        va_start(va, fmt);
        if (blen <= 0 || (unsigned)(ret = vsnprintf(buf, blen, fmt, va)) >= (unsigned)blen)
-               die("Fatal: buffer too small. Please report a bug.\n");
+               die("Fatal: buffer too small. Please report a bug.");
        va_end(va);
        return ret;
 }
@@ -1262,6 +1264,53 @@ static int imap_store_msg(struct store *gctx, struct msg_data *data, int *uid)
        return DRV_OK;
 }
 
+static void encode_html_chars(struct strbuf *p)
+{
+       int i;
+       for (i = 0; i < p->len; i++) {
+               if (p->buf[i] == '&')
+                       strbuf_splice(p, i, 1, "&amp;", 5);
+               if (p->buf[i] == '<')
+                       strbuf_splice(p, i, 1, "&lt;", 4);
+               if (p->buf[i] == '>')
+                       strbuf_splice(p, i, 1, "&gt;", 4);
+               if (p->buf[i] == '"')
+                       strbuf_splice(p, i, 1, "&quot;", 6);
+       }
+}
+static void wrap_in_html(struct msg_data *msg)
+{
+       struct strbuf buf = STRBUF_INIT;
+       struct strbuf **lines;
+       struct strbuf **p;
+       static char *content_type = "Content-Type: text/html;\n";
+       static char *pre_open = "<pre>\n";
+       static char *pre_close = "</pre>\n";
+       int added_header = 0;
+
+       strbuf_attach(&buf, msg->data, msg->len, msg->len);
+       lines = strbuf_split(&buf, '\n');
+       strbuf_release(&buf);
+       for (p = lines; *p; p++) {
+               if (! added_header) {
+                       if ((*p)->len == 1 && *((*p)->buf) == '\n') {
+                               strbuf_addstr(&buf, content_type);
+                               strbuf_addbuf(&buf, *p);
+                               strbuf_addstr(&buf, pre_open);
+                               added_header = 1;
+                               continue;
+                       }
+               }
+               else
+                       encode_html_chars(*p);
+               strbuf_addbuf(&buf, *p);
+       }
+       strbuf_addstr(&buf, pre_close);
+       strbuf_list_free(lines);
+       msg->len  = buf.len;
+       msg->data = strbuf_detach(&buf, NULL);
+}
+
 #define CHUNKSIZE 0x1000
 
 static int read_message(FILE *f, struct msg_data *msg)
@@ -1338,6 +1387,7 @@ static struct imap_server_conf server = {
        NULL,   /* pass */
        0,      /* use_ssl */
        1,      /* ssl_verify */
+       0,      /* use_html */
 };
 
 static char *imap_folder;
@@ -1376,6 +1426,8 @@ static int git_imap_config(const char *key, const char *val, void *cb)
                server.tunnel = xstrdup(val);
        else if (!strcmp("sslverify", key))
                server.ssl_verify = git_config_bool(key, val);
+       else if (!strcmp("preformattedHTML", key))
+               server.use_html = git_config_bool(key, val);
        return 0;
 }
 
@@ -1389,6 +1441,8 @@ int main(int argc, char **argv)
        int total, n = 0;
        int nongit_ok;
 
+       git_extract_argv0_path(argv[0]);
+
        /* init the random number generator */
        arc4_init();
 
@@ -1436,6 +1490,8 @@ int main(int argc, char **argv)
                fprintf(stderr, "%4u%% (%d/%d) done\r", percent, n, total);
                if (!split_msg(&all_msgs, &msg, &ofs))
                        break;
+               if (server.use_html)
+                       wrap_in_html(&msg);
                r = imap_store_msg(ctx, &msg, &uid);
                if (r != DRV_OK)
                        break;
index c0a3d97a1276265d3f253dc396ded9c54d2caf5c..7fee8725333860dbbd13d8de5ae7baf1ef33976d 100644 (file)
@@ -8,6 +8,7 @@
 #include "tree.h"
 #include "progress.h"
 #include "fsck.h"
+#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>] }";
@@ -171,14 +172,13 @@ static char *open_pack_file(char *pack_name)
                input_fd = 0;
                if (!pack_name) {
                        static char tmpfile[PATH_MAX];
-                       snprintf(tmpfile, sizeof(tmpfile),
-                                "%s/pack/tmp_pack_XXXXXX", get_object_directory());
-                       output_fd = xmkstemp(tmpfile);
+                       output_fd = odb_mkstemp(tmpfile, sizeof(tmpfile),
+                                               "pack/tmp_pack_XXXXXX");
                        pack_name = xstrdup(tmpfile);
                } else
                        output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
                if (output_fd < 0)
-                       die("unable to create %s: %s\n", pack_name, strerror(errno));
+                       die("unable to create %s: %s", pack_name, strerror(errno));
                pack_fd = output_fd;
        } else {
                input_fd = open(pack_name, O_RDONLY);
@@ -793,22 +793,24 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
 
        if (keep_msg) {
                int keep_fd, keep_msg_len = strlen(keep_msg);
-               if (!keep_name) {
-                       snprintf(name, sizeof(name), "%s/pack/pack-%s.keep",
-                                get_object_directory(), sha1_to_hex(sha1));
-                       keep_name = name;
-               }
-               keep_fd = open(keep_name, O_RDWR|O_CREAT|O_EXCL, 0600);
+
+               if (!keep_name)
+                       keep_fd = odb_pack_keep(name, sizeof(name), sha1);
+               else
+                       keep_fd = open(keep_name, O_RDWR|O_CREAT|O_EXCL, 0600);
+
                if (keep_fd < 0) {
                        if (errno != EEXIST)
-                               die("cannot write keep file");
+                               die("cannot write keep file '%s' (%s)",
+                                   keep_name, strerror(errno));
                } else {
                        if (keep_msg_len > 0) {
                                write_or_die(keep_fd, keep_msg, keep_msg_len);
                                write_or_die(keep_fd, "\n", 1);
                        }
                        if (close(keep_fd) != 0)
-                               die("cannot write keep file");
+                               die("cannot close written keep file '%s' (%s)",
+                                   keep_name, strerror(errno));
                        report = "keep";
                }
        }
@@ -880,6 +882,8 @@ int main(int argc, char **argv)
        struct pack_idx_entry **idx_objects;
        unsigned char pack_sha1[20];
 
+       git_extract_argv0_path(argv[0]);
+
        /*
         * We wish to read the repository's config file if any, and
         * for that it is necessary to call setup_git_directory_gently().
index 8e556ff8c9671864db44dc8b6f4a861bd35142a6..3dbb2d1ff9407a7417be294adedc5312e7421a96 100644 (file)
@@ -2,6 +2,7 @@
  * Copyright (c) 2005, Junio C Hamano
  */
 #include "cache.h"
+#include "sigchain.h"
 
 static struct lock_file *lock_file_list;
 static const char *alternate_index_output;
@@ -24,7 +25,7 @@ static void remove_lock_file(void)
 static void remove_lock_file_on_signal(int signo)
 {
        remove_lock_file();
-       signal(signo, SIG_DFL);
+       sigchain_pop(signo);
        raise(signo);
 }
 
@@ -136,11 +137,7 @@ static int lock_file(struct lock_file *lk, const char *path, int flags)
        lk->fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666);
        if (0 <= lk->fd) {
                if (!lock_file_list) {
-                       signal(SIGINT, remove_lock_file_on_signal);
-                       signal(SIGHUP, remove_lock_file_on_signal);
-                       signal(SIGTERM, remove_lock_file_on_signal);
-                       signal(SIGQUIT, remove_lock_file_on_signal);
-                       signal(SIGPIPE, remove_lock_file_on_signal);
+                       sigchain_push_common(remove_lock_file_on_signal);
                        atexit(remove_lock_file);
                }
                lk->owner = getpid();
@@ -161,7 +158,7 @@ static int lock_file(struct lock_file *lk, const char *path, int flags)
 
 NORETURN void unable_to_lock_index_die(const char *path, int err)
 {
-       if (errno == EEXIST) {
+       if (err == EEXIST) {
                die("Unable to create '%s.lock': %s.\n\n"
                    "If no other git process is currently running, this probably means a\n"
                    "git process crashed in this repository earlier. Make sure no other git\n"
@@ -187,7 +184,7 @@ int hold_lock_file_for_append(struct lock_file *lk, const char *path, int flags)
        fd = lock_file(lk, path, flags);
        if (fd < 0) {
                if (flags & LOCK_DIE_ON_ERROR)
-                       die("unable to create '%s.lock': %s", path, strerror(errno));
+                       unable_to_lock_index_die(path, errno);
                return fd;
        }
 
index 194ddb13da09668334cd9e1f6eeef517c3346ee9..9565c184db681ac1de905d33dbaf175ec61c48fc 100644 (file)
@@ -6,6 +6,7 @@
 #include "log-tree.h"
 #include "reflog-walk.h"
 #include "refs.h"
+#include "string-list.h"
 
 struct decoration name_decoration = { "object names" };
 
@@ -48,7 +49,7 @@ static void show_parents(struct commit *commit, int abbrev)
        struct commit_list *p;
        for (p = commit->parents; p ; p = p->next) {
                struct commit *parent = p->item;
-               printf(" %s", diff_unique_abbrev(parent->object.sha1, abbrev));
+               printf(" %s", find_unique_abbrev(parent->object.sha1, abbrev));
        }
 }
 
@@ -79,18 +80,18 @@ void show_decorations(struct rev_info *opt, struct commit *commit)
  */
 static int detect_any_signoff(char *letter, int size)
 {
-       char ch, *cp;
+       char *cp;
        int seen_colon = 0;
        int seen_at = 0;
        int seen_name = 0;
        int seen_head = 0;
 
        cp = letter + size;
-       while (letter <= --cp && (ch = *cp) == '\n')
+       while (letter <= --cp && *cp == '\n')
                continue;
 
        while (letter <= cp) {
-               ch = *cp--;
+               char ch = *cp--;
                if (ch == '\n')
                        break;
 
@@ -211,9 +212,13 @@ void log_write_email_headers(struct rev_info *opt, const char *name,
                printf("Message-Id: <%s>\n", opt->message_id);
                graph_show_oneline(opt->graph);
        }
-       if (opt->ref_message_id) {
-               printf("In-Reply-To: <%s>\nReferences: <%s>\n",
-                      opt->ref_message_id, opt->ref_message_id);
+       if (opt->ref_message_ids && opt->ref_message_ids->nr > 0) {
+               int i, n;
+               n = opt->ref_message_ids->nr;
+               printf("In-Reply-To: <%s>\n", opt->ref_message_ids->items[n-1].string);
+               for (i = 0; i < n; i++)
+                       printf("%s<%s>\n", (i > 0 ? "\t" : "References: "),
+                              opt->ref_message_ids->items[i].string);
                graph_show_oneline(opt->graph);
        }
        if (opt->mime_boundary) {
@@ -280,7 +285,7 @@ void show_log(struct rev_info *opt)
                                        putchar('>');
                        }
                }
-               fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
+               fputs(find_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
                if (opt->print_parents)
                        show_parents(commit, abbrev_commit);
                show_decorations(opt, commit);
@@ -348,13 +353,13 @@ void show_log(struct rev_info *opt)
                                        putchar('>');
                        }
                }
-               fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit),
+               fputs(find_unique_abbrev(commit->object.sha1, abbrev_commit),
                      stdout);
                if (opt->print_parents)
                        show_parents(commit, abbrev_commit);
                if (parent)
                        printf(" (from %s)",
-                              diff_unique_abbrev(parent->object.sha1,
+                              find_unique_abbrev(parent->object.sha1,
                                                  abbrev_commit));
                show_decorations(opt, commit);
                printf("%s", diff_get_color_opt(&opt->diffopt, DIFF_RESET));
index 88fc6f394684436967002ca477eac1e084537348..f12bb45a3f734e48f003a7a2943d168c26778603 100644 (file)
--- a/mailmap.c
+++ b/mailmap.c
 #include "string-list.h"
 #include "mailmap.h"
 
-int read_mailmap(struct string_list *map, const char *filename, char **repo_abbrev)
+#define DEBUG_MAILMAP 0
+#if DEBUG_MAILMAP
+#define debug_mm(...) fprintf(stderr, __VA_ARGS__)
+#else
+static inline void debug_mm(const char *format, ...) {}
+#endif
+
+const char *git_mailmap_file;
+
+struct mailmap_info {
+       char *name;
+       char *email;
+};
+
+struct mailmap_entry {
+       /* name and email for the simple mail-only case */
+       char *name;
+       char *email;
+
+       /* name and email for the complex mail and name matching case */
+       struct string_list namemap;
+};
+
+static void free_mailmap_info(void *p, const char *s)
+{
+       struct mailmap_info *mi = (struct mailmap_info *)p;
+       debug_mm("mailmap: -- complex: '%s' -> '%s' <%s>\n", s, mi->name, mi->email);
+       free(mi->name);
+       free(mi->email);
+}
+
+static void free_mailmap_entry(void *p, const char *s)
+{
+       struct mailmap_entry *me = (struct mailmap_entry *)p;
+       debug_mm("mailmap: removing entries for <%s>, with %d sub-entries\n", s, me->namemap.nr);
+       debug_mm("mailmap: - simple: '%s' <%s>\n", me->name, me->email);
+       free(me->name);
+       free(me->email);
+
+       me->namemap.strdup_strings = 1;
+       string_list_clear_func(&me->namemap, free_mailmap_info);
+}
+
+static void add_mapping(struct string_list *map,
+                       char *new_name, char *new_email, char *old_name, char *old_email)
+{
+       struct mailmap_entry *me;
+       int index;
+       if (old_email == NULL) {
+               old_email = new_email;
+               new_email = NULL;
+       }
+
+       if ((index = string_list_find_insert_index(map, old_email, 1)) < 0) {
+               /* mailmap entry exists, invert index value */
+               index = -1 - index;
+       } else {
+               /* create mailmap entry */
+               struct string_list_item *item = string_list_insert_at_index(index, old_email, map);
+               item->util = xmalloc(sizeof(struct mailmap_entry));
+               memset(item->util, 0, sizeof(struct mailmap_entry));
+               ((struct mailmap_entry *)item->util)->namemap.strdup_strings = 1;
+       }
+       me = (struct mailmap_entry *)map->items[index].util;
+
+       if (old_name == NULL) {
+               debug_mm("mailmap: adding (simple) entry for %s at index %d\n", old_email, index);
+               /* Replace current name and new email for simple entry */
+               free(me->name);
+               free(me->email);
+               if (new_name)
+                       me->name = xstrdup(new_name);
+               if (new_email)
+                       me->email = xstrdup(new_email);
+       } else {
+               struct mailmap_info *mi = xmalloc(sizeof(struct mailmap_info));
+               debug_mm("mailmap: adding (complex) entry for %s at index %d\n", old_email, index);
+               if (new_name)
+                       mi->name = xstrdup(new_name);
+               if (new_email)
+                       mi->email = xstrdup(new_email);
+               string_list_insert(old_name, &me->namemap)->util = mi;
+       }
+
+       debug_mm("mailmap:  '%s' <%s> -> '%s' <%s>\n",
+                old_name, old_email, new_name, new_email);
+}
+
+static char *parse_name_and_email(char *buffer, char **name, char **email)
+{
+       char *left, *right, *nstart, *nend;
+       *name = *email = 0;
+
+       if ((left = strchr(buffer, '<')) == NULL)
+               return NULL;
+       if ((right = strchr(left+1, '>')) == NULL)
+               return NULL;
+       if (left+1 == right)
+               return NULL;
+
+       /* remove whitespace from beginning and end of name */
+       nstart = buffer;
+       while (isspace(*nstart) && nstart < left)
+               ++nstart;
+       nend = left-1;
+       while (isspace(*nend) && nend > nstart)
+               --nend;
+
+       *name = (nstart < nend ? nstart : NULL);
+       *email = left+1;
+       *(nend+1) = '\0';
+       *right++ = '\0';
+
+       return (*right == '\0' ? NULL : right);
+}
+
+static int read_single_mailmap(struct string_list *map, const char *filename, char **repo_abbrev)
 {
        char buffer[1024];
-       FILE *f = fopen(filename, "r");
+       FILE *f = (filename == NULL ? NULL : fopen(filename, "r"));
 
        if (f == NULL)
                return 1;
        while (fgets(buffer, sizeof(buffer), f) != NULL) {
-               char *end_of_name, *left_bracket, *right_bracket;
-               char *name, *email;
-               int i;
+               char *name1 = 0, *email1 = 0, *name2 = 0, *email2 = 0;
                if (buffer[0] == '#') {
                        static const char abbrev[] = "# repo-abbrev:";
                        int abblen = sizeof(abbrev) - 1;
@@ -36,41 +150,49 @@ int read_mailmap(struct string_list *map, const char *filename, char **repo_abbr
                        }
                        continue;
                }
-               if ((left_bracket = strchr(buffer, '<')) == NULL)
-                       continue;
-               if ((right_bracket = strchr(left_bracket + 1, '>')) == NULL)
-                       continue;
-               if (right_bracket == left_bracket + 1)
-                       continue;
-               for (end_of_name = left_bracket;
-                    end_of_name != buffer && isspace(end_of_name[-1]);
-                    end_of_name--)
-                       ; /* keep on looking */
-               if (end_of_name == buffer)
-                       continue;
-               name = xmalloc(end_of_name - buffer + 1);
-               strlcpy(name, buffer, end_of_name - buffer + 1);
-               email = xmalloc(right_bracket - left_bracket);
-               for (i = 0; i < right_bracket - left_bracket - 1; i++)
-                       email[i] = tolower(left_bracket[i + 1]);
-               email[right_bracket - left_bracket - 1] = '\0';
-               string_list_insert(email, map)->util = name;
+               if ((name2 = parse_name_and_email(buffer, &name1, &email1)) != NULL)
+                       parse_name_and_email(name2, &name2, &email2);
+
+               if (email1)
+                       add_mapping(map, name1, email1, name2, email2);
        }
        fclose(f);
        return 0;
 }
 
-int map_email(struct string_list *map, const char *email, char *name, int maxlen)
+int read_mailmap(struct string_list *map, char **repo_abbrev)
+{
+       map->strdup_strings = 1;
+       /* each failure returns 1, so >1 means both calls failed */
+       return read_single_mailmap(map, ".mailmap", repo_abbrev) +
+              read_single_mailmap(map, git_mailmap_file, repo_abbrev) > 1;
+}
+
+void clear_mailmap(struct string_list *map)
+{
+       debug_mm("mailmap: clearing %d entries...\n", map->nr);
+       map->strdup_strings = 1;
+       string_list_clear_func(map, free_mailmap_entry);
+       debug_mm("mailmap: cleared\n");
+}
+
+int map_user(struct string_list *map,
+            char *email, int maxlen_email, char *name, int maxlen_name)
 {
        char *p;
        struct string_list_item *item;
+       struct mailmap_entry *me;
        char buf[1024], *mailbuf;
        int i;
 
-       /* autocomplete common developers */
+       /* figure out space requirement for email */
        p = strchr(email, '>');
-       if (!p)
-               return 0;
+       if (!p) {
+               /* email passed in might not be wrapped in <>, but end with a \0 */
+               p = memchr(email, '\0', maxlen_email);
+               if (p == 0)
+                       return 0;
+       }
        if (p - email + 1 < sizeof(buf))
                mailbuf = buf;
        else
@@ -80,13 +202,39 @@ int map_email(struct string_list *map, const char *email, char *name, int maxlen
        for (i = 0; i < p - email; i++)
                mailbuf[i] = tolower(email[i]);
        mailbuf[i] = 0;
+
+       debug_mm("map_user: map '%s' <%s>\n", name, mailbuf);
        item = string_list_lookup(mailbuf, map);
+       if (item != NULL) {
+               me = (struct mailmap_entry *)item->util;
+               if (me->namemap.nr) {
+                       /* The item has multiple items, so we'll look up on name too */
+                       /* If the name is not found, we choose the simple entry      */
+                       struct string_list_item *subitem = string_list_lookup(name, &me->namemap);
+                       if (subitem)
+                               item = subitem;
+               }
+       }
        if (mailbuf != buf)
                free(mailbuf);
        if (item != NULL) {
-               const char *realname = (const char *)item->util;
-               strlcpy(name, realname, maxlen);
+               struct mailmap_info *mi = (struct mailmap_info *)item->util;
+               if (mi->name == NULL && (mi->email == NULL || maxlen_email == 0)) {
+                       debug_mm("map_user:  -- (no simple mapping)\n");
+                       return 0;
+               }
+               if (maxlen_email && mi->email)
+                       strlcpy(email, mi->email, maxlen_email);
+               if (maxlen_name && mi->name)
+                       strlcpy(name, mi->name, maxlen_name);
+               debug_mm("map_user:  to '%s' <%s>\n", name, mi->email ? mi->email : "");
                return 1;
        }
+       debug_mm("map_user:  --\n");
        return 0;
 }
+
+int map_email(struct string_list *map, const char *email, char *name, int maxlen)
+{
+       return map_user(map, (char *)email, 0, name, maxlen);
+}
index 6e48f83cedd13e24d50cddf47f037791ddc5ad4b..4b2ca3a7de972c10f214b38a25be522abcbbafd0 100644 (file)
--- a/mailmap.h
+++ b/mailmap.h
@@ -1,7 +1,11 @@
 #ifndef MAILMAP_H
 #define MAILMAP_H
 
-int read_mailmap(struct string_list *map, const char *filename, char **repo_abbrev);
+int read_mailmap(struct string_list *map, char **repo_abbrev);
+void clear_mailmap(struct string_list *map);
+
 int map_email(struct string_list *mailmap, const char *email, char *name, int maxlen);
+int map_user(struct string_list *mailmap,
+            char *email, int maxlen_email, char *name, int maxlen_name);
 
 #endif
index 7827e87a928586226570132fc8922991b1cb6c8a..aa9cf23a39ae271a53d1a0c05ac99be0e832b46a 100644 (file)
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "run-command.h"
+#include "exec_cmd.h"
 
 static const char *pgm;
 static const char *arguments[9];
@@ -91,7 +92,9 @@ int main(int argc, char **argv)
        signal(SIGCHLD, SIG_DFL);
 
        if (argc < 3)
-               usage("git-merge-index [-o] [-q] <merge-program> (-a | [--] <filename>*)");
+               usage("git merge-index [-o] [-q] <merge-program> (-a | [--] <filename>*)");
+
+       git_extract_argv0_path(argv[0]);
 
        setup_git_directory();
        read_cache();
index b97026bd5cc1d2ef1b46a9ef3dcd7562ad52c377..ee853b990d8bfb15e0058fefbbd267bd58ed40fc 100644 (file)
@@ -237,7 +237,7 @@ static int save_files_dirs(const unsigned char *sha1,
                string_list_insert(newpath, &o->current_file_set);
        free(newpath);
 
-       return READ_TREE_RECURSIVE;
+       return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);
 }
 
 static int get_files_dirs(struct merge_options *o, struct tree *tree)
index 2d1413efbbc33c51fd4820933dcb54164e12d706..f01e7c81aebea84b95154c9076af66979e52715f 100644 (file)
@@ -2,8 +2,9 @@
 #include "tree-walk.h"
 #include "xdiff-interface.h"
 #include "blob.h"
+#include "exec_cmd.h"
 
-static const char merge_tree_usage[] = "git-merge-tree <base-tree> <branch1> <branch2>";
+static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>";
 static int resolve_directories = 1;
 
 struct merge_list {
@@ -344,6 +345,8 @@ int main(int argc, char **argv)
        if (argc != 4)
                usage(merge_tree_usage);
 
+       git_extract_argv0_path(argv[0]);
+
        setup_git_directory();
 
        buf1 = get_tree_descriptor(t+0, argv[1]);
diff --git a/mktag.c b/mktag.c
index ba3d495e0715d83ffab3103e4d340a3b9ac4f4e7..99a356e9ee75cb247d80ed6dc0b251ceb0bd9e46 100644 (file)
--- a/mktag.c
+++ b/mktag.c
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "tag.h"
+#include "exec_cmd.h"
 
 /*
  * A signature file has a very simple fixed format: four lines
@@ -157,7 +158,9 @@ int main(int argc, char **argv)
        unsigned char result_sha1[20];
 
        if (argc != 1)
-               usage("git-mktag < signaturefile");
+               usage("git mktag < signaturefile");
+
+       git_extract_argv0_path(argv[0]);
 
        setup_git_directory();
 
index 514fd9b15a6680389bf2c1274d29c55d2c1034f7..137a0950f686691740ac87330cf0ac7bdea8b1e7 100644 (file)
--- a/mktree.c
+++ b/mktree.c
@@ -6,6 +6,7 @@
 #include "cache.h"
 #include "quote.h"
 #include "tree.h"
+#include "exec_cmd.h"
 
 static struct treeent {
        unsigned mode;
@@ -61,7 +62,7 @@ static void write_tree(unsigned char *sha1)
        write_sha1_file(buf.buf, buf.len, tree_type, sha1);
 }
 
-static const char mktree_usage[] = "git-mktree [-z]";
+static const char mktree_usage[] = "git mktree [-z]";
 
 int main(int ac, char **av)
 {
@@ -70,6 +71,8 @@ int main(int ac, char **av)
        unsigned char sha1[20];
        int line_termination = '\n';
 
+       git_extract_argv0_path(av[0]);
+
        setup_git_directory();
 
        while ((1 < ac) && av[1][0] == '-') {
index 25b81a445c8fafe0c00ce30082b7d9a7c22ccf1e..48a12bc1352ad53fbc19ec8c5982a91673a098e1 100644 (file)
@@ -7,6 +7,7 @@
 */
 
 #include "cache.h"
+#include "exec_cmd.h"
 
 #define BLKSIZE 512
 
@@ -463,7 +464,7 @@ static void minimize(struct pack_list **min)
                pll_free(perm_all);
        }
        if (perm_ok == NULL)
-               die("Internal error: No complete sets found!\n");
+               die("Internal error: No complete sets found!");
 
        /* find the permutation with the smallest size */
        perm = perm_ok;
@@ -573,14 +574,14 @@ static struct pack_list * add_pack_file(char *filename)
        struct packed_git *p = packed_git;
 
        if (strlen(filename) < 40)
-               die("Bad pack filename: %s\n", filename);
+               die("Bad pack filename: %s", filename);
 
        while (p) {
                if (strstr(p->pack_name, filename))
                        return add_pack(p);
                p = p->next;
        }
-       die("Filename %s not found in packed_git\n", filename);
+       die("Filename %s not found in packed_git", filename);
 }
 
 static void load_all(void)
@@ -601,6 +602,8 @@ int main(int argc, char **argv)
        unsigned char *sha1;
        char buf[42]; /* 40 byte sha1 + \n + \0 */
 
+       git_extract_argv0_path(argv[0]);
+
        setup_git_directory();
 
        for (i = 1; i < argc; i++) {
@@ -636,7 +639,7 @@ int main(int argc, char **argv)
                        add_pack_file(*(argv + i++));
 
        if (local_packs == NULL)
-               die("Zero packs found!\n");
+               die("Zero packs found!");
 
        load_all_objects();
 
index b426006c5851c98fce8894bd9f76cd51a7cde170..7053538f4cf44e15a788ab46dfb680ee85ce4fc2 100644 (file)
@@ -44,9 +44,7 @@ char *write_idx_file(char *index_name, struct pack_idx_entry **objects,
 
        if (!index_name) {
                static char tmpfile[PATH_MAX];
-               snprintf(tmpfile, sizeof(tmpfile),
-                        "%s/pack/tmp_idx_XXXXXX", get_object_directory());
-               fd = xmkstemp(tmpfile);
+               fd = odb_mkstemp(tmpfile, sizeof(tmpfile), "pack/tmp_idx_XXXXXX");
                index_name = xstrdup(tmpfile);
        } else {
                unlink(index_name);
@@ -239,7 +237,7 @@ char *index_pack_lockfile(int ip_out)
        char packname[46];
 
        /*
-        * The first thing we expects from index-pack's output
+        * The first thing we expect from index-pack's output
         * is "pack\t%40s\n" or "keep\t%40s\n" (46 bytes) where
         * %40s is the newly created pack SHA1 name.  In the "keep"
         * case, we need it to remove the corresponding .keep file
diff --git a/pager.c b/pager.c
index f19ddbc87df04f117cd5e39189c8322fd5f29d68..4921843577e42b774457a61277b9bc3441d3ab6b 100644 (file)
--- a/pager.c
+++ b/pager.c
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "run-command.h"
+#include "sigchain.h"
 
 /*
  * This is split up from the rest of git so that we can do
@@ -38,6 +39,13 @@ static void wait_for_pager(void)
        finish_command(&pager_process);
 }
 
+static void wait_for_pager_signal(int signo)
+{
+       wait_for_pager();
+       sigchain_pop(signo);
+       raise(signo);
+}
+
 void setup_pager(void)
 {
        const char *pager = getenv("GIT_PAGER");
@@ -75,6 +83,7 @@ void setup_pager(void)
        close(pager_process.in);
 
        /* this makes sure that the parent terminates after the pager */
+       sigchain_push_common(wait_for_pager_signal);
        atexit(wait_for_pager);
 }
 
index 9eb55cc8b5182495b687e133a938937db1bc3949..cf71bcffd2e1e9aeacb44df488b0825f09d54255 100644 (file)
@@ -1,6 +1,7 @@
 #include "git-compat-util.h"
 #include "parse-options.h"
 #include "cache.h"
+#include "commit.h"
 
 #define OPT_SHORT 1
 #define OPT_UNSET 2
@@ -243,6 +244,9 @@ void parse_options_start(struct parse_opt_ctx_t *ctx,
        ctx->out  = argv;
        ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0);
        ctx->flags = flags;
+       if ((flags & PARSE_OPT_KEEP_UNKNOWN) &&
+           (flags & PARSE_OPT_STOP_AT_NON_OPTION))
+               die("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together");
 }
 
 static int usage_with_options_internal(const char * const *,
@@ -252,6 +256,8 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
                       const struct option *options,
                       const char * const usagestr[])
 {
+       int internal_help = !(ctx->flags & PARSE_OPT_NO_INTERNAL_HELP);
+
        /* we must reset ->opt, unknown short option leave it dangling */
        ctx->opt = NULL;
 
@@ -267,18 +273,18 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
 
                if (arg[1] != '-') {
                        ctx->opt = arg + 1;
-                       if (*ctx->opt == 'h')
+                       if (internal_help && *ctx->opt == 'h')
                                return parse_options_usage(usagestr, options);
                        switch (parse_short_opt(ctx, options)) {
                        case -1:
                                return parse_options_usage(usagestr, options);
                        case -2:
-                               return PARSE_OPT_UNKNOWN;
+                               goto unknown;
                        }
                        if (ctx->opt)
                                check_typos(arg + 1, options);
                        while (ctx->opt) {
-                               if (*ctx->opt == 'h')
+                               if (internal_help && *ctx->opt == 'h')
                                        return parse_options_usage(usagestr, options);
                                switch (parse_short_opt(ctx, options)) {
                                case -1:
@@ -291,7 +297,7 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
                                         */
                                        ctx->argv[0] = xstrdup(ctx->opt - 1);
                                        *(char *)ctx->argv[0] = '-';
-                                       return PARSE_OPT_UNKNOWN;
+                                       goto unknown;
                                }
                        }
                        continue;
@@ -305,16 +311,22 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
                        break;
                }
 
-               if (!strcmp(arg + 2, "help-all"))
+               if (internal_help && !strcmp(arg + 2, "help-all"))
                        return usage_with_options_internal(usagestr, options, 1);
-               if (!strcmp(arg + 2, "help"))
+               if (internal_help && !strcmp(arg + 2, "help"))
                        return parse_options_usage(usagestr, options);
                switch (parse_long_opt(ctx, arg + 2, options)) {
                case -1:
                        return parse_options_usage(usagestr, options);
                case -2:
-                       return PARSE_OPT_UNKNOWN;
+                       goto unknown;
                }
+               continue;
+unknown:
+               if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN))
+                       return PARSE_OPT_UNKNOWN;
+               ctx->out[ctx->cpidx++] = ctx->argv[0];
+               ctx->opt = NULL;
        }
        return PARSE_OPT_DONE;
 }
@@ -355,6 +367,9 @@ int parse_options(int argc, const char **argv, const struct option *options,
 int usage_with_options_internal(const char * const *usagestr,
                                const struct option *opts, int full)
 {
+       if (!usagestr)
+               return PARSE_OPT_HELP;
+
        fprintf(stderr, "usage: %s\n", *usagestr++);
        while (*usagestr && **usagestr)
                fprintf(stderr, "   or: %s\n", *usagestr++);
@@ -506,6 +521,22 @@ int parse_opt_verbosity_cb(const struct option *opt, const char *arg,
        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;
+}
+
 /*
  * This should really be OPTION_FILENAME type as a part of
  * parse_options that take prefix to do this while parsing.
index 034162ec6975cfca8dedadc4eb541212b0d97e43..f8ef1db1289f85e0877b66d4573044d962df934e 100644 (file)
@@ -21,6 +21,8 @@ enum parse_opt_flags {
        PARSE_OPT_KEEP_DASHDASH = 1,
        PARSE_OPT_STOP_AT_NON_OPTION = 2,
        PARSE_OPT_KEEP_ARGV0 = 4,
+       PARSE_OPT_KEEP_UNKNOWN = 8,
+       PARSE_OPT_NO_INTERNAL_HELP = 16,
 };
 
 enum parse_opt_option_flags {
@@ -151,6 +153,7 @@ extern int parse_options_end(struct parse_opt_ctx_t *ctx);
 extern int parse_opt_abbrev_cb(const struct option *, const char *, int);
 extern int parse_opt_approxidate_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);
 
 #define OPT__VERBOSE(var)  OPT_BOOLEAN('v', "verbose", (var), "be verbose")
 #define OPT__QUIET(var)    OPT_BOOLEAN('q', "quiet",   (var), "be quiet")
index 871f1d20c0e364220d23035b34685ced8737cda8..0df4cb086ba26d1f4d56b8347a6a7bcf219832f5 100644 (file)
@@ -1,4 +1,5 @@
 #include "cache.h"
+#include "exec_cmd.h"
 
 static void flush_current_id(int patchlen, unsigned char *id, git_SHA_CTX *c)
 {
@@ -72,13 +73,15 @@ static void generate_id_list(void)
        flush_current_id(patchlen, sha1, &ctx);
 }
 
-static const char patch_id_usage[] = "git-patch-id < patch";
+static const char patch_id_usage[] = "git patch-id < patch";
 
 int main(int argc, char **argv)
 {
        if (argc != 1)
                usage(patch_id_usage);
 
+       git_extract_argv0_path(argv[0]);
+
        generate_id_list();
        return 0;
 }
diff --git a/path.c b/path.c
index a074aea64921eb1fb90f079ede9087e6b8109f6a..e332b504a6b8c6b229da2526c4f810b8d0fb9889 100644 (file)
--- a/path.c
+++ b/path.c
@@ -363,56 +363,97 @@ const char *make_relative_path(const char *abs, const char *base)
 }
 
 /*
- * path = absolute path
- * buf = buffer of at least max(2, strlen(path)+1) bytes
- * It is okay if buf == path, but they should not overlap otherwise.
+ * It is okay if dst == src, but they should not overlap otherwise.
  *
- * Performs the following normalizations on path, storing the result in buf:
- * - Removes trailing slashes.
- * - Removes empty components.
+ * Performs the following normalizations on src, storing the result in dst:
+ * - Ensures that components are separated by '/' (Windows only)
+ * - Squashes sequences of '/'.
  * - Removes "." components.
  * - Removes ".." components, and the components the precede them.
- * "" and paths that contain only slashes are normalized to "/".
- * Returns the length of the output.
+ * Returns failure (non-zero) if a ".." component appears as first path
+ * component anytime during the normalization. Otherwise, returns success (0).
  *
  * Note that this function is purely textual.  It does not follow symlinks,
  * verify the existence of the path, or make any system calls.
  */
-int normalize_absolute_path(char *buf, const char *path)
+int normalize_path_copy(char *dst, const char *src)
 {
-       const char *comp_start = path, *comp_end = path;
-       char *dst = buf;
-       int comp_len;
-       assert(buf);
-       assert(path);
-
-       while (*comp_start) {
-               assert(*comp_start == '/');
-               while (*++comp_end && *comp_end != '/')
-                       ; /* nothing */
-               comp_len = comp_end - comp_start;
-
-               if (!strncmp("/",  comp_start, comp_len) ||
-                   !strncmp("/.", comp_start, comp_len))
-                       goto next;
-
-               if (!strncmp("/..", comp_start, comp_len)) {
-                       while (dst > buf && *--dst != '/')
-                               ; /* nothing */
-                       goto next;
-               }
+       char *dst0;
 
-               memmove(dst, comp_start, comp_len);
-               dst += comp_len;
-       next:
-               comp_start = comp_end;
+       if (has_dos_drive_prefix(src)) {
+               *dst++ = *src++;
+               *dst++ = *src++;
        }
+       dst0 = dst;
 
-       if (dst == buf)
+       if (is_dir_sep(*src)) {
                *dst++ = '/';
+               while (is_dir_sep(*src))
+                       src++;
+       }
 
+       for (;;) {
+               char c = *src;
+
+               /*
+                * A path component that begins with . could be
+                * special:
+                * (1) "." and ends   -- ignore and terminate.
+                * (2) "./"           -- ignore them, eat slash and continue.
+                * (3) ".." and ends  -- strip one and terminate.
+                * (4) "../"          -- strip one, eat slash and continue.
+                */
+               if (c == '.') {
+                       if (!src[1]) {
+                               /* (1) */
+                               src++;
+                       } else if (is_dir_sep(src[1])) {
+                               /* (2) */
+                               src += 2;
+                               while (is_dir_sep(*src))
+                                       src++;
+                               continue;
+                       } else if (src[1] == '.') {
+                               if (!src[2]) {
+                                       /* (3) */
+                                       src += 2;
+                                       goto up_one;
+                               } else if (is_dir_sep(src[2])) {
+                                       /* (4) */
+                                       src += 3;
+                                       while (is_dir_sep(*src))
+                                               src++;
+                                       goto up_one;
+                               }
+                       }
+               }
+
+               /* copy up to the next '/', and eat all '/' */
+               while ((c = *src++) != '\0' && !is_dir_sep(c))
+                       *dst++ = c;
+               if (is_dir_sep(c)) {
+                       *dst++ = '/';
+                       while (is_dir_sep(c))
+                               c = *src++;
+                       src--;
+               } else if (!c)
+                       break;
+               continue;
+
+       up_one:
+               /*
+                * dst0..dst is prefix portion, and dst[-1] is '/';
+                * go up one level.
+                */
+               dst--;  /* go to trailing '/' */
+               if (dst <= dst0)
+                       return -1;
+               /* Windows: dst[-1] cannot be backslash anymore */
+               while (dst0 < dst && dst[-1] != '/')
+                       dst--;
+       }
        *dst = '\0';
-       return dst - buf;
+       return 0;
 }
 
 /*
@@ -438,15 +479,16 @@ int longest_ancestor_length(const char *path, const char *prefix_list)
                return -1;
 
        for (colon = ceil = prefix_list; *colon; ceil = colon+1) {
-               for (colon = ceil; *colon && *colon != ':'; colon++);
+               for (colon = ceil; *colon && *colon != PATH_SEP; colon++);
                len = colon - ceil;
                if (len == 0 || len > PATH_MAX || !is_absolute_path(ceil))
                        continue;
                strlcpy(buf, ceil, len+1);
-               len = normalize_absolute_path(buf, buf);
-               /* Strip "trailing slashes" from "/". */
-               if (len == 1)
-                       len = 0;
+               if (normalize_path_copy(buf, buf) < 0)
+                       continue;
+               len = strlen(buf);
+               if (len > 0 && buf[len-1] == '/')
+                       buf[--len] = '\0';
 
                if (!strncmp(path, buf, len) &&
                    path[len] == '/' &&
@@ -457,3 +499,39 @@ int longest_ancestor_length(const char *path, const char *prefix_list)
 
        return max_len;
 }
+
+/* strip arbitrary amount of directory separators at end of path */
+static inline int chomp_trailing_dir_sep(const char *path, int len)
+{
+       while (len && is_dir_sep(path[len - 1]))
+               len--;
+       return len;
+}
+
+/*
+ * If path ends with suffix (complete path components), returns the
+ * part before suffix (sans trailing directory separators).
+ * Otherwise returns NULL.
+ */
+char *strip_path_suffix(const char *path, const char *suffix)
+{
+       int path_len = strlen(path), suffix_len = strlen(suffix);
+
+       while (suffix_len) {
+               if (!path_len)
+                       return NULL;
+
+               if (is_dir_sep(path[path_len - 1])) {
+                       if (!is_dir_sep(suffix[suffix_len - 1]))
+                               return NULL;
+                       path_len = chomp_trailing_dir_sep(path, path_len);
+                       suffix_len = chomp_trailing_dir_sep(suffix, suffix_len);
+               }
+               else if (path[--path_len] != suffix[--suffix_len])
+                       return NULL;
+       }
+
+       if (path_len && !is_dir_sep(path[path_len - 1]))
+               return NULL;
+       return xstrndup(path, chomp_trailing_dir_sep(path, path_len));
+}
index 421d9c5bca2224777808ccc06fe4912ba8793229..c0184080998734450b250aa7dc72c427e1ecac8a 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -6,9 +6,19 @@
 #include "string-list.h"
 #include "mailmap.h"
 #include "log-tree.h"
+#include "color.h"
 
 static char *user_format;
 
+static void save_user_format(struct rev_info *rev, const char *cp, int is_tformat)
+{
+       free(user_format);
+       user_format = xstrdup(cp);
+       if (is_tformat)
+               rev->use_terminator = 1;
+       rev->commit_format = CMIT_FMT_USERFORMAT;
+}
+
 void get_commit_format(const char *arg, struct rev_info *rev)
 {
        int i;
@@ -32,12 +42,7 @@ void get_commit_format(const char *arg, struct rev_info *rev)
                return;
        }
        if (!prefixcmp(arg, "format:") || !prefixcmp(arg, "tformat:")) {
-               const char *cp = strchr(arg, ':') + 1;
-               free(user_format);
-               user_format = xstrdup(cp);
-               if (arg[0] == 't')
-                       rev->use_terminator = 1;
-               rev->commit_format = CMIT_FMT_USERFORMAT;
+               save_user_format(rev, strchr(arg, ':') + 1, arg[0] == 't');
                return;
        }
        for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) {
@@ -49,6 +54,10 @@ void get_commit_format(const char *arg, struct rev_info *rev)
                        return;
                }
        }
+       if (strchr(arg, '%')) {
+               save_user_format(rev, arg, 1);
+               return;
+       }
 
        die("invalid --pretty format: %s", arg);
 }
@@ -74,8 +83,7 @@ static int get_one_line(const char *msg)
 /* High bit set, or ISO-2022-INT */
 int non_ascii(int ch)
 {
-       ch = (ch & 0xff);
-       return ((ch & 0x80) || (ch == 0x1b));
+       return !isascii(ch) || ch == '\033';
 }
 
 static int is_rfc2047_special(char ch)
@@ -209,15 +217,13 @@ static void add_merge_info(enum cmit_fmt fmt, struct strbuf *sb,
        while (parent) {
                struct commit *p = parent->item;
                const char *hex = NULL;
-               const char *dots;
                if (abbrev)
                        hex = find_unique_abbrev(p->object.sha1, abbrev);
                if (!hex)
                        hex = sha1_to_hex(p->object.sha1);
-               dots = (abbrev && strlen(hex) != 40) ?  "..." : "";
                parent = parent->next;
 
-               strbuf_addf(sb, " %s%s", hex, dots);
+               strbuf_addf(sb, " %s", hex);
        }
        strbuf_addch(sb, '\n');
 }
@@ -304,23 +310,14 @@ static char *logmsg_reencode(const struct commit *commit,
        return out;
 }
 
-static int mailmap_name(struct strbuf *sb, const char *email)
+static int mailmap_name(char *email, int email_len, char *name, int name_len)
 {
        static struct string_list *mail_map;
-       char buffer[1024];
-
        if (!mail_map) {
                mail_map = xcalloc(1, sizeof(*mail_map));
-               read_mailmap(mail_map, ".mailmap", NULL);
+               read_mailmap(mail_map, NULL);
        }
-
-       if (!mail_map->nr)
-               return -1;
-
-       if (!map_email(mail_map, email, buffer, sizeof(buffer)))
-               return -1;
-       strbuf_addstr(sb, buffer);
-       return 0;
+       return mail_map->nr && map_user(mail_map, email, email_len, name, name_len);
 }
 
 static size_t format_person_part(struct strbuf *sb, char part,
@@ -331,6 +328,9 @@ static size_t format_person_part(struct strbuf *sb, char part,
        int start, end, tz = 0;
        unsigned long date = 0;
        char *ep;
+       const char *name_start, *name_end, *mail_start, *mail_end, *msg_end = msg+len;
+       char person_name[1024];
+       char person_mail[1024];
 
        /* advance 'end' to point to email start delimiter */
        for (end = 0; end < len && msg[end] != '<'; end++)
@@ -344,25 +344,34 @@ static size_t format_person_part(struct strbuf *sb, char part,
        if (end >= len - 2)
                goto skip;
 
+       /* Seek for both name and email part */
+       name_start = msg;
+       name_end = msg+end;
+       while (name_end > name_start && isspace(*(name_end-1)))
+               name_end--;
+       mail_start = msg+end+1;
+       mail_end = mail_start;
+       while (mail_end < msg_end && *mail_end != '>')
+               mail_end++;
+       if (mail_end == msg_end)
+               goto skip;
+       end = mail_end-msg;
+
+       if (part == 'N' || part == 'E') { /* mailmap lookup */
+               strlcpy(person_name, name_start, name_end-name_start+1);
+               strlcpy(person_mail, mail_start, mail_end-mail_start+1);
+               mailmap_name(person_mail, sizeof(person_mail), person_name, sizeof(person_name));
+               name_start = person_name;
+               name_end = name_start + strlen(person_name);
+               mail_start = person_mail;
+               mail_end = mail_start +  strlen(person_mail);
+       }
        if (part == 'n' || part == 'N') {       /* name */
-               while (end > 0 && isspace(msg[end - 1]))
-                       end--;
-               if (part != 'N' || !msg[end] || !msg[end + 1] ||
-                   mailmap_name(sb, msg + end + 2) < 0)
-                       strbuf_add(sb, msg, end);
+               strbuf_add(sb, name_start, name_end-name_start);
                return placeholder_len;
        }
-       start = ++end; /* save email start position */
-
-       /* advance 'end' to point to email end delimiter */
-       for ( ; end < len && msg[end] != '>'; end++)
-               ; /* do nothing */
-
-       if (end >= len)
-               goto skip;
-
-       if (part == 'e') {      /* email */
-               strbuf_add(sb, msg + start, end - start);
+       if (part == 'e' || part == 'E') {       /* email */
+               strbuf_add(sb, mail_start, mail_end-mail_start);
                return placeholder_len;
        }
 
@@ -554,17 +563,28 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
        /* these are independent of the commit */
        switch (placeholder[0]) {
        case 'C':
+               if (placeholder[1] == '(') {
+                       const char *end = strchr(placeholder + 2, ')');
+                       char color[COLOR_MAXLEN];
+                       if (!end)
+                               return 0;
+                       color_parse_mem(placeholder + 2,
+                                       end - (placeholder + 2),
+                                       "--pretty format", color);
+                       strbuf_addstr(sb, color);
+                       return end - placeholder + 1;
+               }
                if (!prefixcmp(placeholder + 1, "red")) {
-                       strbuf_addstr(sb, "\033[31m");
+                       strbuf_addstr(sb, GIT_COLOR_RED);
                        return 4;
                } else if (!prefixcmp(placeholder + 1, "green")) {
-                       strbuf_addstr(sb, "\033[32m");
+                       strbuf_addstr(sb, GIT_COLOR_GREEN);
                        return 6;
                } else if (!prefixcmp(placeholder + 1, "blue")) {
-                       strbuf_addstr(sb, "\033[34m");
+                       strbuf_addstr(sb, GIT_COLOR_BLUE);
                        return 5;
                } else if (!prefixcmp(placeholder + 1, "reset")) {
-                       strbuf_addstr(sb, "\033[m");
+                       strbuf_addstr(sb, GIT_COLOR_RESET);
                        return 6;
                } else
                        return 0;
index b1475ffa0962e1f0238fdb9a6870aa4b0bfd6d3b..940ec76fdf231ac1345079ca2dc5da88925bcfb6 100644 (file)
@@ -1574,6 +1574,26 @@ static void update_callback(struct diff_queue_struct *q,
                default:
                        die("unexpected diff status %c", p->status);
                case DIFF_STATUS_UNMERGED:
+                       /*
+                        * ADD_CACHE_IGNORE_REMOVAL is unset if "git
+                        * add -u" is calling us, In such a case, a
+                        * missing work tree file needs to be removed
+                        * if there is an unmerged entry at stage #2,
+                        * but such a diff record is followed by
+                        * another with DIFF_STATUS_DELETED (and if
+                        * there is no stage #2, we won't see DELETED
+                        * nor MODIFIED).  We can simply continue
+                        * either way.
+                        */
+                       if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL))
+                               continue;
+                       /*
+                        * Otherwise, it is "git add path" is asking
+                        * to explicitly add it; we fall through.  A
+                        * missing work tree file is an error and is
+                        * caught by add_file_to_index() in such a
+                        * case.
+                        */
                case DIFF_STATUS_MODIFIED:
                case DIFF_STATUS_TYPE_CHANGED:
                        if (add_file_to_index(&the_index, path, data->flags)) {
diff --git a/refs.c b/refs.c
index 33ced65a7801f8653d608a9580e237ce8df39ae2..6eb5f5384611bb5d159d892a1bfd120d72e54b9b 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -275,10 +275,8 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
                                list = get_ref_dir(ref, list);
                                continue;
                        }
-                       if (!resolve_ref(ref, sha1, 1, &flag)) {
-                               error("%s points nowhere!", ref);
-                               continue;
-                       }
+                       if (!resolve_ref(ref, sha1, 1, &flag))
+                               hashclr(sha1);
                        list = add_ref(ref, sha1, flag, list, NULL);
                }
                free(ref);
@@ -287,6 +285,35 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
        return sort_ref_list(list);
 }
 
+struct warn_if_dangling_data {
+       const char *refname;
+       const char *msg_fmt;
+};
+
+static int warn_if_dangling_symref(const char *refname, const unsigned char *sha1,
+                                  int flags, void *cb_data)
+{
+       struct warn_if_dangling_data *d = cb_data;
+       const char *resolves_to;
+       unsigned char junk[20];
+
+       if (!(flags & REF_ISSYMREF))
+               return 0;
+
+       resolves_to = resolve_ref(refname, junk, 0, NULL);
+       if (!resolves_to || strcmp(resolves_to, d->refname))
+               return 0;
+
+       printf(d->msg_fmt, refname);
+       return 0;
+}
+
+void warn_dangling_symref(const char *msg_fmt, const char *refname)
+{
+       struct warn_if_dangling_data data = { refname, msg_fmt };
+       for_each_rawref(warn_if_dangling_symref, &data);
+}
+
 static struct ref_list *get_loose_refs(void)
 {
        if (!cached_refs.did_loose) {
@@ -498,16 +525,19 @@ int read_ref(const char *ref, unsigned char *sha1)
        return -1;
 }
 
+#define DO_FOR_EACH_INCLUDE_BROKEN 01
 static int do_one_ref(const char *base, each_ref_fn fn, int trim,
-                     void *cb_data, struct ref_list *entry)
+                     int flags, void *cb_data, struct ref_list *entry)
 {
        if (strncmp(base, entry->name, trim))
                return 0;
-       if (is_null_sha1(entry->sha1))
-               return 0;
-       if (!has_sha1_file(entry->sha1)) {
-               error("%s does not point to a valid object!", entry->name);
-               return 0;
+       if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
+               if (is_null_sha1(entry->sha1))
+                       return 0;
+               if (!has_sha1_file(entry->sha1)) {
+                       error("%s does not point to a valid object!", entry->name);
+                       return 0;
+               }
        }
        current_ref = entry;
        return fn(entry->name + trim, entry->sha1, entry->flag, cb_data);
@@ -561,7 +591,7 @@ int peel_ref(const char *ref, unsigned char *sha1)
 }
 
 static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
-                          void *cb_data)
+                          int flags, void *cb_data)
 {
        int retval = 0;
        struct ref_list *packed = get_packed_refs();
@@ -570,7 +600,7 @@ static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
        struct ref_list *extra;
 
        for (extra = extra_refs; extra; extra = extra->next)
-               retval = do_one_ref(base, fn, trim, cb_data, extra);
+               retval = do_one_ref(base, fn, trim, flags, cb_data, extra);
 
        while (packed && loose) {
                struct ref_list *entry;
@@ -586,13 +616,13 @@ static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
                        entry = packed;
                        packed = packed->next;
                }
-               retval = do_one_ref(base, fn, trim, cb_data, entry);
+               retval = do_one_ref(base, fn, trim, flags, cb_data, entry);
                if (retval)
                        goto end_each;
        }
 
        for (packed = packed ? packed : loose; packed; packed = packed->next) {
-               retval = do_one_ref(base, fn, trim, cb_data, packed);
+               retval = do_one_ref(base, fn, trim, flags, cb_data, packed);
                if (retval)
                        goto end_each;
        }
@@ -614,22 +644,28 @@ int head_ref(each_ref_fn fn, void *cb_data)
 
 int for_each_ref(each_ref_fn fn, void *cb_data)
 {
-       return do_for_each_ref("refs/", fn, 0, cb_data);
+       return do_for_each_ref("refs/", fn, 0, 0, cb_data);
 }
 
 int for_each_tag_ref(each_ref_fn fn, void *cb_data)
 {
-       return do_for_each_ref("refs/tags/", fn, 10, cb_data);
+       return do_for_each_ref("refs/tags/", fn, 10, 0, cb_data);
 }
 
 int for_each_branch_ref(each_ref_fn fn, void *cb_data)
 {
-       return do_for_each_ref("refs/heads/", fn, 11, cb_data);
+       return do_for_each_ref("refs/heads/", fn, 11, 0, cb_data);
 }
 
 int for_each_remote_ref(each_ref_fn fn, void *cb_data)
 {
-       return do_for_each_ref("refs/remotes/", fn, 13, cb_data);
+       return do_for_each_ref("refs/remotes/", fn, 13, 0, cb_data);
+}
+
+int for_each_rawref(each_ref_fn fn, void *cb_data)
+{
+       return do_for_each_ref("refs/", fn, 0,
+                              DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
 }
 
 /*
@@ -1453,7 +1489,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
        return 1;
 }
 
-int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
+int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long ofs, void *cb_data)
 {
        const char *logfile;
        FILE *logfp;
@@ -1464,6 +1500,16 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
        logfp = fopen(logfile, "r");
        if (!logfp)
                return -1;
+
+       if (ofs) {
+               struct stat statbuf;
+               if (fstat(fileno(logfp), &statbuf) ||
+                   statbuf.st_size < ofs ||
+                   fseek(logfp, -ofs, SEEK_END) ||
+                   fgets(buf, sizeof(buf), logfp))
+                       return -1;
+       }
+
        while (fgets(buf, sizeof(buf), logfp)) {
                unsigned char osha1[20], nsha1[20];
                char *email_end, *message;
@@ -1497,6 +1543,11 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
        return ret;
 }
 
+int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
+{
+       return for_each_recent_reflog_ent(ref, fn, 0, cb_data);
+}
+
 static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data)
 {
        DIR *dir = opendir(git_path("logs/%s", base));
diff --git a/refs.h b/refs.h
index 06ad26055661a9b9e475d0f8a7bd6d1cfb42e792..29bdcecd4edb5e7281a4da36a06aa05e025f38a7 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -24,6 +24,11 @@ extern int for_each_tag_ref(each_ref_fn, void *);
 extern int for_each_branch_ref(each_ref_fn, void *);
 extern int for_each_remote_ref(each_ref_fn, void *);
 
+/* can be used to learn about broken ref and symref */
+extern int for_each_rawref(each_ref_fn, void *);
+
+extern void warn_dangling_symref(const char *msg_fmt, const char *refname);
+
 /*
  * Extra refs will be listed by for_each_ref() before any actual refs
  * for the duration of this process or until clear_extra_refs() is
@@ -60,6 +65,7 @@ extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned
 /* iterate over reflog entries */
 typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *);
 int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data);
+int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long, void *cb_data);
 
 /*
  * Calls the specified function for each reflog file until it returns nonzero,
index 570e11286ea295e825a23b1d5ed40c9fbd02be57..d7079c6dd871dc1b482d347d013438fe30cc0908 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -4,6 +4,7 @@
 #include "commit.h"
 #include "diff.h"
 #include "revision.h"
+#include "dir.h"
 
 static struct refspec s_tag_refspec = {
        0,
@@ -634,10 +635,7 @@ static struct refspec *parse_push_refspec(int nr_refspec, const char **refspec)
 
 static int valid_remote_nick(const char *name)
 {
-       if (!name[0] || /* not empty */
-           (name[0] == '.' && /* not "." */
-            (!name[1] || /* not ".." */
-             (name[1] == '.' && !name[2]))))
+       if (!name[0] || is_dot_or_dotdot(name))
                return 0;
        return !strchr(name, '/'); /* no slash */
 }
index 718fb526dd1fbbc5de18784586d37d6069807b77..713c6e16ac536eaa667cc9f22768670be129483c 100644 (file)
--- a/rerere.c
+++ b/rerere.c
@@ -12,15 +12,15 @@ static int rerere_autoupdate;
 
 static char *merge_rr_path;
 
-static const char *rr_path(const char *name, const char *file)
+const char *rerere_path(const char *hex, const char *file)
 {
-       return git_path("rr-cache/%s/%s", name, file);
+       return git_path("rr-cache/%s/%s", hex, file);
 }
 
-static int has_resolution(const char *name)
+int has_rerere_resolution(const char *hex)
 {
        struct stat st;
-       return !stat(rr_path(name, "postimage"), &st);
+       return !stat(rerere_path(hex, "postimage"), &st);
 }
 
 static void read_rr(struct string_list *rr)
@@ -208,12 +208,12 @@ static int merge(const char *name, const char *path)
        mmbuffer_t result = {NULL, 0};
        xpparam_t xpp = {XDF_NEED_MINIMAL};
 
-       if (handle_file(path, NULL, rr_path(name, "thisimage")) < 0)
+       if (handle_file(path, NULL, rerere_path(name, "thisimage")) < 0)
                return 1;
 
-       if (read_mmfile(&cur, rr_path(name, "thisimage")) ||
-                       read_mmfile(&base, rr_path(name, "preimage")) ||
-                       read_mmfile(&other, rr_path(name, "postimage")))
+       if (read_mmfile(&cur, rerere_path(name, "thisimage")) ||
+                       read_mmfile(&base, rerere_path(name, "preimage")) ||
+                       read_mmfile(&other, rerere_path(name, "postimage")))
                return 1;
        ret = xdl_merge(&base, &cur, "", &other, "",
                        &xpp, XDL_MERGE_ZEALOUS, &result);
@@ -290,8 +290,8 @@ static int do_plain_rerere(struct string_list *rr, int fd)
                        hex = xstrdup(sha1_to_hex(sha1));
                        string_list_insert(path, rr)->util = hex;
                        if (mkdir(git_path("rr-cache/%s", hex), 0755))
-                               continue;;
-                       handle_file(path, NULL, rr_path(hex, "preimage"));
+                               continue;
+                       handle_file(path, NULL, rerere_path(hex, "preimage"));
                        fprintf(stderr, "Recorded preimage for '%s'\n", path);
                }
        }
@@ -307,7 +307,7 @@ static int do_plain_rerere(struct string_list *rr, int fd)
                const char *path = rr->items[i].string;
                const char *name = (const char *)rr->items[i].util;
 
-               if (has_resolution(name)) {
+               if (has_rerere_resolution(name)) {
                        if (!merge(name, path)) {
                                if (rerere_autoupdate)
                                        string_list_insert(path, &update);
@@ -326,7 +326,7 @@ static int do_plain_rerere(struct string_list *rr, int fd)
                        continue;
 
                fprintf(stderr, "Recorded resolution for '%s'.\n", path);
-               copy_file(rr_path(name, "postimage"), path, 0666);
+               copy_file(rerere_path(name, "postimage"), path, 0666);
        mark_resolved:
                rr->items[i].util = NULL;
        }
index f9b03862fe78b560ee606637be3b1ce972a2cc14..13313f3f2b2cc6a8d895305b7ac92c12c1753682 100644 (file)
--- a/rerere.h
+++ b/rerere.h
@@ -5,5 +5,7 @@
 
 extern int setup_rerere(struct string_list *);
 extern int rerere(void);
+extern const char *rerere_path(const char *hex, const char *file);
+extern int has_rerere_resolution(const char *hex);
 
 #endif
index c0df714f6cb0825196d1e79cbc53a042529e2d26..f5771c7898a939e34effb8c757c5a237830075b9 100644 (file)
@@ -183,8 +183,11 @@ static struct commit *handle_commit(struct rev_info *revs, struct object *object
                if (!tag->tagged)
                        die("bad tag");
                object = parse_object(tag->tagged->sha1);
-               if (!object)
+               if (!object) {
+                       if (flags & UNINTERESTING)
+                               return NULL;
                        die("bad object %s", sha1_to_hex(tag->tagged->sha1));
+               }
        }
 
        /*
@@ -479,9 +482,10 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit,
                while (parent) {
                        struct commit *p = parent->item;
                        parent = parent->next;
+                       if (p)
+                               p->object.flags |= UNINTERESTING;
                        if (parse_commit(p) < 0)
-                               return -1;
-                       p->object.flags |= UNINTERESTING;
+                               continue;
                        if (p->parents)
                                mark_parents_uninteresting(p);
                        if (p->object.flags & SEEN)
@@ -990,16 +994,6 @@ static void add_message_grep(struct rev_info *revs, const char *pattern)
        add_grep(revs, pattern, GREP_PATTERN_BODY);
 }
 
-static void add_ignore_packed(struct rev_info *revs, const char *name)
-{
-       int num = ++revs->num_ignore_packed;
-
-       revs->ignore_packed = xrealloc(revs->ignore_packed,
-                                      sizeof(const char *) * (num + 1));
-       revs->ignore_packed[num-1] = name;
-       revs->ignore_packed[num] = NULL;
-}
-
 static int handle_revision_opt(struct rev_info *revs, int argc, const char **argv,
                               int *unkc, const char **unkv)
 {
@@ -1112,12 +1106,12 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->edge_hint = 1;
        } else if (!strcmp(arg, "--unpacked")) {
                revs->unpacked = 1;
-               free(revs->ignore_packed);
-               revs->ignore_packed = NULL;
-               revs->num_ignore_packed = 0;
-       } else if (!prefixcmp(arg, "--unpacked=")) {
+               revs->kept_pack_only = 0;
+       } else if (!strcmp(arg, "--kept-pack-only")) {
                revs->unpacked = 1;
-               add_ignore_packed(revs, arg+11);
+               revs->kept_pack_only = 1;
+       } else if (!prefixcmp(arg, "--unpacked=")) {
+               die("--unpacked=<packfile> no longer supported.");
        } else if (!strcmp(arg, "-r")) {
                revs->diff = 1;
                DIFF_OPT_SET(&revs->diffopt, RECURSIVE);
@@ -1140,9 +1134,13 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
        } else if (!strcmp(arg, "--pretty")) {
                revs->verbose_header = 1;
                get_commit_format(arg+8, revs);
-       } else if (!prefixcmp(arg, "--pretty=")) {
+       } else if (!prefixcmp(arg, "--pretty=") || !prefixcmp(arg, "--format=")) {
                revs->verbose_header = 1;
                get_commit_format(arg+9, revs);
+       } else if (!strcmp(arg, "--oneline")) {
+               revs->verbose_header = 1;
+               get_commit_format("oneline", revs);
+               revs->abbrev_commit = 1;
        } else if (!strcmp(arg, "--graph")) {
                revs->topo_order = 1;
                revs->rewrite_parents = 1;
@@ -1681,7 +1679,10 @@ enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
 {
        if (commit->object.flags & SHOWN)
                return commit_ignore;
-       if (revs->unpacked && has_sha1_pack(commit->object.sha1, revs->ignore_packed))
+       if (revs->unpacked &&
+           (revs->kept_pack_only
+            ? has_sha1_kept_pack(commit->object.sha1)
+            : has_sha1_pack(commit->object.sha1)))
                return commit_ignore;
        if (revs->show_all)
                return commit_show;
index 7cf848771b5be811f7741ce988b860760202f6f3..ad123d78c582e11e27748a613fae5b17bd87b0e9 100644 (file)
@@ -49,7 +49,8 @@ struct rev_info {
                        blob_objects:1,
                        edge_hint:1,
                        limited:1,
-                       unpacked:1, /* see also ignore_packed below */
+                       unpacked:1,
+                       kept_pack_only:1,
                        boundary:2,
                        left_right:1,
                        rewrite_parents:1,
@@ -80,16 +81,13 @@ struct rev_info {
                        missing_newline:1;
        enum date_mode date_mode;
 
-       const char **ignore_packed; /* pretend objects in these are unpacked */
-       int num_ignore_packed;
-
        unsigned int    abbrev;
        enum cmit_fmt   commit_format;
        struct log_info *loginfo;
        int             nr, total;
        const char      *mime_boundary;
        char            *message_id;
-       const char      *ref_message_id;
+       struct string_list *ref_message_ids;
        const char      *add_signoff;
        const char      *extra_headers;
        const char      *log_reencode;
index c90cdc50e3165bcdb798c85b2dc7b929a9b0a144..b05c734d05e99cd009a0df26f0fc95fa13ae6f25 100644 (file)
@@ -118,7 +118,9 @@ int start_command(struct child_process *cmd)
                } else {
                        execvp(cmd->argv[0], (char *const*) cmd->argv);
                }
-               die("exec %s failed.", cmd->argv[0]);
+               trace_printf("trace: exec '%s' failed: %s\n", cmd->argv[0],
+                               strerror(errno));
+               exit(127);
        }
 #else
        int s0 = -1, s1 = -1, s2 = -1;  /* backups of stdin, stdout, stderr */
@@ -187,6 +189,7 @@ int start_command(struct child_process *cmd)
 #endif
 
        if (cmd->pid < 0) {
+               int err = errno;
                if (need_in)
                        close_pair(fdin);
                else if (cmd->in)
@@ -197,7 +200,9 @@ int start_command(struct child_process *cmd)
                        close(cmd->out);
                if (need_err)
                        close_pair(fderr);
-               return -ERR_RUN_COMMAND_FORK;
+               return err == ENOENT ?
+                       -ERR_RUN_COMMAND_EXEC :
+                       -ERR_RUN_COMMAND_FORK;
        }
 
        if (need_in)
@@ -236,9 +241,14 @@ static int wait_or_whine(pid_t pid)
                if (!WIFEXITED(status))
                        return -ERR_RUN_COMMAND_WAITPID_NOEXIT;
                code = WEXITSTATUS(status);
-               if (code)
+               switch (code) {
+               case 127:
+                       return -ERR_RUN_COMMAND_EXEC;
+               case 0:
+                       return 0;
+               default:
                        return -code;
-               return 0;
+               }
        }
 }
 
@@ -342,3 +352,48 @@ int finish_async(struct async *async)
 #endif
        return ret;
 }
+
+int run_hook(const char *index_file, const char *name, ...)
+{
+       struct child_process hook;
+       const char **argv = NULL, *env[2];
+       char index[PATH_MAX];
+       va_list args;
+       int ret;
+       size_t i = 0, alloc = 0;
+
+       if (access(git_path("hooks/%s", name), X_OK) < 0)
+               return 0;
+
+       va_start(args, name);
+       ALLOC_GROW(argv, i + 1, alloc);
+       argv[i++] = git_path("hooks/%s", name);
+       while (argv[i-1]) {
+               ALLOC_GROW(argv, i + 1, alloc);
+               argv[i++] = va_arg(args, const char *);
+       }
+       va_end(args);
+
+       memset(&hook, 0, sizeof(hook));
+       hook.argv = argv;
+       hook.no_stdin = 1;
+       hook.stdout_to_stderr = 1;
+       if (index_file) {
+               snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
+               env[0] = index;
+               env[1] = NULL;
+               hook.env = env;
+       }
+
+       ret = start_command(&hook);
+       free(argv);
+       if (ret) {
+               warning("Could not spawn %s", argv[0]);
+               return ret;
+       }
+       ret = finish_command(&hook);
+       if (ret == -ERR_RUN_COMMAND_WAITPID_SIGNAL)
+               warning("%s exited due to uncaught signal", argv[0]);
+
+       return ret;
+}
index a8b0c209e9b7487ce3c2b214cbebe7ad4f97fd11..15e870a65eb037cd49d1e01251711915da06d260 100644 (file)
@@ -10,6 +10,7 @@ enum {
        ERR_RUN_COMMAND_WAITPID_SIGNAL,
        ERR_RUN_COMMAND_WAITPID_NOEXIT,
 };
+#define IS_RUN_COMMAND_ERR(x) ((x) <= -ERR_RUN_COMMAND_FORK)
 
 struct child_process {
        const char **argv;
@@ -49,6 +50,8 @@ int start_command(struct child_process *);
 int finish_command(struct child_process *);
 int run_command(struct child_process *);
 
+extern int run_hook(const char *index_file, const char *name, ...);
+
 #define RUN_COMMAND_NO_STDIN 1
 #define RUN_GIT_CMD         2  /*If this is to be git sub-command */
 #define RUN_COMMAND_STDOUT_TO_STDERR 4
diff --git a/setup.c b/setup.c
index dfda532adc16f5e6d25d7cfc5add3e0e2b6a5209..6c2deda18492acb5a8597563d6843f9d0dd232c0 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -4,92 +4,6 @@
 static int inside_git_dir = -1;
 static int inside_work_tree = -1;
 
-static int sanitary_path_copy(char *dst, const char *src)
-{
-       char *dst0;
-
-       if (has_dos_drive_prefix(src)) {
-               *dst++ = *src++;
-               *dst++ = *src++;
-       }
-       dst0 = dst;
-
-       if (is_dir_sep(*src)) {
-               *dst++ = '/';
-               while (is_dir_sep(*src))
-                       src++;
-       }
-
-       for (;;) {
-               char c = *src;
-
-               /*
-                * A path component that begins with . could be
-                * special:
-                * (1) "." and ends   -- ignore and terminate.
-                * (2) "./"           -- ignore them, eat slash and continue.
-                * (3) ".." and ends  -- strip one and terminate.
-                * (4) "../"          -- strip one, eat slash and continue.
-                */
-               if (c == '.') {
-                       if (!src[1]) {
-                               /* (1) */
-                               src++;
-                       } else if (is_dir_sep(src[1])) {
-                               /* (2) */
-                               src += 2;
-                               while (is_dir_sep(*src))
-                                       src++;
-                               continue;
-                       } else if (src[1] == '.') {
-                               if (!src[2]) {
-                                       /* (3) */
-                                       src += 2;
-                                       goto up_one;
-                               } else if (is_dir_sep(src[2])) {
-                                       /* (4) */
-                                       src += 3;
-                                       while (is_dir_sep(*src))
-                                               src++;
-                                       goto up_one;
-                               }
-                       }
-               }
-
-               /* copy up to the next '/', and eat all '/' */
-               while ((c = *src++) != '\0' && !is_dir_sep(c))
-                       *dst++ = c;
-               if (is_dir_sep(c)) {
-                       *dst++ = '/';
-                       while (is_dir_sep(c))
-                               c = *src++;
-                       src--;
-               } else if (!c)
-                       break;
-               continue;
-
-       up_one:
-               /*
-                * dst0..dst is prefix portion, and dst[-1] is '/';
-                * go up one level.
-                */
-               dst -= 2; /* go past trailing '/' if any */
-               if (dst < dst0)
-                       return -1;
-               while (1) {
-                       if (dst <= dst0)
-                               break;
-                       c = *dst--;
-                       if (c == '/') { /* MinGW: cannot be '\\' anymore */
-                               dst += 2;
-                               break;
-                       }
-               }
-       }
-       *dst = '\0';
-       return 0;
-}
-
 const char *prefix_path(const char *prefix, int len, const char *path)
 {
        const char *orig = path;
@@ -101,7 +15,7 @@ const char *prefix_path(const char *prefix, int len, const char *path)
                        memcpy(sanitized, prefix, len);
                strcpy(sanitized + len, path);
        }
-       if (sanitary_path_copy(sanitized, sanitized))
+       if (normalize_path_copy(sanitized, sanitized))
                goto error_out;
        if (is_absolute_path(orig)) {
                const char *work_tree = get_git_work_tree();
index b63f842cbdaf01e8a3b0ba35cbfc1ad0cbac6913..456317356f323eb744c207d787a2e7598ad9601e 100644 (file)
@@ -801,7 +801,7 @@ unsigned char* use_pack(struct packed_git *p,
        if (p->pack_fd == -1 && open_packed_git(p))
                die("packfile %s cannot be accessed", p->pack_name);
 
-       /* Since packfiles end in a hash of their content and its
+       /* Since packfiles end in a hash of their content and it's
         * pointless to ask for an offset into the middle of that
         * hash, and the in_window function above wouldn't match
         * don't allow an offset too close to the end of the file.
@@ -1708,6 +1708,9 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset,
        delta_base_cache_lru.prev = &ent->lru;
 }
 
+static void *read_object(const unsigned char *sha1, enum object_type *type,
+                        unsigned long *size);
+
 static void *unpack_delta_entry(struct packed_git *p,
                                struct pack_window **w_curs,
                                off_t curpos,
@@ -1916,25 +1919,8 @@ off_t find_pack_entry_one(const unsigned char *sha1,
        return 0;
 }
 
-int matches_pack_name(struct packed_git *p, const char *name)
-{
-       const char *last_c, *c;
-
-       if (!strcmp(p->pack_name, name))
-               return 1;
-
-       for (c = p->pack_name, last_c = c; *c;)
-               if (*c == '/')
-                       last_c = ++c;
-               else
-                       ++c;
-       if (!strcmp(last_c, name))
-               return 1;
-
-       return 0;
-}
-
-static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, const char **ignore_packed)
+static int find_pack_ent(const unsigned char *sha1, struct pack_entry *e,
+                        int kept_pack_only)
 {
        static struct packed_git *last_found = (void *)1;
        struct packed_git *p;
@@ -1946,15 +1932,8 @@ static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, cons
        p = (last_found == (void *)1) ? packed_git : last_found;
 
        do {
-               if (ignore_packed) {
-                       const char **ig;
-                       for (ig = ignore_packed; *ig; ig++)
-                               if (matches_pack_name(p, *ig))
-                                       break;
-                       if (*ig)
-                               goto next;
-               }
-
+               if (kept_pack_only && !p->pack_keep)
+                       goto next;
                if (p->num_bad_objects) {
                        unsigned i;
                        for (i = 0; i < p->num_bad_objects; i++)
@@ -1994,6 +1973,16 @@ static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, cons
        return 0;
 }
 
+static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e)
+{
+       return find_pack_ent(sha1, e, 0);
+}
+
+static int find_kept_pack_entry(const unsigned char *sha1, struct pack_entry *e)
+{
+       return find_pack_ent(sha1, e, 1);
+}
+
 struct packed_git *find_sha1_pack(const unsigned char *sha1,
                                  struct packed_git *packs)
 {
@@ -2035,7 +2024,7 @@ int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)
        struct pack_entry e;
        int status;
 
-       if (!find_pack_entry(sha1, &e, NULL)) {
+       if (!find_pack_entry(sha1, &e)) {
                /* Most likely it's a loose object. */
                status = sha1_loose_object_info(sha1, sizep);
                if (status >= 0)
@@ -2043,7 +2032,7 @@ int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)
 
                /* Not a loose object; someone else may have just packed it. */
                reprepare_packed_git();
-               if (!find_pack_entry(sha1, &e, NULL))
+               if (!find_pack_entry(sha1, &e))
                        return status;
        }
 
@@ -2062,7 +2051,7 @@ static void *read_packed_sha1(const unsigned char *sha1,
        struct pack_entry e;
        void *data;
 
-       if (!find_pack_entry(sha1, &e, NULL))
+       if (!find_pack_entry(sha1, &e))
                return NULL;
        data = cache_or_unpack_entry(e.p, e.offset, size, type, 1);
        if (!data) {
@@ -2138,8 +2127,8 @@ int pretend_sha1_file(void *buf, unsigned long len, enum object_type type,
        return 0;
 }
 
-void *read_object(const unsigned char *sha1, enum object_type *type,
-                 unsigned long *size)
+static void *read_object(const unsigned char *sha1, enum object_type *type,
+                        unsigned long *size)
 {
        unsigned long mapsize;
        void *map, *buf;
@@ -2461,17 +2450,23 @@ int has_pack_file(const unsigned char *sha1)
        return 1;
 }
 
-int has_sha1_pack(const unsigned char *sha1, const char **ignore_packed)
+int has_sha1_pack(const unsigned char *sha1)
+{
+       struct pack_entry e;
+       return find_pack_entry(sha1, &e);
+}
+
+int has_sha1_kept_pack(const unsigned char *sha1)
 {
        struct pack_entry e;
-       return find_pack_entry(sha1, &e, ignore_packed);
+       return find_kept_pack_entry(sha1, &e);
 }
 
 int has_sha1_file(const unsigned char *sha1)
 {
        struct pack_entry e;
 
-       if (find_pack_entry(sha1, &e, NULL))
+       if (find_pack_entry(sha1, &e))
                return 1;
        return has_loose_object(sha1);
 }
index 722fc35a6d98e1c260c6e14738ad3b2d41d924aa..2f75179f4c6c1d05bdd7594b23dcf77007c26751 100644 (file)
@@ -238,8 +238,28 @@ static int ambiguous_path(const char *path, int len)
        return slash;
 }
 
+/*
+ * *string and *len will only be substituted, and *string returned (for
+ * later free()ing) if the string passed in is of the form @{-<n>}.
+ */
+static char *substitute_nth_last_branch(const char **string, int *len)
+{
+       struct strbuf buf = STRBUF_INIT;
+       int ret = interpret_nth_last_branch(*string, &buf);
+
+       if (ret == *len) {
+               size_t size;
+               *string = strbuf_detach(&buf, &size);
+               *len = size;
+               return (char *)*string;
+       }
+
+       return NULL;
+}
+
 int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
 {
+       char *last_branch = substitute_nth_last_branch(&str, &len);
        const char **p, *r;
        int refs_found = 0;
 
@@ -248,22 +268,27 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
                char fullref[PATH_MAX];
                unsigned char sha1_from_ref[20];
                unsigned char *this_result;
+               int flag;
 
                this_result = refs_found ? sha1_from_ref : sha1;
                mksnpath(fullref, sizeof(fullref), *p, len, str);
-               r = resolve_ref(fullref, this_result, 1, NULL);
+               r = resolve_ref(fullref, this_result, 1, &flag);
                if (r) {
                        if (!refs_found++)
                                *ref = xstrdup(r);
                        if (!warn_ambiguous_refs)
                                break;
-               }
+               } else if ((flag & REF_ISSYMREF) &&
+                          (len != 4 || strcmp(str, "HEAD")))
+                       warning("ignoring dangling symref %s.", fullref);
        }
+       free(last_branch);
        return refs_found;
 }
 
 int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
 {
+       char *last_branch = substitute_nth_last_branch(&str, &len);
        const char **p;
        int logs_found = 0;
 
@@ -294,9 +319,12 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
                if (!warn_ambiguous_refs)
                        break;
        }
+       free(last_branch);
        return logs_found;
 }
 
+static int get_sha1_1(const char *name, int len, unsigned char *sha1);
+
 static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 {
        static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
@@ -307,10 +335,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
        if (len == 40 && !get_sha1_hex(str, sha1))
                return 0;
 
-       /* basic@{time or number} format to query ref-log */
+       /* basic@{time or number or -number} format to query ref-log */
        reflog_len = at = 0;
        if (len && str[len-1] == '}') {
-               for (at = 0; at < len - 1; at++) {
+               for (at = len-2; at >= 0; at--) {
                        if (str[at] == '@' && str[at+1] == '{') {
                                reflog_len = (len-1) - (at+2);
                                len = at;
@@ -324,6 +352,16 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
                return -1;
 
        if (!len && reflog_len) {
+               struct strbuf buf = STRBUF_INIT;
+               int ret;
+               /* try the @{-N} syntax for n-th checkout */
+               ret = interpret_nth_last_branch(str+at, &buf);
+               if (ret > 0) {
+                       /* substitute this branch name and restart */
+                       return get_sha1_1(buf.buf, buf.len, sha1);
+               } else if (ret == 0) {
+                       return -1;
+               }
                /* allow "@{...}" to mean the current branch reflog */
                refs_found = dwim_ref("HEAD", 4, sha1, &real_ref);
        } else if (reflog_len)
@@ -379,8 +417,6 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
        return 0;
 }
 
-static int get_sha1_1(const char *name, int len, unsigned char *sha1);
-
 static int get_parent(const char *name, int len,
                      unsigned char *result, int idx)
 {
@@ -674,6 +710,92 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
        return retval;
 }
 
+struct grab_nth_branch_switch_cbdata {
+       long cnt, alloc;
+       struct strbuf *buf;
+};
+
+static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
+                                 const char *email, unsigned long timestamp, int tz,
+                                 const char *message, void *cb_data)
+{
+       struct grab_nth_branch_switch_cbdata *cb = cb_data;
+       const char *match = NULL, *target = NULL;
+       size_t len;
+       int nth;
+
+       if (!prefixcmp(message, "checkout: moving from ")) {
+               match = message + strlen("checkout: moving from ");
+               target = strstr(match, " to ");
+       }
+
+       if (!match || !target)
+               return 0;
+
+       len = target - match;
+       nth = cb->cnt++ % cb->alloc;
+       strbuf_reset(&cb->buf[nth]);
+       strbuf_add(&cb->buf[nth], match, len);
+       return 0;
+}
+
+/*
+ * This reads "@{-N}" syntax, finds the name of the Nth previous
+ * branch we were on, and places the name of the branch in the given
+ * buf and returns the number of characters parsed if successful.
+ *
+ * If the input is not of the accepted format, it returns a negative
+ * number to signal an error.
+ *
+ * If the input was ok but there are not N branch switches in the
+ * reflog, it returns 0.
+ */
+int interpret_nth_last_branch(const char *name, struct strbuf *buf)
+{
+       long nth;
+       int i, retval;
+       struct grab_nth_branch_switch_cbdata cb;
+       const char *brace;
+       char *num_end;
+
+       if (name[0] != '@' || name[1] != '{' || name[2] != '-')
+               return -1;
+       brace = strchr(name, '}');
+       if (!brace)
+               return -1;
+       nth = strtol(name+3, &num_end, 10);
+       if (num_end != brace)
+               return -1;
+       if (nth <= 0)
+               return -1;
+       cb.alloc = nth;
+       cb.buf = xmalloc(nth * sizeof(struct strbuf));
+       for (i = 0; i < nth; i++)
+               strbuf_init(&cb.buf[i], 20);
+       cb.cnt = 0;
+       retval = 0;
+       for_each_recent_reflog_ent("HEAD", grab_nth_branch_switch, 40960, &cb);
+       if (cb.cnt < nth) {
+               cb.cnt = 0;
+               for (i = 0; i < nth; i++)
+                       strbuf_release(&cb.buf[i]);
+               for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+       }
+       if (cb.cnt < nth)
+               goto release_return;
+       i = cb.cnt % nth;
+       strbuf_reset(buf);
+       strbuf_add(buf, cb.buf[i].buf, cb.buf[i].len);
+       retval = brace-name+1;
+
+release_return:
+       for (i = 0; i < nth; i++)
+               strbuf_release(&cb.buf[i]);
+       free(cb.buf);
+
+       return retval;
+}
+
 /*
  * This is like "get_sha1_basic()", except it allows "sha1 expressions",
  * notably "xyz^" for "parent of xyz"
diff --git a/sigchain.c b/sigchain.c
new file mode 100644 (file)
index 0000000..1118b99
--- /dev/null
@@ -0,0 +1,52 @@
+#include "sigchain.h"
+#include "cache.h"
+
+#define SIGCHAIN_MAX_SIGNALS 32
+
+struct sigchain_signal {
+       sigchain_fun *old;
+       int n;
+       int alloc;
+};
+static struct sigchain_signal signals[SIGCHAIN_MAX_SIGNALS];
+
+static void check_signum(int sig)
+{
+       if (sig < 1 || sig >= SIGCHAIN_MAX_SIGNALS)
+               die("BUG: signal out of range: %d", sig);
+}
+
+int sigchain_push(int sig, sigchain_fun f)
+{
+       struct sigchain_signal *s = signals + sig;
+       check_signum(sig);
+
+       ALLOC_GROW(s->old, s->n + 1, s->alloc);
+       s->old[s->n] = signal(sig, f);
+       if (s->old[s->n] == SIG_ERR)
+               return -1;
+       s->n++;
+       return 0;
+}
+
+int sigchain_pop(int sig)
+{
+       struct sigchain_signal *s = signals + sig;
+       check_signum(sig);
+       if (s->n < 1)
+               return 0;
+
+       if (signal(sig, s->old[s->n - 1]) == SIG_ERR)
+               return -1;
+       s->n--;
+       return 0;
+}
+
+void sigchain_push_common(sigchain_fun f)
+{
+       sigchain_push(SIGINT, f);
+       sigchain_push(SIGHUP, f);
+       sigchain_push(SIGTERM, f);
+       sigchain_push(SIGQUIT, f);
+       sigchain_push(SIGPIPE, f);
+}
diff --git a/sigchain.h b/sigchain.h
new file mode 100644 (file)
index 0000000..618083b
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef SIGCHAIN_H
+#define SIGCHAIN_H
+
+typedef void (*sigchain_fun)(int);
+
+int sigchain_push(int sig, sigchain_fun f);
+int sigchain_pop(int sig);
+
+void sigchain_push_common(sigchain_fun f);
+
+#endif /* SIGCHAIN_H */
index bdf49544d47b97475800b104ed9efe8f846db265..6ed06840b856a91f6d215c9a862e064f521384f0 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -256,18 +256,21 @@ size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder,
 size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *f)
 {
        size_t res;
+       size_t oldalloc = sb->alloc;
 
        strbuf_grow(sb, size);
        res = fread(sb->buf + sb->len, 1, size, f);
-       if (res > 0) {
+       if (res > 0)
                strbuf_setlen(sb, sb->len + res);
-       }
+       else if (res < 0 && oldalloc == 0)
+               strbuf_release(sb);
        return res;
 }
 
 ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint)
 {
        size_t oldlen = sb->len;
+       size_t oldalloc = sb->alloc;
 
        strbuf_grow(sb, hint ? hint : 8192);
        for (;;) {
@@ -275,7 +278,10 @@ ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint)
 
                cnt = xread(fd, sb->buf + sb->len, sb->alloc - sb->len - 1);
                if (cnt < 0) {
-                       strbuf_setlen(sb, oldlen);
+                       if (oldalloc == 0)
+                               strbuf_release(sb);
+                       else
+                               strbuf_setlen(sb, oldlen);
                        return -1;
                }
                if (!cnt)
@@ -292,6 +298,8 @@ ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint)
 
 int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint)
 {
+       size_t oldalloc = sb->alloc;
+
        if (hint < 32)
                hint = 32;
 
@@ -311,7 +319,8 @@ int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint)
                /* .. the buffer was too small - try again */
                hint *= 2;
        }
-       strbuf_release(sb);
+       if (oldalloc == 0)
+               strbuf_release(sb);
        return -1;
 }
 
index ddd83c8c76112cecd5d23668aaca467601855a72..15e14cf47a586613a62970b393c269728ffbef93 100644 (file)
@@ -26,10 +26,10 @@ static int get_entry_index(const struct string_list *list, const char *string,
 }
 
 /* returns -1-index if already exists */
-static int add_entry(struct string_list *list, const char *string)
+static int add_entry(int insert_at, struct string_list *list, const char *string)
 {
-       int exact_match;
-       int index = get_entry_index(list, string, &exact_match);
+       int exact_match = 0;
+       int index = insert_at != -1 ? insert_at : get_entry_index(list, string, &exact_match);
 
        if (exact_match)
                return -1 - index;
@@ -53,7 +53,13 @@ static int add_entry(struct string_list *list, const char *string)
 
 struct string_list_item *string_list_insert(const char *string, struct string_list *list)
 {
-       int index = add_entry(list, string);
+       return string_list_insert_at_index(-1, string, list);
+}
+
+struct string_list_item *string_list_insert_at_index(int insert_at,
+                                                    const char *string, struct string_list *list)
+{
+       int index = add_entry(insert_at, list, string);
 
        if (index < 0)
                index = -1 - index;
@@ -68,6 +74,16 @@ int string_list_has_string(const struct string_list *list, const char *string)
        return exact_match;
 }
 
+int string_list_find_insert_index(const struct string_list *list, const char *string,
+                                 int negative_existing_index)
+{
+       int exact_match;
+       int index = get_entry_index(list, string, &exact_match);
+       if (exact_match)
+               index = -1 - (negative_existing_index ? index : 0);
+       return index;
+}
+
 struct string_list_item *string_list_lookup(const char *string, struct string_list *list)
 {
        int exact_match, i = get_entry_index(list, string, &exact_match);
@@ -94,6 +110,25 @@ void string_list_clear(struct string_list *list, int free_util)
        list->nr = list->alloc = 0;
 }
 
+void string_list_clear_func(struct string_list *list, string_list_clear_func_t clearfunc)
+{
+       if (list->items) {
+               int i;
+               if (clearfunc) {
+                       for (i = 0; i < list->nr; i++)
+                               clearfunc(list->items[i].util, list->items[i].string);
+               }
+               if (list->strdup_strings) {
+                       for (i = 0; i < list->nr; i++)
+                               free(list->items[i].string);
+               }
+               free(list->items);
+       }
+       list->items = NULL;
+       list->nr = list->alloc = 0;
+}
+
+
 void print_string_list(const char *text, const struct string_list *p)
 {
        int i;
index 4d6a7051fe5bccf04a0d0c32a90e5cf9c00dba3c..d32ba05202880733dd76f5465a0ae16753d1fba6 100644 (file)
@@ -15,9 +15,18 @@ struct string_list
 void print_string_list(const char *text, const struct string_list *p);
 void string_list_clear(struct string_list *list, int free_util);
 
+/* Use this function to call a custom clear function on each util pointer */
+/* The string associated with the util pointer is passed as the second argument */
+typedef void (*string_list_clear_func_t)(void *p, const char *str);
+void string_list_clear_func(struct string_list *list, string_list_clear_func_t clearfunc);
+
 /* Use these functions only on sorted lists: */
 int string_list_has_string(const struct string_list *list, const char *string);
+int string_list_find_insert_index(const struct string_list *list, const char *string,
+                                 int negative_existing_index);
 struct string_list_item *string_list_insert(const char *string, struct string_list *list);
+struct string_list_item *string_list_insert_at_index(int insert_at,
+                                                    const char *string, struct string_list *list);
 struct string_list_item *string_list_lookup(const char *string, struct string_list *list);
 
 /* Use these functions only on unsorted lists: */
index 5a5e781a15d7d9cb60797958433eca896b31ec85..f262b7c44b387f4c60901124c30585b3c059fa73 100644 (file)
 #include "cache.h"
 
-struct pathname {
+static struct cache_def {
+       char path[PATH_MAX + 1];
        int len;
-       char path[PATH_MAX];
-};
+       int flags;
+       int track_flags;
+       int prefix_len_stat_func;
+} cache;
 
-/* Return matching pathname prefix length, or zero if not matching */
-static inline int match_pathname(int len, const char *name, struct pathname *match)
+/*
+ * Returns the length (on a path component basis) of the longest
+ * common prefix match of 'name' and the cached path string.
+ */
+static inline int longest_match_lstat_cache(int len, const char *name,
+                                           int *previous_slash)
 {
-       int match_len = match->len;
-       return (len > match_len &&
-               name[match_len] == '/' &&
-               !memcmp(name, match->path, match_len)) ? match_len : 0;
+       int max_len, match_len = 0, match_len_prev = 0, i = 0;
+
+       max_len = len < cache.len ? len : cache.len;
+       while (i < max_len && name[i] == cache.path[i]) {
+               if (name[i] == '/') {
+                       match_len_prev = match_len;
+                       match_len = i;
+               }
+               i++;
+       }
+       /* Is the cached path string a substring of 'name'? */
+       if (i == cache.len && cache.len < len && name[cache.len] == '/') {
+               match_len_prev = match_len;
+               match_len = cache.len;
+       /* Is 'name' a substring of the cached path string? */
+       } else if ((i == len && len < cache.len && cache.path[len] == '/') ||
+                  (i == len && len == cache.len)) {
+               match_len_prev = match_len;
+               match_len = len;
+       }
+       *previous_slash = match_len_prev;
+       return match_len;
 }
 
-static inline void set_pathname(int len, const char *name, struct pathname *match)
+static inline void reset_lstat_cache(int track_flags, int prefix_len_stat_func)
 {
-       if (len < PATH_MAX) {
-               match->len = len;
-               memcpy(match->path, name, len);
-               match->path[len] = 0;
-       }
+       cache.path[0] = '\0';
+       cache.len = 0;
+       cache.flags = 0;
+       cache.track_flags = track_flags;
+       cache.prefix_len_stat_func = prefix_len_stat_func;
 }
 
-int has_symlink_leading_path(int len, const char *name)
+#define FL_DIR      (1 << 0)
+#define FL_NOENT    (1 << 1)
+#define FL_SYMLINK  (1 << 2)
+#define FL_LSTATERR (1 << 3)
+#define FL_ERR      (1 << 4)
+#define FL_FULLPATH (1 << 5)
+
+/*
+ * Check if name 'name' of length 'len' has a symlink leading
+ * component, or if the directory exists and is real, or not.
+ *
+ * To speed up the check, some information is allowed to be cached.
+ * This can be indicated by the 'track_flags' argument, which also can
+ * be used to indicate that we should check the full path.
+ *
+ * The 'prefix_len_stat_func' parameter can be used to set the length
+ * of the prefix, where the cache should use the stat() function
+ * instead of the lstat() function to test each path component.
+ */
+static int lstat_cache(int len, const char *name,
+                      int track_flags, int prefix_len_stat_func)
 {
-       static struct pathname link, nonlink;
-       char path[PATH_MAX];
+       int match_len, last_slash, last_slash_dir, previous_slash;
+       int match_flags, ret_flags, save_flags, max_len, ret;
        struct stat st;
-       char *sp;
-       int known_dir;
 
-       /*
-        * See if the last known symlink cache matches.
-        */
-       if (match_pathname(len, name, &link))
-               return 1;
+       if (cache.track_flags != track_flags ||
+           cache.prefix_len_stat_func != prefix_len_stat_func) {
+               /*
+                * As a safeguard we clear the cache if the values of
+                * track_flags and/or prefix_len_stat_func does not
+                * match with the last supplied values.
+                */
+               reset_lstat_cache(track_flags, prefix_len_stat_func);
+               match_len = last_slash = 0;
+       } else {
+               /*
+                * Check to see if we have a match from the cache for
+                * the 2 "excluding" path types.
+                */
+               match_len = last_slash =
+                       longest_match_lstat_cache(len, name, &previous_slash);
+               match_flags = cache.flags & track_flags & (FL_NOENT|FL_SYMLINK);
+               if (match_flags && match_len == cache.len)
+                       return match_flags;
+               /*
+                * If we now have match_len > 0, we would know that
+                * the matched part will always be a directory.
+                *
+                * Also, if we are tracking directories and 'name' is
+                * a substring of the cache on a path component basis,
+                * we can return immediately.
+                */
+               match_flags = track_flags & FL_DIR;
+               if (match_flags && len == match_len)
+                       return match_flags;
+       }
 
        /*
-        * Get rid of the last known directory part
+        * Okay, no match from the cache so far, so now we have to
+        * check the rest of the path components.
         */
-       known_dir = match_pathname(len, name, &nonlink);
-
-       while ((sp = strchr(name + known_dir + 1, '/')) != NULL) {
-               int thislen = sp - name ;
-               memcpy(path, name, thislen);
-               path[thislen] = 0;
-
-               if (lstat(path, &st))
-                       return 0;
-               if (S_ISDIR(st.st_mode)) {
-                       set_pathname(thislen, path, &nonlink);
-                       known_dir = thislen;
+       ret_flags = FL_DIR;
+       last_slash_dir = last_slash;
+       max_len = len < PATH_MAX ? len : PATH_MAX;
+       while (match_len < max_len) {
+               do {
+                       cache.path[match_len] = name[match_len];
+                       match_len++;
+               } while (match_len < max_len && name[match_len] != '/');
+               if (match_len >= max_len && !(track_flags & FL_FULLPATH))
+                       break;
+               last_slash = match_len;
+               cache.path[last_slash] = '\0';
+
+               if (last_slash <= prefix_len_stat_func)
+                       ret = stat(cache.path, &st);
+               else
+                       ret = lstat(cache.path, &st);
+
+               if (ret) {
+                       ret_flags = FL_LSTATERR;
+                       if (errno == ENOENT)
+                               ret_flags |= FL_NOENT;
+               } else if (S_ISDIR(st.st_mode)) {
+                       last_slash_dir = last_slash;
                        continue;
-               }
-               if (S_ISLNK(st.st_mode)) {
-                       set_pathname(thislen, path, &link);
-                       return 1;
+               } else if (S_ISLNK(st.st_mode)) {
+                       ret_flags = FL_SYMLINK;
+               } else {
+                       ret_flags = FL_ERR;
                }
                break;
        }
-       return 0;
+
+       /*
+        * At the end update the cache.  Note that max 3 different
+        * path types, FL_NOENT, FL_SYMLINK and FL_DIR, can be cached
+        * for the moment!
+        */
+       save_flags = ret_flags & track_flags & (FL_NOENT|FL_SYMLINK);
+       if (save_flags && last_slash > 0 && last_slash <= PATH_MAX) {
+               cache.path[last_slash] = '\0';
+               cache.len = last_slash;
+               cache.flags = save_flags;
+       } else if (track_flags & FL_DIR &&
+                  last_slash_dir > 0 && last_slash_dir <= PATH_MAX) {
+               /*
+                * We have a separate test for the directory case,
+                * since it could be that we have found a symlink or a
+                * non-existing directory and the track_flags says
+                * that we cannot cache this fact, so the cache would
+                * then have been left empty in this case.
+                *
+                * But if we are allowed to track real directories, we
+                * can still cache the path components before the last
+                * one (the found symlink or non-existing component).
+                */
+               cache.path[last_slash_dir] = '\0';
+               cache.len = last_slash_dir;
+               cache.flags = FL_DIR;
+       } else {
+               reset_lstat_cache(track_flags, prefix_len_stat_func);
+       }
+       return ret_flags;
+}
+
+/*
+ * Invalidate the given 'name' from the cache, if 'name' matches
+ * completely with the cache.
+ */
+void invalidate_lstat_cache(int len, const char *name)
+{
+       int match_len, previous_slash;
+
+       match_len = longest_match_lstat_cache(len, name, &previous_slash);
+       if (len == match_len) {
+               if ((cache.track_flags & FL_DIR) && previous_slash > 0) {
+                       cache.path[previous_slash] = '\0';
+                       cache.len = previous_slash;
+                       cache.flags = FL_DIR;
+               } else
+                       reset_lstat_cache(cache.track_flags,
+                                         cache.prefix_len_stat_func);
+       }
+}
+
+/*
+ * Completely clear the contents of the cache
+ */
+void clear_lstat_cache(void)
+{
+       reset_lstat_cache(0, 0);
+}
+
+#define USE_ONLY_LSTAT  0
+
+/*
+ * Return non-zero if path 'name' has a leading symlink component
+ */
+int has_symlink_leading_path(int len, const char *name)
+{
+       return lstat_cache(len, name,
+                          FL_SYMLINK|FL_DIR, USE_ONLY_LSTAT) &
+               FL_SYMLINK;
+}
+
+/*
+ * Return non-zero if path 'name' has a leading symlink component or
+ * if some leading path component does not exists.
+ */
+int has_symlink_or_noent_leading_path(int len, const char *name)
+{
+       return lstat_cache(len, name,
+                          FL_SYMLINK|FL_NOENT|FL_DIR, USE_ONLY_LSTAT) &
+               (FL_SYMLINK|FL_NOENT);
+}
+
+/*
+ * Return non-zero if all path components of 'name' exists as a
+ * directory.  If prefix_len > 0, we will test with the stat()
+ * function instead of the lstat() function for a prefix length of
+ * 'prefix_len', thus we then allow for symlinks in the prefix part as
+ * long as those points to real existing directories.
+ */
+int has_dirs_only_path(int len, const char *name, int prefix_len)
+{
+       return lstat_cache(len, name,
+                          FL_DIR|FL_FULLPATH, prefix_len) &
+               FL_DIR;
 }
index ed49c20b16b520da1b460960dbadd5d31c4927ba..09623414a755dc7e964a0d21768e136dba4556e2 100644 (file)
@@ -38,4 +38,7 @@ full-svn-test:
        $(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C
        $(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=0 LC_ALL=en_US.UTF-8
 
-.PHONY: pre-clean $(T) aggregate-results clean
+valgrind:
+       GIT_TEST_OPTS=--valgrind $(MAKE)
+
+.PHONY: pre-clean $(T) aggregate-results clean valgrind
index 8f12d48fe8b4ffe4a4b37dcd16ce58e50837433f..d8f6c7de6d27e27a33982e82893baa3bb5ffd7fd 100644 (file)
--- a/t/README
+++ b/t/README
@@ -39,7 +39,8 @@ this:
     * passed all 3 test(s)
 
 You can pass --verbose (or -v), --debug (or -d), and --immediate
-(or -i) command line argument to the test.
+(or -i) command line argument to the test, or by setting GIT_TEST_OPTS
+appropriately before running "make".
 
 --verbose::
        This makes the test more verbose.  Specifically, the
@@ -58,6 +59,21 @@ You can pass --verbose (or -v), --debug (or -d), and --immediate
        This causes additional long-running tests to be run (where
        available), for more exhaustive testing.
 
+--valgrind::
+       Execute all Git binaries with valgrind and exit with status
+       126 on errors (just like regular tests, this will only stop
+       the test script when running under -i).  Valgrind errors
+       go to stderr, so you might want to pass the -v option, too.
+
+       Since it makes no sense to run the tests with --valgrind and
+       not see any output, this option implies --verbose.  For
+       convenience, it also implies --tee.
+
+--tee::
+       In addition to printing the test output to the terminal,
+       write it to files named 't/test-results/$TEST_NAME.out'.
+       As the names depend on the tests' file names, it is safe to
+       run the tests with this option in parallel.
 
 Skipping Tests
 --------------
@@ -212,6 +228,24 @@ library for your script to use.
    is to summarize successes and failures in the test script and
    exit with an appropriate error code.
 
+ - test_tick
+
+   Make commit and tag names consistent by setting the author and
+   committer times to defined stated.  Subsequent calls will
+   advance the times by a fixed amount.
+
+ - test_commit <message> [<filename> [<contents>]]
+
+   Creates a commit with the given message, committing the given
+   file with the given contents (default for both is to reuse the
+   message string), and adds a tag (again reusing the message
+   string as name).  Calls test_tick to make the SHA-1s
+   reproducible.
+
+ - test_merge <message> <commit-or-tag>
+
+   Merges the given rev using the given message.  Like test_commit,
+   creates a tag and calls test_tick before committing.
 
 Tips for Writing Tests
 ----------------------
index 6ac312b9059394b44cd6e106f9da6394674ee54a..86cdebc727c4964899a71366151a7782558153c7 100644 (file)
@@ -11,7 +11,21 @@ then
        exit
 fi
 
-LIB_HTTPD_PATH=${LIB_HTTPD_PATH-'/usr/sbin/apache2'}
+HTTPD_PARA=""
+
+case $(uname) in
+       Darwin)
+               DEFAULT_HTTPD_PATH='/usr/sbin/httpd'
+               DEFAULT_HTTPD_MODULE_PATH='/usr/libexec/apache2'
+               HTTPD_PARA="$HTTPD_PARA -DDarwin"
+       ;;
+       *)
+               DEFAULT_HTTPD_PATH='/usr/sbin/apache2'
+               DEFAULT_HTTPD_MODULE_PATH='/usr/lib/apache2/modules'
+       ;;
+esac
+
+LIB_HTTPD_PATH=${LIB_HTTPD_PATH-"$DEFAULT_HTTPD_PATH"}
 LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'8111'}
 
 TEST_PATH="$TEST_DIRECTORY"/lib-httpd
@@ -20,9 +34,9 @@ HTTPD_DOCUMENT_ROOT_PATH=$HTTPD_ROOT_PATH/www
 
 if ! test -x "$LIB_HTTPD_PATH"
 then
-        say "skipping test, no web server found at '$LIB_HTTPD_PATH'"
-        test_done
-        exit
+       say "skipping test, no web server found at '$LIB_HTTPD_PATH'"
+       test_done
+       exit
 fi
 
 HTTPD_VERSION=`$LIB_HTTPD_PATH -v | \
@@ -39,14 +53,12 @@ then
                        exit
                fi
 
-               LIB_HTTPD_MODULE_PATH='/usr/lib/apache2/modules'
+               LIB_HTTPD_MODULE_PATH="$DEFAULT_HTTPD_MODULE_PATH"
        fi
 else
        error "Could not identify web server at '$LIB_HTTPD_PATH'"
 fi
 
-HTTPD_PARA=""
-
 prepare_httpd() {
        mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH"
 
@@ -84,7 +96,7 @@ prepare_httpd() {
 start_httpd() {
        prepare_httpd
 
-       trap 'stop_httpd; die' exit
+       trap 'stop_httpd; die' EXIT
 
        "$LIB_HTTPD_PATH" -d "$HTTPD_ROOT_PATH" \
                -f "$TEST_PATH/apache.conf" $HTTPD_PARA \
@@ -92,8 +104,8 @@ start_httpd() {
 }
 
 stop_httpd() {
-       trap 'die' exit
+       trap 'die' EXIT
 
        "$LIB_HTTPD_PATH" -d "$HTTPD_ROOT_PATH" \
-               -f "$TEST_PATH/apache.conf" -k stop
+               -f "$TEST_PATH/apache.conf" $HTTPD_PARA -k stop
 }
index fdb19a50f11c8c71c9f7addcceeab8847558cc49..f460e404169bba1c57a48a3aac1df2f2b97d0b99 100644 (file)
@@ -4,6 +4,15 @@ DocumentRoot www
 LogFormat "%h %l %u %t \"%r\" %>s %b" common
 CustomLog access.log common
 ErrorLog error.log
+<IfModule !mod_log_config.c>
+       LoadModule log_config_module modules/mod_log_config.so
+</IfModule>
+
+<IfDefine Darwin>
+       LoadModule log_config_module modules/mod_log_config.so
+       LockFile accept.lock
+       PidFile httpd.pid
+</IfDefine>
 
 <IfDefine SSL>
 LoadModule ssl_module modules/mod_ssl.so
diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh
new file mode 100644 (file)
index 0000000..260a231
--- /dev/null
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+# After setting the fake editor with this function, you can
+#
+# - override the commit message with $FAKE_COMMIT_MESSAGE,
+# - amend the commit message with $FAKE_COMMIT_AMEND
+# - check that non-commit messages have a certain line count with $EXPECT_COUNT
+# - rewrite a rebase -i script with $FAKE_LINES in the form
+#
+#      "[<lineno1>] [<lineno2>]..."
+#
+#   If a line number is prefixed with "squash" or "edit", the respective line's
+#   command will be replaced with the specified one.
+
+set_fake_editor () {
+       echo "#!$SHELL_PATH" >fake-editor.sh
+       cat >> fake-editor.sh <<\EOF
+case "$1" in
+*/COMMIT_EDITMSG)
+       test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1"
+       test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1"
+       exit
+       ;;
+esac
+test -z "$EXPECT_COUNT" ||
+       test "$EXPECT_COUNT" = $(sed -e '/^#/d' -e '/^$/d' < "$1" | wc -l) ||
+       exit
+test -z "$FAKE_LINES" && exit
+grep -v '^#' < "$1" > "$1".tmp
+rm -f "$1"
+cat "$1".tmp
+action=pick
+for line in $FAKE_LINES; do
+       case $line in
+       squash|edit)
+               action="$line";;
+       *)
+               echo sed -n "${line}s/^pick/$action/p"
+               sed -n "${line}p" < "$1".tmp
+               sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1"
+               action=pick;;
+       esac
+done
+EOF
+
+       test_set_editor "$(pwd)/fake-editor.sh"
+       chmod a+x fake-editor.sh
+}
diff --git a/t/t0005-signals.sh b/t/t0005-signals.sh
new file mode 100755 (executable)
index 0000000..09f855a
--- /dev/null
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+test_description='signals work as we expect'
+. ./test-lib.sh
+
+cat >expect <<EOF
+three
+two
+one
+EOF
+
+test_expect_success 'sigchain works' '
+       test-sigchain >actual
+       case "$?" in
+       143) true ;; # POSIX w/ SIGTERM=15
+         3) true ;; # Windows
+         *) false ;;
+       esac &&
+       test_cmp expect actual
+'
+
+test_done
index 6e7501f352ee97636280357da54c50d73ceb0138..8336114f9820aa5972d38efe316de7d9e39bcc5c 100755 (executable)
@@ -8,36 +8,37 @@ test_description='Test various path utilities'
 . ./test-lib.sh
 
 norm_abs() {
-       test_expect_success "normalize absolute" \
-       "test \$(test-path-utils normalize_absolute_path '$1') = '$2'"
+       test_expect_success "normalize absolute: $1 => $2" \
+       "test \"\$(test-path-utils normalize_path_copy '$1')\" = '$2'"
 }
 
 ancestor() {
-       test_expect_success "longest ancestor" \
-       "test \$(test-path-utils longest_ancestor_length '$1' '$2') = '$3'"
+       test_expect_success "longest ancestor: $1 $2 => $3" \
+       "test \"\$(test-path-utils longest_ancestor_length '$1' '$2')\" = '$3'"
 }
 
-norm_abs "" /
+norm_abs "" ""
 norm_abs / /
 norm_abs // /
 norm_abs /// /
 norm_abs /. /
 norm_abs /./ /
-norm_abs /./.. /
-norm_abs /../. /
-norm_abs /./../.// /
+norm_abs /./.. ++failed++
+norm_abs /../. ++failed++
+norm_abs /./../.// ++failed++
 norm_abs /dir/.. /
 norm_abs /dir/sub/../.. /
+norm_abs /dir/sub/../../.. ++failed++
 norm_abs /dir /dir
-norm_abs /dir// /dir
+norm_abs /dir// /dir/
 norm_abs /./dir /dir
-norm_abs /dir/. /dir
-norm_abs /dir///./ /dir
-norm_abs /dir//sub/.. /dir
-norm_abs /dir/sub/../ /dir
-norm_abs //dir/sub/../. /dir
-norm_abs /dir/s1/../s2/ /dir/s2
-norm_abs /d1/s1///s2/..//../s3/ /d1/s3
+norm_abs /dir/. /dir/
+norm_abs /dir///./ /dir/
+norm_abs /dir//sub/.. /dir/
+norm_abs /dir/sub/../ /dir/
+norm_abs //dir/sub/../. /dir/
+norm_abs /dir/s1/../s2/ /dir/s2/
+norm_abs /d1/s1///s2/..//../s3/ /d1/s3/
 norm_abs /d1/s1//../s2/../../d2 /d2
 norm_abs /d1/.../d2 /d1/.../d2
 norm_abs /d1/..././../d2 /d1/d2
@@ -84,4 +85,8 @@ ancestor /foo/bar :://foo/.:: 4
 ancestor /foo/bar //foo/./::/bar 4
 ancestor /foo/bar ::/bar -1
 
+test_expect_success 'strip_path_suffix' '
+       test c:/msysgit = $(test-path-utils strip_path_suffix \
+               c:/msysgit/libexec//git-core libexec/git-core)
+'
 test_done
diff --git a/t/t0070-fundamental.sh b/t/t0070-fundamental.sh
new file mode 100755 (executable)
index 0000000..680d7d6
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+test_description='check that the most basic functions work
+
+
+Verify wrappers and compatibility functions.
+'
+
+. ./test-lib.sh
+
+test_expect_success 'character classes (isspace, isalpha etc.)' '
+       test-ctype
+'
+
+test_done
diff --git a/t/t0100-previous.sh b/t/t0100-previous.sh
new file mode 100755 (executable)
index 0000000..315b9b3
--- /dev/null
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+test_description='previous branch syntax @{-n}'
+
+. ./test-lib.sh
+
+test_expect_success 'branch -d @{-1}' '
+       test_commit A &&
+       git checkout -b junk &&
+       git checkout - &&
+       test "$(git symbolic-ref HEAD)" = refs/heads/master &&
+       git branch -d @{-1} &&
+       test_must_fail git rev-parse --verify refs/heads/junk
+'
+
+test_expect_success 'branch -d @{-12} when there is not enough switches yet' '
+       git reflog expire --expire=now &&
+       git checkout -b junk2 &&
+       git checkout - &&
+       test "$(git symbolic-ref HEAD)" = refs/heads/master &&
+       test_must_fail git branch -d @{-12} &&
+       git rev-parse --verify refs/heads/master
+'
+
+test_expect_success 'merge @{-1}' '
+       git checkout A &&
+       test_commit B &&
+       git checkout A &&
+       test_commit C &&
+       git branch -f master B &&
+       git branch -f other &&
+       git checkout other &&
+       git checkout master &&
+       git merge @{-1} &&
+       git cat-file commit HEAD | grep "Merge branch '\''other'\''"
+'
+
+test_expect_success 'merge @{-1} when there is not enough switches yet' '
+       git reflog expire --expire=now &&
+       git checkout -f master &&
+       git reset --hard B &&
+       git branch -f other C &&
+       git checkout other &&
+       git checkout master &&
+       test_must_fail git merge @{-12}
+'
+
+test_done
+
index 11b82f43dd0220c736dd269b2f6531a1381edf3a..3c06842d99a68ea37ce82546b4bfa0cd487b87d7 100755 (executable)
@@ -336,10 +336,10 @@ test_expect_success 'get bool variable with empty value' \
        'git config --bool emptyvalue.variable > output &&
         cmp output expect'
 
-git config > output 2>&1
-
-test_expect_success 'no arguments, but no crash' \
-       "test $? = 129 && grep usage output"
+test_expect_success 'no arguments, but no crash' '
+       test_must_fail git config >output 2>&1 &&
+       grep usage output
+'
 
 cat > .git/config << EOF
 [a.b]
@@ -373,7 +373,7 @@ EOF
 test_expect_success 'new variable inserts into proper section' 'cmp .git/config expect'
 
 test_expect_success 'alternative GIT_CONFIG (non-existing file should fail)' \
-       'git config --file non-existing-config -l; test $? != 0'
+       'test_must_fail git config --file non-existing-config -l'
 
 cat > other-config << EOF
 [ein]
diff --git a/t/t1401-symbolic-ref.sh b/t/t1401-symbolic-ref.sh
new file mode 100755 (executable)
index 0000000..7fa5f5b
--- /dev/null
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+test_description='basic symbolic-ref tests'
+. ./test-lib.sh
+
+# If the tests munging HEAD fail, they can break detection of
+# the git repo, meaning that further tests will operate on
+# the surrounding git repo instead of the trash directory.
+reset_to_sane() {
+       echo ref: refs/heads/foo >.git/HEAD
+}
+
+test_expect_success 'symbolic-ref writes HEAD' '
+       git symbolic-ref HEAD refs/heads/foo &&
+       echo ref: refs/heads/foo >expect &&
+       test_cmp expect .git/HEAD
+'
+
+test_expect_success 'symbolic-ref reads HEAD' '
+       echo refs/heads/foo >expect &&
+       git symbolic-ref HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'symbolic-ref refuses non-ref for HEAD' '
+       test_must_fail git symbolic-ref HEAD foo
+'
+reset_to_sane
+
+test_expect_success 'symbolic-ref refuses bare sha1' '
+       echo content >file && git add file && git commit -m one
+       test_must_fail git symbolic-ref HEAD `git rev-parse HEAD`
+'
+reset_to_sane
+
+test_done
diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh
new file mode 100755 (executable)
index 0000000..a22632f
--- /dev/null
@@ -0,0 +1,98 @@
+#!/bin/sh
+
+test_description='git fsck random collection of tests'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       test_commit A fileA one &&
+       git checkout HEAD^0 &&
+       test_commit B fileB two &&
+       git tag -d A B &&
+       git reflog expire --expire=now --all
+'
+
+test_expect_success 'HEAD is part of refs' '
+       test 0 = $(git fsck | wc -l)
+'
+
+test_expect_success 'loose objects borrowed from alternate are not missing' '
+       mkdir another &&
+       (
+               cd another &&
+               git init &&
+               echo ../../../.git/objects >.git/objects/info/alternates &&
+               test_commit C fileC one &&
+               git fsck >out &&
+               ! grep "missing blob" out
+       )
+'
+
+# Corruption tests follow.  Make sure to remove all traces of the
+# specific corruption you test afterwards, lest a later test trip over
+# it.
+
+test_expect_success 'object with bad sha1' '
+       sha=$(echo blob | git hash-object -w --stdin) &&
+       echo $sha &&
+       old=$(echo $sha | sed "s+^..+&/+") &&
+       new=$(dirname $old)/ffffffffffffffffffffffffffffffffffffff &&
+       sha="$(dirname $new)$(basename $new)"
+       mv .git/objects/$old .git/objects/$new &&
+       git update-index --add --cacheinfo 100644 $sha foo &&
+       tree=$(git write-tree) &&
+       cmt=$(echo bogus | git commit-tree $tree) &&
+       git update-ref refs/heads/bogus $cmt &&
+       (git fsck 2>out; true) &&
+       grep "$sha.*corrupt" out &&
+       rm -f .git/objects/$new &&
+       git update-ref -d refs/heads/bogus &&
+       git read-tree -u --reset HEAD
+'
+
+test_expect_success 'branch pointing to non-commit' '
+       git rev-parse HEAD^{tree} > .git/refs/heads/invalid &&
+       git fsck 2>out &&
+       grep "not a commit" out &&
+       git update-ref -d refs/heads/invalid
+'
+
+cat > invalid-tag <<EOF
+object ffffffffffffffffffffffffffffffffffffffff
+type commit
+tag invalid
+tagger T A Gger <tagger@example.com> 1234567890 -0000
+
+This is an invalid tag.
+EOF
+
+test_expect_failure 'tag pointing to nonexistent' '
+       tag=$(git hash-object -w --stdin < invalid-tag) &&
+       echo $tag > .git/refs/tags/invalid &&
+       git fsck --tags 2>out &&
+       cat out &&
+       grep "could not load tagged object" out &&
+       rm .git/refs/tags/invalid
+'
+
+cat > wrong-tag <<EOF
+object $(echo blob | git hash-object -w --stdin)
+type commit
+tag wrong
+tagger T A Gger <tagger@example.com> 1234567890 -0000
+
+This is an invalid tag.
+EOF
+
+test_expect_failure 'tag pointing to something else than its type' '
+       tag=$(git hash-object -w --stdin < wrong-tag) &&
+       echo $tag > .git/refs/tags/wrong &&
+       git fsck --tags 2>out &&
+       cat out &&
+       grep "some sane error message" out &&
+       rm .git/refs/tags/wrong
+'
+
+
+
+test_done
index 85da4caa7ed1b8bcaca7b21e218f2d1839d2db82..48ee07779d64147c3eb8325a3b3f9579f8ec41d8 100755 (executable)
@@ -26,21 +26,28 @@ test_rev_parse() {
        "test '$1' = \"\$(git rev-parse --show-prefix)\""
        shift
        [ $# -eq 0 ] && return
+
+       test_expect_success "$name: git-dir" \
+       "test '$1' = \"\$(git rev-parse --git-dir)\""
+       shift
+       [ $# -eq 0 ] && return
 }
 
-# label is-bare is-inside-git is-inside-work prefix
+# label is-bare is-inside-git is-inside-work prefix git-dir
+
+ROOT=$(pwd)
 
-test_rev_parse toplevel false false true ''
+test_rev_parse toplevel false false true '' .git
 
 cd .git || exit 1
-test_rev_parse .git/ false true false ''
+test_rev_parse .git/ false true false '' .
 cd objects || exit 1
-test_rev_parse .git/objects/ false true false ''
+test_rev_parse .git/objects/ false true false '' "$ROOT/.git"
 cd ../.. || exit 1
 
 mkdir -p sub/dir || exit 1
 cd sub/dir || exit 1
-test_rev_parse subdirectory false false true sub/dir/
+test_rev_parse subdirectory false false true sub/dir/ "$ROOT/.git"
 cd ../.. || exit 1
 
 git config core.bare true
index 27dc6c55d5f50a7fd30388b60230482bad6be2d8..f6a6f839a18de4c3775ea965f164d0d20f2bbe9b 100755 (executable)
@@ -92,13 +92,6 @@ cd sub/dir || exit 1
 test_rev_parse 'in repo.git/sub/dir' false true true sub/dir/
 cd ../../../.. || exit 1
 
-test_expect_success 'detecting gitdir when cwd is in a subdir of gitdir' '
-       (expected=$(pwd)/repo.git &&
-        cd repo.git/refs &&
-        unset GIT_DIR &&
-        test "$expected" = "$(git rev-parse --git-dir)")
-'
-
 test_expect_success 'repo finds its work tree' '
        (cd repo.git &&
         : > work/sub/dir/untracked &&
index 91b704a3a4ce6771071d19bd84aa228856fe6875..e377d48902cd5fd539a0ce4cb1fb32ceb8732632 100755 (executable)
@@ -93,13 +93,13 @@ GIT_CEILING_DIRECTORIES="$TRASH_ROOT/subdi"
 test_prefix subdir_ceil_at_subdi_slash "sub/dir/"
 
 
-GIT_CEILING_DIRECTORIES="foo:$TRASH_ROOT/sub"
+GIT_CEILING_DIRECTORIES="/foo:$TRASH_ROOT/sub"
 test_fail second_of_two
 
-GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub:bar"
+GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub:/bar"
 test_fail first_of_two
 
-GIT_CEILING_DIRECTORIES="foo:$TRASH_ROOT/sub:bar"
+GIT_CEILING_DIRECTORIES="/foo:$TRASH_ROOT/sub:/bar"
 test_fail second_of_three
 
 
diff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh
new file mode 100755 (executable)
index 0000000..d709ecf
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+test_description='test @{-N} syntax'
+
+. ./test-lib.sh
+
+
+make_commit () {
+       echo "$1" > "$1" &&
+       git add "$1" &&
+       git commit -m "$1"
+}
+
+
+test_expect_success 'setup' '
+
+       make_commit 1 &&
+       git branch side &&
+       make_commit 2 &&
+       make_commit 3 &&
+       git checkout side &&
+       make_commit 4 &&
+       git merge master &&
+       git checkout master
+
+'
+
+# 1 -- 2 -- 3 master
+#  \         \
+#   \         \
+#    --- 4 --- 5 side
+#
+# and 'side' should be the last branch
+
+test_rev_equivalent () {
+
+       git rev-parse "$1" > expect &&
+       git rev-parse "$2" > output &&
+       test_cmp expect output
+
+}
+
+test_expect_success '@{-1} works' '
+       test_rev_equivalent side @{-1}
+'
+
+test_expect_success '@{-1}~2 works' '
+       test_rev_equivalent side~2 @{-1}~2
+'
+
+test_expect_success '@{-1}^2 works' '
+       test_rev_equivalent side^2 @{-1}^2
+'
+
+test_expect_success '@{-1}@{1} works' '
+       test_rev_equivalent side@{1} @{-1}@{1}
+'
+
+test_expect_success '@{-2} works' '
+       test_rev_equivalent master @{-2}
+'
+
+test_expect_success '@{-3} fails' '
+       test_must_fail git rev-parse @{-3}
+'
+
+test_done
+
+
diff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh
new file mode 100755 (executable)
index 0000000..87b30a2
--- /dev/null
@@ -0,0 +1,94 @@
+#!/bin/sh
+
+test_description='checkout can switch to last branch'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       echo hello >world &&
+       git add world &&
+       git commit -m initial &&
+       git branch other &&
+       echo "hello again" >>world &&
+       git add world &&
+       git commit -m second
+'
+
+test_expect_success '"checkout -" does not work initially' '
+       test_must_fail git checkout -
+'
+
+test_expect_success 'first branch switch' '
+       git checkout other
+'
+
+test_expect_success '"checkout -" switches back' '
+       git checkout - &&
+       test "z$(git symbolic-ref HEAD)" = "zrefs/heads/master"
+'
+
+test_expect_success '"checkout -" switches forth' '
+       git checkout - &&
+       test "z$(git symbolic-ref HEAD)" = "zrefs/heads/other"
+'
+
+test_expect_success 'detach HEAD' '
+       git checkout $(git rev-parse HEAD)
+'
+
+test_expect_success '"checkout -" attaches again' '
+       git checkout - &&
+       test "z$(git symbolic-ref HEAD)" = "zrefs/heads/other"
+'
+
+test_expect_success '"checkout -" detaches again' '
+       git checkout - &&
+       test "z$(git rev-parse HEAD)" = "z$(git rev-parse other)" &&
+       test_must_fail git symbolic-ref HEAD
+'
+
+test_expect_success 'more switches' '
+       for i in 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
+       do
+               git checkout -b branch$i
+       done
+'
+
+more_switches () {
+       for i in 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
+       do
+               git checkout branch$i
+       done
+}
+
+test_expect_success 'switch to the last' '
+       more_switches &&
+       git checkout @{-1} &&
+       test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch2"
+'
+
+test_expect_success 'switch to second from the last' '
+       more_switches &&
+       git checkout @{-2} &&
+       test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch3"
+'
+
+test_expect_success 'switch to third from the last' '
+       more_switches &&
+       git checkout @{-3} &&
+       test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch4"
+'
+
+test_expect_success 'switch to fourth from the last' '
+       more_switches &&
+       git checkout @{-4} &&
+       test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch5"
+'
+
+test_expect_success 'switch to twelfth from the last' '
+       more_switches &&
+       git checkout @{-12} &&
+       test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch13"
+'
+
+test_done
index cd9231cf614c4326518632e514ccc68a5dc59223..b2ddf5ace3581bc2a7056c61b9d43499b2657b65 100755 (executable)
@@ -12,7 +12,7 @@ and issues a git add -u with path limiting on "dir" to add
 only the updates to dir/sub.
 
 Also tested are "git add -u" without limiting, and "git add -u"
-without contents changes.'
+without contents changes, and other conditions'
 
 . ./test-lib.sh
 
@@ -128,4 +128,52 @@ test_expect_success 'add -n -u should not add but just report' '
 
 '
 
+test_expect_success 'add -u resolves unmerged paths' '
+       git reset --hard &&
+       one=$(echo 1 | git hash-object -w --stdin) &&
+       two=$(echo 2 | git hash-object -w --stdin) &&
+       three=$(echo 3 | git hash-object -w --stdin) &&
+       {
+               for path in path1 path2
+               do
+                       echo "100644 $one 1     $path"
+                       echo "100644 $two 2     $path"
+                       echo "100644 $three 3   $path"
+               done
+               echo "100644 $one 1     path3"
+               echo "100644 $one 1     path4"
+               echo "100644 $one 3     path5"
+               echo "100644 $one 3     path6"
+       } |
+       git update-index --index-info &&
+       echo 3 >path1 &&
+       echo 2 >path3 &&
+       echo 2 >path5 &&
+       git add -u &&
+       git ls-files -s "path?" >actual &&
+       {
+               echo "100644 $three 0   path1"
+               echo "100644 $one 1     path3"
+               echo "100644 $one 1     path4"
+               echo "100644 $one 3     path5"
+               echo "100644 $one 3     path6"
+       } >expect &&
+       test_cmp expect actual &&
+
+       # Bonus tests.  Explicit resolving
+       git add path3 path5 &&
+       test_must_fail git add path4 &&
+       test_must_fail git add path6 &&
+       git rm path4 &&
+       git rm path6 &&
+
+       git ls-files -s "path?" >actual &&
+       {
+               echo "100644 $three 0   path1"
+               echo "100644 $two 0     path3"
+               echo "100644 $two 0     path5"
+       } >expect
+
+'
+
 test_done
index e42cbfe6c61951c6887a363cb668d26a7adcf20c..293dc353b1601e137f86dfe441fff57b96c92753 100755 (executable)
@@ -10,12 +10,12 @@ test_cd_to_toplevel () {
                        cd '"'$1'"' &&
                        . git-sh-setup &&
                        cd_to_toplevel &&
-                       [ "$(unset PWD; /bin/pwd)" = "$TOPLEVEL" ]
+                       [ "$(pwd -P)" = "$TOPLEVEL" ]
                )
        '
 }
 
-TOPLEVEL="$(unset PWD; /bin/pwd)/repo"
+TOPLEVEL="$(pwd -P)/repo"
 mkdir -p repo/sub/dir
 mv .git repo/
 SUBDIRECTORY_OK=1
index 85aef12a113fed8164a085a19ce5c794ab96ff6f..c65bca838881938e2f924cfe62b2c303dbd5b1cd 100755 (executable)
@@ -19,6 +19,9 @@ do
     >$dir/a.$i
   done
 done
+>"#ignore1"
+>"#ignore2"
+>"#hidden"
 
 cat >expect <<EOF
 a.2
@@ -42,6 +45,9 @@ three/a.8
 EOF
 
 echo '.gitignore
+\#ignore1
+\#ignore2*
+\#hid*n
 output
 expect
 .gitignore
@@ -79,9 +85,10 @@ test_expect_success \
        >output &&
      test_cmp expect output'
 
-cat > excludes-file << EOF
+cat > excludes-file <<\EOF
 *.[1-8]
 e*
+\#*
 EOF
 
 git config core.excludesFile excludes-file
diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh
new file mode 100755 (executable)
index 0000000..809d1c4
--- /dev/null
@@ -0,0 +1,81 @@
+#!/bin/sh
+
+test_description='git branch display tests'
+. ./test-lib.sh
+
+test_expect_success 'make commits' '
+       echo content >file &&
+       git add file &&
+       git commit -m one &&
+       echo content >>file &&
+       git commit -a -m two
+'
+
+test_expect_success 'make branches' '
+       git branch branch-one
+       git branch branch-two HEAD^
+'
+
+test_expect_success 'make remote branches' '
+       git update-ref refs/remotes/origin/branch-one branch-one
+       git update-ref refs/remotes/origin/branch-two branch-two
+       git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/branch-one
+'
+
+cat >expect <<'EOF'
+  branch-one
+  branch-two
+* master
+EOF
+test_expect_success 'git branch shows local branches' '
+       git branch >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+  origin/HEAD -> origin/branch-one
+  origin/branch-one
+  origin/branch-two
+EOF
+test_expect_success 'git branch -r shows remote branches' '
+       git branch -r >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+  branch-one
+  branch-two
+* master
+  remotes/origin/HEAD -> origin/branch-one
+  remotes/origin/branch-one
+  remotes/origin/branch-two
+EOF
+test_expect_success 'git branch -a shows local and remote branches' '
+       git branch -a >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+two
+one
+two
+EOF
+test_expect_success 'git branch -v shows branch summaries' '
+       git branch -v >tmp &&
+       awk "{print \$NF}" <tmp >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+* (no branch)
+  branch-one
+  branch-two
+  master
+EOF
+test_expect_success 'git branch shows detached HEAD properly' '
+       git checkout HEAD^0 &&
+       git branch >actual &&
+       test_cmp expect actual
+'
+
+test_done
index b7a670ef401429a50eb97e5f874c9e2c3897fd7d..be7ae5a0041a0d06b40a7152d4c5d495a842a87f 100755 (executable)
@@ -14,7 +14,8 @@ export GIT_AUTHOR_EMAIL
 
 test_expect_success \
     'prepare repository with topic branches' \
-    'echo First > A &&
+    'git config core.logAllRefUpdates true &&
+     echo First > A &&
      git update-index --add A &&
      git commit -m "Add A." &&
      git checkout -b my-topic-branch &&
@@ -47,6 +48,10 @@ test_expect_success \
     'the rebase operation should not have destroyed author information' \
     '! (git log | grep "Author:" | grep "<>")'
 
+test_expect_success 'HEAD was detached during rebase' '
+     test $(git rev-parse HEAD@{1}) != $(git rev-parse my-topic-branch@{1})
+'
+
 test_expect_success 'rebase after merge master' '
      git reset --hard topic &&
      git merge master &&
@@ -84,4 +89,10 @@ test_expect_success 'rebase a single mode change' '
      GIT_TRACE=1 git rebase master
 '
 
+test_expect_success 'Show verbose error when HEAD could not be detached' '
+     : > B &&
+     test_must_fail git rebase topic 2> output.err > output.out &&
+     grep "Untracked working tree file .B. would be overwritten" output.err
+'
+
 test_done
index 2cc8e7abe1b9244b2d6520cdbb0e0769e2a6d986..603b003edff6d32fe8725f119778658c76c806fb 100755 (executable)
@@ -10,6 +10,10 @@ that the result still makes sense.
 '
 . ./test-lib.sh
 
+. ../lib-rebase.sh
+
+set_fake_editor
+
 # set up two branches like this:
 #
 # A - B - C - D - E
@@ -61,39 +65,6 @@ test_expect_success 'setup' '
        git tag I
 '
 
-echo "#!$SHELL_PATH" >fake-editor.sh
-cat >> fake-editor.sh <<\EOF
-case "$1" in
-*/COMMIT_EDITMSG)
-       test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1"
-       test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1"
-       exit
-       ;;
-esac
-test -z "$EXPECT_COUNT" ||
-       test "$EXPECT_COUNT" = $(sed -e '/^#/d' -e '/^$/d' < "$1" | wc -l) ||
-       exit
-test -z "$FAKE_LINES" && exit
-grep -v '^#' < "$1" > "$1".tmp
-rm -f "$1"
-cat "$1".tmp
-action=pick
-for line in $FAKE_LINES; do
-       case $line in
-       squash|edit)
-               action="$line";;
-       *)
-               echo sed -n "${line}s/^pick/$action/p"
-               sed -n "${line}p" < "$1".tmp
-               sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1"
-               action=pick;;
-       esac
-done
-EOF
-
-test_set_editor "$(pwd)/fake-editor.sh"
-chmod a+x fake-editor.sh
-
 test_expect_success 'no changes are a nop' '
        git rebase -i F &&
        test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" &&
@@ -462,4 +433,30 @@ test_expect_success 'do "noop" when there is nothing to cherry-pick' '
 
 '
 
+test_expect_success 'submodule rebase setup' '
+       git checkout A &&
+       mkdir sub &&
+       (
+               cd sub && git init && >elif &&
+               git add elif && git commit -m "submodule initial"
+       ) &&
+       echo 1 >file1 &&
+       git add file1 sub
+       test_tick &&
+       git commit -m "One" &&
+       echo 2 >file1 &&
+       test_tick &&
+       git commit -a -m "Two" &&
+       (
+               cd sub && echo 3 >elif &&
+               git commit -a -m "submodule second"
+       ) &&
+       test_tick &&
+       git commit -a -m "Three changes submodule"
+'
+
+test_expect_success 'submodule rebase -i' '
+       FAKE_LINES="1 squash 2 3" git rebase -i A
+'
+
 test_done
index 539108094345e3e0ba4cf03fc20a5ca6486fa230..85fc7c4af8cebdb50a7fa294b274bb2e7988997b 100755 (executable)
@@ -22,7 +22,8 @@ test_expect_success setup '
        git checkout topic &&
        quick_one A &&
        quick_one B &&
-       quick_one Z
+       quick_one Z &&
+       git tag start
 
 '
 
@@ -41,4 +42,24 @@ test_expect_success 'rebase -m' '
 
 '
 
+test_expect_success 'rebase --stat' '
+        git reset --hard start
+        git rebase --stat master >diffstat.txt &&
+        grep "^ fileX |  *1 +$" diffstat.txt
+'
+
+test_expect_success 'rebase w/config rebase.stat' '
+        git reset --hard start
+        git config rebase.stat true &&
+        git rebase master >diffstat.txt &&
+        grep "^ fileX |  *1 +$" diffstat.txt
+'
+
+test_expect_success 'rebase -n overrides config rebase.stat config' '
+        git reset --hard start
+        git config rebase.stat true &&
+        git rebase -n master >diffstat.txt &&
+        ! grep "^ fileX |  *1 +$" diffstat.txt
+'
+
 test_done
index 1f1b85067773e40262c2ace48a67b60179663931..098b75507bf2ef6906f3f4fd4c532bb4c55ce7bd 100755 (executable)
@@ -118,7 +118,11 @@ test_expect_success 'pre-rebase hook stops rebase (1)' '
 test_expect_success 'pre-rebase hook stops rebase (2)' '
        git checkout test &&
        git reset --hard side &&
-       EDITOR=true test_must_fail git rebase -i master &&
+       (
+               EDITOR=:
+               export EDITOR
+               test_must_fail git rebase -i master
+       ) &&
        test "z$(git symbolic-ref HEAD)" = zrefs/heads/test &&
        test 0 = $(git rev-list HEAD...side | wc -l)
 '
index 5816415aafe02f03ede5e1afb6f2e92dedc4b3da..c49143a1a45d6949253e2daf0e57f60029041e60 100755 (executable)
@@ -22,47 +22,17 @@ rewritten.
 # where B, D and G touch the same file.
 
 test_expect_success 'setup' '
-       : > file1 &&
-       git add file1 &&
-       test_tick &&
-       git commit -m A &&
-       git tag A &&
-       echo 1 > file1 &&
-       test_tick &&
-       git commit -m B file1 &&
-       : > file2 &&
-       git add file2 &&
-       test_tick &&
-       git commit -m C &&
-       echo 2 > file1 &&
-       test_tick &&
-       git commit -m D file1 &&
-       : > file3 &&
-       git add file3 &&
-       test_tick &&
-       git commit -m E &&
-       git tag E &&
-       git checkout -b branch1 A &&
-       : > file4 &&
-       git add file4 &&
-       test_tick &&
-       git commit -m F &&
-       git tag F &&
-       echo 3 > file1 &&
-       test_tick &&
-       git commit -m G file1 &&
-       git tag G &&
-       : > file5 &&
-       git add file5 &&
-       test_tick &&
-       git commit -m H &&
-       git tag H &&
-       git checkout -b branch2 F &&
-       : > file6 &&
-       git add file6 &&
-       test_tick &&
-       git commit -m I &&
-       git tag I
+       test_commit A file1 &&
+       test_commit B file1 1 &&
+       test_commit C file2 &&
+       test_commit D file1 2 &&
+       test_commit E file3 &&
+       git checkout A &&
+       test_commit F file4 &&
+       test_commit G file1 3 &&
+       test_commit H file5 &&
+       git checkout F &&
+       test_commit I file6
 '
 
 # A - B - C - D - E
@@ -72,68 +42,44 @@ test_expect_success 'setup' '
 #         I -- G2 -- J -- K           I -- K
 # G2 = same changes as G
 test_expect_success 'skip same-resolution merges with -p' '
-       git checkout branch1 &&
+       git checkout H &&
        ! git merge E &&
-       echo 23 > file1 &&
-       git add file1 &&
-       git commit -m L &&
-       git checkout branch2 &&
-       echo 3 > file1 &&
-       git commit -a -m G2 &&
+       test_commit L file1 23 &&
+       git checkout I &&
+       test_commit G2 file1 3 &&
        ! git merge E &&
-       echo 23 > file1 &&
-       git add file1 &&
-       git commit -m J &&
-       echo file7 > file7 &&
-       git add file7 &&
-       git commit -m K &&
-       GIT_EDITOR=: git rebase -i -p branch1 &&
-       test $(git rev-parse branch2^^) = $(git rev-parse branch1) &&
+       test_commit J file1 23 &&
+       test_commit K file7 file7 &&
+       git rebase -i -p L &&
+       test $(git rev-parse HEAD^^) = $(git rev-parse L) &&
        test "23" = "$(cat file1)" &&
-       test "" = "$(cat file6)" &&
-       test "file7" = "$(cat file7)" &&
-
-       git checkout branch1 &&
-       git reset --hard H &&
-       git checkout branch2 &&
-       git reset --hard I
+       test "I" = "$(cat file6)" &&
+       test "file7" = "$(cat file7)"
 '
 
 # A - B - C - D - E
 #   \             \ \
-#     F - G - H -- L \        -->   L
-#       \            |               \
-#         I -- G2 -- J -- K           I -- G2 -- K
+#     F - G - H -- L2 \        -->   L2
+#       \             |                \
+#         I -- G3 --- J2 -- K2           I -- G3 -- K2
 # G2 = different changes as G
 test_expect_success 'keep different-resolution merges with -p' '
-       git checkout branch1 &&
+       git checkout H &&
        ! git merge E &&
-       echo 23 > file1 &&
-       git add file1 &&
-       git commit -m L &&
-       git checkout branch2 &&
-       echo 4 > file1 &&
-       git commit -a -m G2 &&
+       test_commit L2 file1 23 &&
+       git checkout I &&
+       test_commit G3 file1 4 &&
        ! git merge E &&
-       echo 24 > file1 &&
-       git add file1 &&
-       git commit -m J &&
-       echo file7 > file7 &&
-       git add file7 &&
-       git commit -m K &&
-       ! GIT_EDITOR=: git rebase -i -p branch1 &&
+       test_commit J2 file1 24 &&
+       test_commit K2 file7 file7 &&
+       test_must_fail git rebase -i -p L2 &&
        echo 234 > file1 &&
        git add file1 &&
-       GIT_EDITOR=: git rebase --continue &&
-       test $(git rev-parse branch2^^^) = $(git rev-parse branch1) &&
+       git rebase --continue &&
+       test $(git rev-parse HEAD^^^) = $(git rev-parse L2) &&
        test "234" = "$(cat file1)" &&
-       test "" = "$(cat file6)" &&
-       test "file7" = "$(cat file7)" &&
-
-       git checkout branch1 &&
-       git reset --hard H &&
-       git checkout branch2 &&
-       git reset --hard I
+       test "I" = "$(cat file6)" &&
+       test "file7" = "$(cat file7)"
 '
 
 test_done
index aacfaae843395e383d999e570a6bc9942e4c1c0b..6533505218a51db369d03357603c2ad1926ce448 100755 (executable)
@@ -5,44 +5,14 @@
 
 test_description='git rebase preserve merges
 
-This test runs git rebase with and tries to squash a commit from after a merge
-to before the merge.
+This test runs git rebase with -p and tries to squash a commit from after
+a merge to before the merge.
 '
 . ./test-lib.sh
 
-# Copy/paste from t3404-rebase-interactive.sh
-echo "#!$SHELL_PATH" >fake-editor.sh
-cat >> fake-editor.sh <<\EOF
-case "$1" in
-*/COMMIT_EDITMSG)
-       test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1"
-       test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1"
-       exit
-       ;;
-esac
-test -z "$EXPECT_COUNT" ||
-       test "$EXPECT_COUNT" = $(sed -e '/^#/d' -e '/^$/d' < "$1" | wc -l) ||
-       exit
-test -z "$FAKE_LINES" && exit
-grep -v '^#' < "$1" > "$1".tmp
-rm -f "$1"
-cat "$1".tmp
-action=pick
-for line in $FAKE_LINES; do
-       case $line in
-       squash|edit)
-               action="$line";;
-       *)
-               echo sed -n "${line}s/^pick/$action/p"
-               sed -n "${line}p" < "$1".tmp
-               sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1"
-               action=pick;;
-       esac
-done
-EOF
+. ../lib-rebase.sh
 
-test_set_editor "$(pwd)/fake-editor.sh"
-chmod a+x fake-editor.sh
+set_fake_editor
 
 # set up two branches like this:
 #
@@ -51,27 +21,13 @@ chmod a+x fake-editor.sh
 #        -- C1 --
 
 test_expect_success 'setup' '
-       touch a &&
-       touch b &&
-       git add a &&
-       git commit -m A1 &&
-       git tag A1
-       git add b &&
-       git commit -m B1 &&
-       git tag B1 &&
-       git checkout -b branch &&
-       touch c &&
-       git add c &&
-       git commit -m C1 &&
-       git checkout master &&
-       touch d &&
-       git add d &&
-       git commit -m D1 &&
-       git merge branch &&
-       touch f &&
-       git add f &&
-       git commit -m F1 &&
-       git tag F1
+       test_commit A1 &&
+       test_commit B1 &&
+       test_commit C1 &&
+       git reset --hard B1 &&
+       test_commit D1 &&
+       test_merge E1 C1 &&
+       test_commit F1
 '
 
 # Should result in:
@@ -82,7 +38,7 @@ test_expect_success 'setup' '
 #
 test_expect_success 'squash F1 into D1' '
        FAKE_LINES="1 squash 3 2" git rebase -i -p B1 &&
-       test "$(git rev-parse HEAD^2)" = "$(git rev-parse branch)" &&
+       test "$(git rev-parse HEAD^2)" = "$(git rev-parse C1)" &&
        test "$(git rev-parse HEAD~2)" = "$(git rev-parse B1)" &&
        git tag E2
 '
@@ -100,32 +56,15 @@ test_expect_success 'squash F1 into D1' '
 # And rebase G1..M1 onto E2
 
 test_expect_success 'rebase two levels of merge' '
-       git checkout -b branch2 A1 &&
-       touch g &&
-       git add g &&
-       git commit -m G1 &&
-       git checkout -b branch3 &&
-       touch h
-       git add h &&
-       git commit -m H1 &&
-       git checkout -b branch4 &&
-       touch i &&
-       git add i &&
-       git commit -m I1 &&
-       git tag I1 &&
-       git checkout branch3 &&
-       touch j &&
-       git add j &&
-       git commit -m J1 &&
-       git merge I1 --no-commit &&
-       git commit -m K1 &&
-       git tag K1 &&
-       git checkout branch2 &&
-       touch l &&
-       git add l &&
-       git commit -m L1 &&
-       git merge K1 --no-commit &&
-       git commit -m M1 &&
+       test_commit G1 &&
+       test_commit H1 &&
+       test_commit I1 &&
+       git checkout -b branch3 H1 &&
+       test_commit J1 &&
+       test_merge K1 I1 &&
+       git checkout -b branch2 G1 &&
+       test_commit L1 &&
+       test_merge M1 K1 &&
        GIT_EDITOR=: git rebase -i -p E2 &&
        test "$(git rev-parse HEAD~3)" = "$(git rev-parse E2)" &&
        test "$(git rev-parse HEAD~2)" = "$(git rev-parse HEAD^2^2~2)" &&
diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh
new file mode 100755 (executable)
index 0000000..5869061
--- /dev/null
@@ -0,0 +1,280 @@
+#!/bin/sh
+
+test_description='git rebase --root
+
+Tests if git rebase --root --onto <newparent> can rebase the root commit.
+'
+. ./test-lib.sh
+
+log_with_names () {
+       git rev-list --topo-order --parents --pretty="tformat:%s" HEAD |
+       git name-rev --stdin --name-only --refs=refs/heads/$1
+}
+
+
+test_expect_success 'prepare repository' '
+       test_commit 1 A &&
+       test_commit 2 A &&
+       git symbolic-ref HEAD refs/heads/other &&
+       rm .git/index &&
+       test_commit 3 B &&
+       test_commit 1b A 1 &&
+       test_commit 4 B
+'
+
+test_expect_success 'rebase --root expects --onto' '
+       test_must_fail git rebase --root
+'
+
+test_expect_success 'setup pre-rebase hook' '
+       mkdir -p .git/hooks &&
+       cat >.git/hooks/pre-rebase <<EOF &&
+#!$SHELL_PATH
+echo "\$1,\$2" >.git/PRE-REBASE-INPUT
+EOF
+       chmod +x .git/hooks/pre-rebase
+'
+cat > expect <<EOF
+4
+3
+2
+1
+EOF
+
+test_expect_success 'rebase --root --onto <newbase>' '
+       git checkout -b work &&
+       git rebase --root --onto master &&
+       git log --pretty=tformat:"%s" > rebased &&
+       test_cmp expect rebased
+'
+
+test_expect_success 'pre-rebase got correct input (1)' '
+       test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,
+'
+
+test_expect_success 'rebase --root --onto <newbase> <branch>' '
+       git branch work2 other &&
+       git rebase --root --onto master work2 &&
+       git log --pretty=tformat:"%s" > rebased2 &&
+       test_cmp expect rebased2
+'
+
+test_expect_success 'pre-rebase got correct input (2)' '
+       test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work2
+'
+
+test_expect_success 'rebase -i --root --onto <newbase>' '
+       git checkout -b work3 other &&
+       git rebase -i --root --onto master &&
+       git log --pretty=tformat:"%s" > rebased3 &&
+       test_cmp expect rebased3
+'
+
+test_expect_success 'pre-rebase got correct input (3)' '
+       test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,
+'
+
+test_expect_success 'rebase -i --root --onto <newbase> <branch>' '
+       git branch work4 other &&
+       git rebase -i --root --onto master work4 &&
+       git log --pretty=tformat:"%s" > rebased4 &&
+       test_cmp expect rebased4
+'
+
+test_expect_success 'pre-rebase got correct input (4)' '
+       test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work4
+'
+
+test_expect_success 'rebase -i -p with linear history' '
+       git checkout -b work5 other &&
+       git rebase -i -p --root --onto master &&
+       git log --pretty=tformat:"%s" > rebased5 &&
+       test_cmp expect rebased5
+'
+
+test_expect_success 'pre-rebase got correct input (5)' '
+       test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,
+'
+
+test_expect_success 'set up merge history' '
+       git checkout other^ &&
+       git checkout -b side &&
+       test_commit 5 C &&
+       git checkout other &&
+       git merge side
+'
+
+cat > expect-side <<'EOF'
+commit work6 work6~1 work6^2
+Merge branch 'side' into other
+commit work6^2 work6~2
+5
+commit work6~1 work6~2
+4
+commit work6~2 work6~3
+3
+commit work6~3 work6~4
+2
+commit work6~4
+1
+EOF
+
+test_expect_success 'rebase -i -p with merge' '
+       git checkout -b work6 other &&
+       git rebase -i -p --root --onto master &&
+       log_with_names work6 > rebased6 &&
+       test_cmp expect-side rebased6
+'
+
+test_expect_success 'set up second root and merge' '
+       git symbolic-ref HEAD refs/heads/third &&
+       rm .git/index &&
+       rm A B C &&
+       test_commit 6 D &&
+       git checkout other &&
+       git merge third
+'
+
+cat > expect-third <<'EOF'
+commit work7 work7~1 work7^2
+Merge branch 'third' into other
+commit work7^2 work7~4
+6
+commit work7~1 work7~2 work7~1^2
+Merge branch 'side' into other
+commit work7~1^2 work7~3
+5
+commit work7~2 work7~3
+4
+commit work7~3 work7~4
+3
+commit work7~4 work7~5
+2
+commit work7~5
+1
+EOF
+
+test_expect_success 'rebase -i -p with two roots' '
+       git checkout -b work7 other &&
+       git rebase -i -p --root --onto master &&
+       log_with_names work7 > rebased7 &&
+       test_cmp expect-third rebased7
+'
+
+test_expect_success 'setup pre-rebase hook that fails' '
+       mkdir -p .git/hooks &&
+       cat >.git/hooks/pre-rebase <<EOF &&
+#!$SHELL_PATH
+false
+EOF
+       chmod +x .git/hooks/pre-rebase
+'
+
+test_expect_success 'pre-rebase hook stops rebase' '
+       git checkout -b stops1 other &&
+       test_must_fail git rebase --root --onto master &&
+       test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops1
+       test 0 = $(git rev-list other...stops1 | wc -l)
+'
+
+test_expect_success 'pre-rebase hook stops rebase -i' '
+       git checkout -b stops2 other &&
+       test_must_fail git rebase --root --onto master &&
+       test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops2
+       test 0 = $(git rev-list other...stops2 | wc -l)
+'
+
+test_expect_success 'remove pre-rebase hook' '
+       rm -f .git/hooks/pre-rebase
+'
+
+test_expect_success 'set up a conflict' '
+       git checkout master &&
+       echo conflict > B &&
+       git add B &&
+       git commit -m conflict
+'
+
+test_expect_success 'rebase --root with conflict (first part)' '
+       git checkout -b conflict1 other &&
+       test_must_fail git rebase --root --onto master &&
+       git ls-files -u | grep "B$"
+'
+
+test_expect_success 'fix the conflict' '
+       echo 3 > B &&
+       git add B
+'
+
+cat > expect-conflict <<EOF
+6
+5
+4
+3
+conflict
+2
+1
+EOF
+
+test_expect_success 'rebase --root with conflict (second part)' '
+       git rebase --continue &&
+       git log --pretty=tformat:"%s" > conflict1 &&
+       test_cmp expect-conflict conflict1
+'
+
+test_expect_success 'rebase -i --root with conflict (first part)' '
+       git checkout -b conflict2 other &&
+       test_must_fail git rebase -i --root --onto master &&
+       git ls-files -u | grep "B$"
+'
+
+test_expect_success 'fix the conflict' '
+       echo 3 > B &&
+       git add B
+'
+
+test_expect_success 'rebase -i --root with conflict (second part)' '
+       git rebase --continue &&
+       git log --pretty=tformat:"%s" > conflict2 &&
+       test_cmp expect-conflict conflict2
+'
+
+cat >expect-conflict-p <<\EOF
+commit conflict3 conflict3~1 conflict3^2
+Merge branch 'third' into other
+commit conflict3^2 conflict3~4
+6
+commit conflict3~1 conflict3~2 conflict3~1^2
+Merge branch 'side' into other
+commit conflict3~1^2 conflict3~3
+5
+commit conflict3~2 conflict3~3
+4
+commit conflict3~3 conflict3~4
+3
+commit conflict3~4 conflict3~5
+conflict
+commit conflict3~5 conflict3~6
+2
+commit conflict3~6
+1
+EOF
+
+test_expect_success 'rebase -i -p --root with conflict (first part)' '
+       git checkout -b conflict3 other &&
+       test_must_fail git rebase -i -p --root --onto master &&
+       git ls-files -u | grep "B$"
+'
+
+test_expect_success 'fix the conflict' '
+       echo 3 > B &&
+       git add B
+'
+
+test_expect_success 'rebase -i -p --root with conflict (second part)' '
+       git rebase --continue &&
+       log_with_names conflict3 >out &&
+       test_cmp expect-conflict-p out
+'
+
+test_done
diff --git a/t/t3505-cherry-pick-empty.sh b/t/t3505-cherry-pick-empty.sh
new file mode 100755 (executable)
index 0000000..9aaeabd
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+test_description='test cherry-picking an empty commit'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+       echo first > file1 &&
+       git add file1 &&
+       test_tick &&
+       git commit -m "first" &&
+
+       git checkout -b empty-branch &&
+       test_tick &&
+       git commit --allow-empty -m "empty"
+
+'
+
+test_expect_code 1 'cherry-pick an empty commit' '
+
+       git checkout master &&
+       git cherry-pick empty-branch
+
+'
+
+test_expect_success 'index lockfile was removed' '
+
+       test ! -f .git/index.lock
+
+'
+
+test_done
index cc3681f16118ca70ae9f65a27ccd6f354a6deee1..18695ce8218a7b383258eeb0bad84b4d4bde45be 100755 (executable)
@@ -258,4 +258,12 @@ test_expect_success \
     git diff-tree -r -R $tree_A $tree_B >.test-b &&
     cmp -s .test-a .test-b'
 
+test_expect_success \
+    'diff can read from stdin' \
+    'test_must_fail git diff --no-index -- MN - < NN |
+        grep -v "^index" | sed "s#/-#/NN#" >.test-a &&
+    test_must_fail git diff --no-index -- MN NN |
+        grep -v "^index" >.test-b &&
+    test_cmp .test-a .test-b'
+
 test_done
index 02efecae3ad06e5a62553e990fc0934dd0c65eab..9055c8b318aa8cfa8b89fd19b20e94a7435ee155 100755 (executable)
@@ -82,4 +82,11 @@ test_expect_success \
     git diff-index -M -p $tree > current &&
     compare_diff_patch current expected'
 
+test_expect_success \
+    'diff symlinks with non-existing targets' \
+    'ln -s narf pinky &&
+    ln -s take\ over brain &&
+    test_must_fail git diff --no-index pinky brain > output 2> output.err &&
+    grep narf output &&
+    ! grep error output.err'
 test_done
index 3cf5b5c4ea05d861d75aa146a52d7d273dcaa4cb..f64aa48d24f2f5704d07b9285c94ba983f7d92ac 100755 (executable)
@@ -87,7 +87,7 @@ nul_to_q() {
 
 test_expect_success 'diff --no-index with binary creation' '
        echo Q | q_to_nul >binary &&
-       (:# hide error code from diff, which just indicates differences
+       (: hide error code from diff, which just indicates differences
         git diff --binary --no-index /dev/null binary >current ||
         true
        ) &&
index 9c709022efb1b553ef53b3100d39e852117cbeda..9cd5a6e68563aede0e69270421b56d1904c34165 100755 (executable)
@@ -207,6 +207,10 @@ log --root -c --patch-with-stat --summary master
 log --root --cc --patch-with-stat --summary master
 log -SF master
 log -SF -p master
+log --decorate --all
+
+rev-list --parents HEAD
+rev-list --children HEAD
 
 whatchanged master
 whatchanged -p master
@@ -268,6 +272,7 @@ diff --no-index --name-status dir2 dir
 diff --no-index --name-status -- dir2 dir
 diff --no-index dir dir3
 diff master master^ side
+diff --dirstat master~1 master~2
 EOF
 
 test_done
diff --git a/t/t4013/diff.diff_--dirstat_master~1_master~2 b/t/t4013/diff.diff_--dirstat_master~1_master~2
new file mode 100644 (file)
index 0000000..b672e1c
--- /dev/null
@@ -0,0 +1,3 @@
+$ git diff --dirstat master~1 master~2
+  40.0% dir/
+$
diff --git a/t/t4013/diff.log_--decorate_--all b/t/t4013/diff.log_--decorate_--all
new file mode 100644 (file)
index 0000000..12da8ac
--- /dev/null
@@ -0,0 +1,34 @@
+$ git log --decorate --all
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (refs/heads/master)
+Merge: 9a6d494 c7a2ab9
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a (refs/heads/side)
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a (refs/heads/initial)
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+$
index 3ceb8e73c5d4e963a2c08eddf41197431ad52178..bd7f5c0f70571d0aa0f9b9c50a343a322831976f 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --patch-with-stat --summary master -- dir/
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index 43d77761f988b7f43f46a5d8fd85d406ef9caa42..14595a614c362da4515f8d5b783bb0e0f079c72b 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --patch-with-stat master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index 5187a26816723476b049c44bb3801816e8672cdf..5a4e72765d316b855a8c2beee8f417cd050de323 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --patch-with-stat master -- dir/
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index c9640976a8f3bd724004f945b00d71f43f00c8ea..df0aaa9f2ca780ce62951a5667ed9fe3aa005e41 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --root --cc --patch-with-stat --summary master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index ad050af55ffa6983bc08ef5919dd20229584fd59..c11b5f2c7f3e6846643b7d7e0e214993f4a2cf0d 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --root --patch-with-stat --summary master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index 628c6c03bc6195268c3e7e003478fd042ef36db2..5f0c98f9ce3d9d067cfcb662084b975a19c90f03 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --root --patch-with-stat master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index 5d4e0f13b59652b170c0b2498319f970d071f774..e62c368dc64dae87c6c168462b175712f197e7af 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --root -c --patch-with-stat --summary master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index 217a2eb203718b91ccd79a04e0369f4c5d2905aa..b42c334439b71cdb391d832d498b90a22aad2656 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --root -p master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index e17ccfc2340eadbb8c393de0aa2793f6f899ba56..e8f46159da1e5ca68524f5657010d06667a9c94b 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --root master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index f8fefef2c3869337ba048d5cd6d95169dc60cf00..bf1326dc36629096fc4e4102375c8a9c550391aa 100644 (file)
@@ -1,6 +1,6 @@
 $ git log -p master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index e9d9e7b40a08d803901ac001e4bf9f0c37ad8b15..a8f6ce5abd642e51672eb3e0ae5ea66a08aa74d2 100644 (file)
@@ -1,6 +1,6 @@
 $ git log master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
diff --git a/t/t4013/diff.rev-list_--children_HEAD b/t/t4013/diff.rev-list_--children_HEAD
new file mode 100644 (file)
index 0000000..e7f17d5
--- /dev/null
@@ -0,0 +1,7 @@
+$ git rev-list --children HEAD
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a 59d314ad6f356dd08601a4cd5e530381da3e3c64
+9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 59d314ad6f356dd08601a4cd5e530381da3e3c64
+1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+444ac553ac7612cc88969031b02b3767fb8a353a 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+$
diff --git a/t/t4013/diff.rev-list_--parents_HEAD b/t/t4013/diff.rev-list_--parents_HEAD
new file mode 100644 (file)
index 0000000..65d2a80
--- /dev/null
@@ -0,0 +1,7 @@
+$ git rev-list --parents HEAD
+59d314ad6f356dd08601a4cd5e530381da3e3c64 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a 444ac553ac7612cc88969031b02b3767fb8a353a
+9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 444ac553ac7612cc88969031b02b3767fb8a353a
+444ac553ac7612cc88969031b02b3767fb8a353a
+$
index 9e6e1f27105ca50262e5eb3f2ff132b46c12fe45..fb08ce0e46d16d223269754295cc4a8dc4364268 100644 (file)
@@ -1,6 +1,6 @@
 $ git show master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index 5facf2543db0135ceb3aafdc5ffe0395b429f44c..e96ff1fb8c11ff8c6774cb249f45744ed05a97c3 100644 (file)
@@ -1,6 +1,6 @@
 $ git whatchanged --root --cc --patch-with-stat --summary master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index 10f6767e498b0d5bdadd73067e4ef2dd5f028e89..c0aff68ef68c2d956492e7f9817b9bf5afe58005 100644 (file)
@@ -1,6 +1,6 @@
 $ git whatchanged --root -c --patch-with-stat --summary master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index f045898fe3196b068d03a66fd9edeea6f32add30..ebfc4a65908cb1929de299a8540d90db3d9a3a15 100755 (executable)
@@ -138,56 +138,243 @@ test_expect_success 'multiple files' '
        ls patches/0001-Side-changes-1.patch patches/0002-Side-changes-2.patch patches/0003-Side-changes-3-with-n-backslash-n-in-it.patch
 '
 
-test_expect_success 'thread' '
+check_threading () {
+       expect="$1" &&
+       shift &&
+       (git format-patch --stdout "$@"; echo $? > status.out) |
+       # Prints everything between the Message-ID and In-Reply-To,
+       # and replaces all Message-ID-lookalikes by a sequence number
+       perl -ne '
+               if (/^(message-id|references|in-reply-to)/i) {
+                       $printing = 1;
+               } elsif (/^\S/) {
+                       $printing = 0;
+               }
+               if ($printing) {
+                       $h{$1}=$i++ if (/<([^>]+)>/ and !exists $h{$1});
+                       for $k (keys %h) {s/$k/$h{$k}/};
+                       print;
+               }
+               print "---\n" if /^From /i;
+       ' > actual &&
+       test 0 = "$(cat status.out)" &&
+       test_cmp "$expect" actual
+}
+
+cat >> expect.no-threading <<EOF
+---
+---
+---
+EOF
 
-       rm -rf patches/ &&
+test_expect_success 'no threading' '
        git checkout side &&
-       git format-patch --thread -o patches/ master &&
-       FIRST_MID=$(grep "Message-Id:" patches/0001-* | sed "s/^[^<]*\(<[^>]*>\).*$/\1/") &&
-       for i in patches/0002-* patches/0003-*
-       do
-         grep "References: $FIRST_MID" $i &&
-         grep "In-Reply-To: $FIRST_MID" $i || break
-       done
+       check_threading expect.no-threading master
 '
 
-test_expect_success 'thread in-reply-to' '
+cat > expect.thread <<EOF
+---
+Message-Id: <0>
+---
+Message-Id: <1>
+In-Reply-To: <0>
+References: <0>
+---
+Message-Id: <2>
+In-Reply-To: <0>
+References: <0>
+EOF
 
-       rm -rf patches/ &&
-       git checkout side &&
-       git format-patch --in-reply-to="<test.message>" --thread -o patches/ master &&
-       FIRST_MID="<test.message>" &&
-       for i in patches/*
-       do
-         grep "References: $FIRST_MID" $i &&
-         grep "In-Reply-To: $FIRST_MID" $i || break
-       done
+test_expect_success 'thread' '
+       check_threading expect.thread --thread master
 '
 
-test_expect_success 'thread cover-letter' '
+cat > expect.in-reply-to <<EOF
+---
+Message-Id: <0>
+In-Reply-To: <1>
+References: <1>
+---
+Message-Id: <2>
+In-Reply-To: <1>
+References: <1>
+---
+Message-Id: <3>
+In-Reply-To: <1>
+References: <1>
+EOF
 
-       rm -rf patches/ &&
-       git checkout side &&
-       git format-patch --cover-letter --thread -o patches/ master &&
-       FIRST_MID=$(grep "Message-Id:" patches/0000-* | sed "s/^[^<]*\(<[^>]*>\).*$/\1/") &&
-       for i in patches/0001-* patches/0002-* patches/0003-*
-       do
-         grep "References: $FIRST_MID" $i &&
-         grep "In-Reply-To: $FIRST_MID" $i || break
-       done
+test_expect_success 'thread in-reply-to' '
+       check_threading expect.in-reply-to --in-reply-to="<test.message>" \
+               --thread master
 '
 
+cat > expect.cover-letter <<EOF
+---
+Message-Id: <0>
+---
+Message-Id: <1>
+In-Reply-To: <0>
+References: <0>
+---
+Message-Id: <2>
+In-Reply-To: <0>
+References: <0>
+---
+Message-Id: <3>
+In-Reply-To: <0>
+References: <0>
+EOF
+
+test_expect_success 'thread cover-letter' '
+       check_threading expect.cover-letter --cover-letter --thread master
+'
+
+cat > expect.cl-irt <<EOF
+---
+Message-Id: <0>
+In-Reply-To: <1>
+References: <1>
+---
+Message-Id: <2>
+In-Reply-To: <0>
+References: <1>
+       <0>
+---
+Message-Id: <3>
+In-Reply-To: <0>
+References: <1>
+       <0>
+---
+Message-Id: <4>
+In-Reply-To: <0>
+References: <1>
+       <0>
+EOF
+
 test_expect_success 'thread cover-letter in-reply-to' '
+       check_threading expect.cl-irt --cover-letter \
+               --in-reply-to="<test.message>" --thread master
+'
 
-       rm -rf patches/ &&
-       git checkout side &&
-       git format-patch --cover-letter --in-reply-to="<test.message>" --thread -o patches/ master &&
-       FIRST_MID="<test.message>" &&
-       for i in patches/*
-       do
-         grep "References: $FIRST_MID" $i &&
-         grep "In-Reply-To: $FIRST_MID" $i || break
-       done
+test_expect_success 'thread explicit shallow' '
+       check_threading expect.cl-irt --cover-letter \
+               --in-reply-to="<test.message>" --thread=shallow master
+'
+
+cat > expect.deep <<EOF
+---
+Message-Id: <0>
+---
+Message-Id: <1>
+In-Reply-To: <0>
+References: <0>
+---
+Message-Id: <2>
+In-Reply-To: <1>
+References: <0>
+       <1>
+EOF
+
+test_expect_success 'thread deep' '
+       check_threading expect.deep --thread=deep master
+'
+
+cat > expect.deep-irt <<EOF
+---
+Message-Id: <0>
+In-Reply-To: <1>
+References: <1>
+---
+Message-Id: <2>
+In-Reply-To: <0>
+References: <1>
+       <0>
+---
+Message-Id: <3>
+In-Reply-To: <2>
+References: <1>
+       <0>
+       <2>
+EOF
+
+test_expect_success 'thread deep in-reply-to' '
+       check_threading expect.deep-irt  --thread=deep \
+               --in-reply-to="<test.message>" master
+'
+
+cat > expect.deep-cl <<EOF
+---
+Message-Id: <0>
+---
+Message-Id: <1>
+In-Reply-To: <0>
+References: <0>
+---
+Message-Id: <2>
+In-Reply-To: <1>
+References: <0>
+       <1>
+---
+Message-Id: <3>
+In-Reply-To: <2>
+References: <0>
+       <1>
+       <2>
+EOF
+
+test_expect_success 'thread deep cover-letter' '
+       check_threading expect.deep-cl --cover-letter --thread=deep master
+'
+
+cat > expect.deep-cl-irt <<EOF
+---
+Message-Id: <0>
+In-Reply-To: <1>
+References: <1>
+---
+Message-Id: <2>
+In-Reply-To: <0>
+References: <1>
+       <0>
+---
+Message-Id: <3>
+In-Reply-To: <2>
+References: <1>
+       <0>
+       <2>
+---
+Message-Id: <4>
+In-Reply-To: <3>
+References: <1>
+       <0>
+       <2>
+       <3>
+EOF
+
+test_expect_success 'thread deep cover-letter in-reply-to' '
+       check_threading expect.deep-cl-irt --cover-letter \
+               --in-reply-to="<test.message>" --thread=deep master
+'
+
+test_expect_success 'thread via config' '
+       git config format.thread true &&
+       check_threading expect.thread master
+'
+
+test_expect_success 'thread deep via config' '
+       git config format.thread deep &&
+       check_threading expect.deep master
+'
+
+test_expect_success 'thread config + override' '
+       git config format.thread deep &&
+       check_threading expect.thread --thread master
+'
+
+test_expect_success 'thread config + --no-thread' '
+       git config format.thread deep &&
+       check_threading expect.no-threading --no-thread master
 '
 
 test_expect_success 'excessive subject' '
index caea292f15437f50fd324496ff511971cc9d1d0c..281680d95afacb291d56d9d7f23efed1f3169e93 100755 (executable)
@@ -128,4 +128,12 @@ test_expect_success 'force diff with "diff"' '
        test_cmp "$TEST_DIRECTORY"/t4020/diff.NUL actual
 '
 
+test_expect_success 'GIT_EXTERNAL_DIFF with more than one changed files' '
+       echo anotherfile > file2 &&
+       git add file2 &&
+       git commit -m "added 2nd file" &&
+       echo modified >file2 &&
+       GIT_EXTERNAL_DIFF=echo git diff
+'
+
 test_done
diff --git a/t/t4032-diff-inter-hunk-context.sh b/t/t4032-diff-inter-hunk-context.sh
new file mode 100755 (executable)
index 0000000..e4e3e28
--- /dev/null
@@ -0,0 +1,92 @@
+#!/bin/sh
+
+test_description='diff hunk fusing'
+
+. ./test-lib.sh
+
+f() {
+       echo $1
+       i=1
+       while test $i -le $2
+       do
+               echo $i
+               i=$(expr $i + 1)
+       done
+       echo $3
+}
+
+t() {
+       case $# in
+       4) hunks=$4; cmd="diff -U$3";;
+       5) hunks=$5; cmd="diff -U$3 --inter-hunk-context=$4";;
+       esac
+       label="$cmd, $1 common $2"
+       file=f$1
+       expected=expected.$file.$3.$hunks
+
+       if ! test -f $file
+       then
+               f A $1 B >$file
+               git add $file
+               git commit -q -m. $file
+               f X $1 Y >$file
+       fi
+
+       test_expect_success "$label: count hunks ($hunks)" "
+               test $(git $cmd $file | grep '^@@ ' | wc -l) = $hunks
+       "
+
+       test -f $expected &&
+       test_expect_success "$label: check output" "
+               git $cmd $file | grep -v '^index ' >actual &&
+               test_cmp $expected actual
+       "
+}
+
+cat <<EOF >expected.f1.0.1 || exit 1
+diff --git a/f1 b/f1
+--- a/f1
++++ b/f1
+@@ -1,3 +1,3 @@
+-A
++X
+ 1
+-B
++Y
+EOF
+
+cat <<EOF >expected.f1.0.2 || exit 1
+diff --git a/f1 b/f1
+--- a/f1
++++ b/f1
+@@ -1 +1 @@
+-A
++X
+@@ -3 +3 @@ A
+-B
++Y
+EOF
+
+# common lines ctx     intrctx hunks
+t 1 line       0               2
+t 1 line       0       0       2
+t 1 line       0       1       1
+t 1 line       0       2       1
+t 1 line       1               1
+
+t 2 lines      0               2
+t 2 lines      0       0       2
+t 2 lines      0       1       2
+t 2 lines      0       2       1
+t 2 lines      1               1
+
+t 3 lines      1               2
+t 3 lines      1       0       2
+t 3 lines      1       1       1
+t 3 lines      1       2       1
+
+t 9 lines      3               2
+t 9 lines      3       2       2
+t 9 lines      3       3       1
+
+test_done
diff --git a/t/t4033-diff-patience.sh b/t/t4033-diff-patience.sh
new file mode 100755 (executable)
index 0000000..1eb1498
--- /dev/null
@@ -0,0 +1,168 @@
+#!/bin/sh
+
+test_description='patience diff algorithm'
+
+. ./test-lib.sh
+
+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
+
+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_done
diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh
new file mode 100755 (executable)
index 0000000..4508eff
--- /dev/null
@@ -0,0 +1,200 @@
+#!/bin/sh
+
+test_description='word diff colors'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+       git config diff.color.old red
+       git config diff.color.new green
+
+'
+
+decrypt_color () {
+       sed \
+               -e 's/.\[1m/<WHITE>/g' \
+               -e 's/.\[31m/<RED>/g' \
+               -e 's/.\[32m/<GREEN>/g' \
+               -e 's/.\[36m/<BROWN>/g' \
+               -e 's/.\[m/<RESET>/g'
+}
+
+word_diff () {
+       test_must_fail git diff --no-index "$@" pre post > output &&
+       decrypt_color < output > output.decrypted &&
+       test_cmp expect output.decrypted
+}
+
+cat > pre <<\EOF
+h(4)
+
+a = b + c
+EOF
+
+cat > post <<\EOF
+h(4),hh[44]
+
+a = b + c
+
+aa = a
+
+aeff = aeff * ( aaa )
+EOF
+
+cat > expect <<\EOF
+<WHITE>diff --git a/pre b/post<RESET>
+<WHITE>index 330b04f..5ed8eff 100644<RESET>
+<WHITE>--- a/pre<RESET>
+<WHITE>+++ b/post<RESET>
+<BROWN>@@ -1,3 +1,7 @@<RESET>
+<RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
+<RESET>
+a = b + c<RESET>
+
+<GREEN>aa = a<RESET>
+
+<GREEN>aeff = aeff * ( aaa )<RESET>
+EOF
+
+test_expect_success 'word diff with runs of whitespace' '
+
+       word_diff --color-words
+
+'
+
+cat > expect <<\EOF
+<WHITE>diff --git a/pre b/post<RESET>
+<WHITE>index 330b04f..5ed8eff 100644<RESET>
+<WHITE>--- a/pre<RESET>
+<WHITE>+++ b/post<RESET>
+<BROWN>@@ -1,3 +1,7 @@<RESET>
+h(4),<GREEN>hh<RESET>[44]
+<RESET>
+a = b + c<RESET>
+
+<GREEN>aa = a<RESET>
+
+<GREEN>aeff = aeff * ( aaa<RESET> )
+EOF
+cp expect expect.letter-runs-are-words
+
+test_expect_success 'word diff with a regular expression' '
+
+       word_diff --color-words="[a-z]+"
+
+'
+
+test_expect_success 'set a diff driver' '
+       git config diff.testdriver.wordRegex "[^[:space:]]" &&
+       cat <<EOF > .gitattributes
+pre diff=testdriver
+post diff=testdriver
+EOF
+'
+
+test_expect_success 'option overrides .gitattributes' '
+
+       word_diff --color-words="[a-z]+"
+
+'
+
+cat > expect <<\EOF
+<WHITE>diff --git a/pre b/post<RESET>
+<WHITE>index 330b04f..5ed8eff 100644<RESET>
+<WHITE>--- a/pre<RESET>
+<WHITE>+++ b/post<RESET>
+<BROWN>@@ -1,3 +1,7 @@<RESET>
+h(4)<GREEN>,hh[44]<RESET>
+<RESET>
+a = b + c<RESET>
+
+<GREEN>aa = a<RESET>
+
+<GREEN>aeff = aeff * ( aaa )<RESET>
+EOF
+cp expect expect.non-whitespace-is-word
+
+test_expect_success 'use regex supplied by driver' '
+
+       word_diff --color-words
+
+'
+
+test_expect_success 'set diff.wordRegex option' '
+       git config diff.wordRegex "[[:alnum:]]+"
+'
+
+cp expect.letter-runs-are-words expect
+
+test_expect_success 'command-line overrides config' '
+       word_diff --color-words="[a-z]+"
+'
+
+cp expect.non-whitespace-is-word expect
+
+test_expect_success '.gitattributes override config' '
+       word_diff --color-words
+'
+
+test_expect_success 'remove diff driver regex' '
+       git config --unset diff.testdriver.wordRegex
+'
+
+cat > expect <<\EOF
+<WHITE>diff --git a/pre b/post<RESET>
+<WHITE>index 330b04f..5ed8eff 100644<RESET>
+<WHITE>--- a/pre<RESET>
+<WHITE>+++ b/post<RESET>
+<BROWN>@@ -1,3 +1,7 @@<RESET>
+h(4),<GREEN>hh[44<RESET>]
+<RESET>
+a = b + c<RESET>
+
+<GREEN>aa = a<RESET>
+
+<GREEN>aeff = aeff * ( aaa<RESET> )
+EOF
+
+test_expect_success 'use configured regex' '
+       word_diff --color-words
+'
+
+echo 'aaa (aaa)' > pre
+echo 'aaa (aaa) aaa' > post
+
+cat > expect <<\EOF
+<WHITE>diff --git a/pre b/post<RESET>
+<WHITE>index c29453b..be22f37 100644<RESET>
+<WHITE>--- a/pre<RESET>
+<WHITE>+++ b/post<RESET>
+<BROWN>@@ -1 +1 @@<RESET>
+aaa (aaa) <GREEN>aaa<RESET>
+EOF
+
+test_expect_success 'test parsing words for newline' '
+
+       word_diff --color-words="a+"
+
+
+'
+
+echo '(:' > pre
+echo '(' > post
+
+cat > expect <<\EOF
+<WHITE>diff --git a/pre b/post<RESET>
+<WHITE>index 289cb9d..2d06f37 100644<RESET>
+<WHITE>--- a/pre<RESET>
+<WHITE>+++ b/post<RESET>
+<BROWN>@@ -1 +1 @@<RESET>
+(<RED>:<RESET>
+EOF
+
+test_expect_success 'test when words are only removed at the end' '
+
+       word_diff --color-words=.
+
+'
+
+test_done
diff --git a/t/t4106-apply-stdin.sh b/t/t4106-apply-stdin.sh
new file mode 100755 (executable)
index 0000000..72467a1
--- /dev/null
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+test_description='git apply --numstat - <patch'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       echo hello >text &&
+       git add text &&
+       echo goodbye >text &&
+       git diff >patch
+'
+
+test_expect_success 'git apply --numstat - < patch' '
+       echo "1 1       text" >expect &&
+       git apply --numstat - <patch >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git apply --numstat - < patch patch' '
+       for i in 1 2; do echo "1        1       text"; done >expect &&
+       git apply --numstat - < patch patch >actual &&
+       test_cmp expect actual
+'
+
+test_done
index 796f795267dee1eaf63b10fb3e5e14ce0431bebd..5e65afa0c10d02e50c79b550a3c142d8ff1f0674 100755 (executable)
@@ -257,4 +257,37 @@ test_expect_success 'am works from file (absolute path given) in subdirectory' '
        test -z "$(git diff second)"
 '
 
+test_expect_success 'am --committer-date-is-author-date' '
+       git checkout first &&
+       test_tick &&
+       git am --committer-date-is-author-date patch1 &&
+       git cat-file commit HEAD | sed -e "/^$/q" >head1 &&
+       at=$(sed -ne "/^author /s/.*> //p" head1) &&
+       ct=$(sed -ne "/^committer /s/.*> //p" head1) &&
+       test "$at" = "$ct"
+'
+
+test_expect_success 'am without --committer-date-is-author-date' '
+       git checkout first &&
+       test_tick &&
+       git am patch1 &&
+       git cat-file commit HEAD | sed -e "/^$/q" >head1 &&
+       at=$(sed -ne "/^author /s/.*> //p" head1) &&
+       ct=$(sed -ne "/^committer /s/.*> //p" head1) &&
+       test "$at" != "$ct"
+'
+
+# This checks for +0000 because TZ is set to UTC and that should
+# show up when the current time is used. The date in message is set
+# by test_tick that uses -0700 timezone; if this feature does not
+# work, we will see that instead of +0000.
+test_expect_success 'am --ignore-date' '
+       git checkout first &&
+       test_tick &&
+       git am --ignore-date patch1 &&
+       git cat-file commit HEAD | sed -e "/^$/q" >head1 &&
+       at=$(sed -ne "/^author /s/.*> //p" head1) &&
+       echo "$at" | grep "+0000"
+'
+
 test_done
index 0ab925c4e4710a560f0d35e47ccdda8ddb2b8212..b98619035c58d1b5c617c6e9998e7ae07ab3f27f 100755 (executable)
@@ -16,27 +16,71 @@ test_expect_success setup '
        test_tick &&
        git commit -m second &&
 
-       mkdir a &&
-       echo ni >a/two &&
-       git add a/two &&
+       git mv one ichi &&
        test_tick &&
        git commit -m third &&
 
-       echo san >a/three &&
-       git add a/three &&
+       cp ichi ein &&
+       git add ein &&
        test_tick &&
        git commit -m fourth &&
 
-       git rm a/three &&
+       mkdir a &&
+       echo ni >a/two &&
+       git add a/two &&
+       test_tick &&
+       git commit -m fifth  &&
+
+       git rm a/two &&
        test_tick &&
-       git commit -m fifth
+       git commit -m sixth
+
+'
+
+printf "sixth\nfifth\nfourth\nthird\nsecond\ninitial" > expect
+test_expect_success 'pretty' '
+
+       git log --pretty="format:%s" > actual &&
+       test_cmp expect actual
+'
+
+printf "sixth\nfifth\nfourth\nthird\nsecond\ninitial\n" > expect
+test_expect_success 'pretty (tformat)' '
+
+       git log --pretty="tformat:%s" > actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'pretty (shortcut)' '
 
+       git log --pretty="%s" > actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'format' '
+
+       git log --format="%s" > actual &&
+       test_cmp expect actual
+'
+
+cat > expect << EOF
+804a787 sixth
+394ef78 fifth
+5d31159 fourth
+2fbe8c0 third
+f7dab8e second
+3a2fdcb initial
+EOF
+test_expect_success 'oneline' '
+
+       git log --oneline > actual &&
+       test_cmp expect actual
 '
 
 test_expect_success 'diff-filter=A' '
 
        actual=$(git log --pretty="format:%s" --diff-filter=A HEAD) &&
-       expect=$(echo fourth ; echo third ; echo initial) &&
+       expect=$(echo fifth ; echo fourth ; echo third ; echo initial) &&
        test "$actual" = "$expect" || {
                echo Oops
                echo "Actual: $actual"
@@ -60,7 +104,43 @@ test_expect_success 'diff-filter=M' '
 test_expect_success 'diff-filter=D' '
 
        actual=$(git log --pretty="format:%s" --diff-filter=D HEAD) &&
-       expect=$(echo fifth) &&
+       expect=$(echo sixth ; echo third) &&
+       test "$actual" = "$expect" || {
+               echo Oops
+               echo "Actual: $actual"
+               false
+       }
+
+'
+
+test_expect_success 'diff-filter=R' '
+
+       actual=$(git log -M --pretty="format:%s" --diff-filter=R HEAD) &&
+       expect=$(echo third) &&
+       test "$actual" = "$expect" || {
+               echo Oops
+               echo "Actual: $actual"
+               false
+       }
+
+'
+
+test_expect_success 'diff-filter=C' '
+
+       actual=$(git log -C -C --pretty="format:%s" --diff-filter=C HEAD) &&
+       expect=$(echo fourth) &&
+       test "$actual" = "$expect" || {
+               echo Oops
+               echo "Actual: $actual"
+               false
+       }
+
+'
+
+test_expect_success 'git log --follow' '
+
+       actual=$(git log --follow --pretty="format:%s" ichi) &&
+       expect=$(echo third ; echo second ; echo initial) &&
        test "$actual" = "$expect" || {
                echo Oops
                echo "Actual: $actual"
@@ -72,6 +152,7 @@ test_expect_success 'diff-filter=D' '
 test_expect_success 'setup case sensitivity tests' '
        echo case >one &&
        test_tick &&
+       git add one
        git commit -a -m Second
 '
 
@@ -93,5 +174,153 @@ test_expect_success 'log --grep -i' '
        test_cmp expect actual
 '
 
+cat > expect <<EOF
+* Second
+* sixth
+* fifth
+* fourth
+* third
+* second
+* initial
+EOF
+
+test_expect_success 'simple log --graph' '
+       git log --graph --pretty=tformat:%s >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'set up merge history' '
+       git checkout -b side HEAD~4 &&
+       test_commit side-1 1 1 &&
+       test_commit side-2 2 2 &&
+       git checkout master &&
+       git merge side
+'
+
+cat > expect <<\EOF
+*   Merge branch 'side'
+|\
+| * side-2
+| * side-1
+* | Second
+* | sixth
+* | fifth
+* | fourth
+|/
+* third
+* second
+* initial
+EOF
+
+test_expect_success 'log --graph with merge' '
+       git log --graph --date-order --pretty=tformat:%s |
+               sed "s/ *$//" >actual &&
+       test_cmp expect actual
+'
+
+cat > expect <<\EOF
+*   commit master
+|\  Merge: A B
+| | Author: A U Thor <author@example.com>
+| |
+| |     Merge branch 'side'
+| |
+| * commit side
+| | Author: A U Thor <author@example.com>
+| |
+| |     side-2
+| |
+| * commit tags/side-1
+| | Author: A U Thor <author@example.com>
+| |
+| |     side-1
+| |
+* | commit master~1
+| | Author: A U Thor <author@example.com>
+| |
+| |     Second
+| |
+* | commit master~2
+| | Author: A U Thor <author@example.com>
+| |
+| |     sixth
+| |
+* | commit master~3
+| | Author: A U Thor <author@example.com>
+| |
+| |     fifth
+| |
+* | commit master~4
+|/  Author: A U Thor <author@example.com>
+|
+|       fourth
+|
+* commit tags/side-1~1
+| Author: A U Thor <author@example.com>
+|
+|     third
+|
+* commit tags/side-1~2
+| Author: A U Thor <author@example.com>
+|
+|     second
+|
+* commit tags/side-1~3
+  Author: A U Thor <author@example.com>
+
+      initial
+EOF
+
+test_expect_success 'log --graph with full output' '
+       git log --graph --date-order --pretty=short |
+               git name-rev --name-only --stdin |
+               sed "s/Merge:.*/Merge: A B/;s/ *$//" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'set up more tangled history' '
+       git checkout -b tangle HEAD~6 &&
+       test_commit tangle-a tangle-a a &&
+       git merge master~3 &&
+       git merge side~1 &&
+       git checkout master &&
+       git merge tangle
+'
+
+cat > expect <<\EOF
+*   Merge branch 'tangle'
+|\
+| *   Merge branch 'side' (early part) into tangle
+| |\
+| * \   Merge branch 'master' (early part) into tangle
+| |\ \
+| * | | tangle-a
+* | | |   Merge branch 'side'
+|\ \ \ \
+| * | | | side-2
+| | | |/
+| | |/|
+| |/| |
+| * | | side-1
+* | | | Second
+* | | | sixth
+| | |/
+| |/|
+|/| |
+* | | fifth
+* | | fourth
+|/ /
+* | third
+|/
+* second
+* initial
+EOF
+
+test_expect_success 'log --graph with merge' '
+       git log --graph --date-order --pretty=tformat:%s |
+               sed "s/ *$//" >actual &&
+       test_cmp expect actual
+'
+
 test_done
 
diff --git a/t/t4203-mailmap.sh b/t/t4203-mailmap.sh
new file mode 100755 (executable)
index 0000000..9a7d1b4
--- /dev/null
@@ -0,0 +1,215 @@
+#!/bin/sh
+
+test_description='.mailmap configurations'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       echo one >one &&
+       git add one &&
+       test_tick &&
+       git commit -m initial &&
+       echo two >>one &&
+       git add one &&
+       git commit --author "nick1 <bugs@company.xx>" -m second
+'
+
+cat >expect <<\EOF
+A U Thor (1):
+      initial
+
+nick1 (1):
+      second
+
+EOF
+
+test_expect_success 'No mailmap' '
+       git shortlog HEAD >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<\EOF
+Repo Guy (1):
+      initial
+
+nick1 (1):
+      second
+
+EOF
+
+test_expect_success 'default .mailmap' '
+       echo "Repo Guy <author@example.com>" > .mailmap &&
+       git shortlog HEAD >actual &&
+       test_cmp expect actual
+'
+
+# Using a mailmap file in a subdirectory of the repo here, but
+# could just as well have been a file outside of the repository
+cat >expect <<\EOF
+Internal Guy (1):
+      second
+
+Repo Guy (1):
+      initial
+
+EOF
+test_expect_success 'mailmap.file set' '
+       mkdir internal_mailmap &&
+       echo "Internal Guy <bugs@company.xx>" > internal_mailmap/.mailmap &&
+       git config mailmap.file internal_mailmap/.mailmap &&
+       git shortlog HEAD >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<\EOF
+External Guy (1):
+      initial
+
+Internal Guy (1):
+      second
+
+EOF
+test_expect_success 'mailmap.file override' '
+       echo "External Guy <author@example.com>" >> internal_mailmap/.mailmap &&
+       git config mailmap.file internal_mailmap/.mailmap &&
+       git shortlog HEAD >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<\EOF
+Repo Guy (1):
+      initial
+
+nick1 (1):
+      second
+
+EOF
+
+test_expect_success 'mailmap.file non-existant' '
+       rm internal_mailmap/.mailmap &&
+       rmdir internal_mailmap &&
+       git shortlog HEAD >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<\EOF
+A U Thor (1):
+      initial
+
+nick1 (1):
+      second
+
+EOF
+test_expect_success 'No mailmap files, but configured' '
+       rm .mailmap &&
+       git shortlog HEAD >actual &&
+       test_cmp expect actual
+'
+
+# Extended mailmap configurations should give us the following output for shortlog
+cat >expect <<\EOF
+A U Thor <author@example.com> (1):
+      initial
+
+CTO <cto@company.xx> (1):
+      seventh
+
+Other Author <other@author.xx> (2):
+      third
+      fourth
+
+Santa Claus <santa.claus@northpole.xx> (2):
+      fifth
+      sixth
+
+Some Dude <some@dude.xx> (1):
+      second
+
+EOF
+
+test_expect_success 'Shortlog output (complex mapping)' '
+       echo three >>one &&
+       git add one &&
+       test_tick &&
+       git commit --author "nick2 <bugs@company.xx>" -m third &&
+
+       echo four >>one &&
+       git add one &&
+       test_tick &&
+       git commit --author "nick2 <nick2@company.xx>" -m fourth &&
+
+       echo five >>one &&
+       git add one &&
+       test_tick &&
+       git commit --author "santa <me@company.xx>" -m fifth &&
+
+       echo six >>one &&
+       git add one &&
+       test_tick &&
+       git commit --author "claus <me@company.xx>" -m sixth &&
+
+       echo seven >>one &&
+       git add one &&
+       test_tick &&
+       git commit --author "CTO <cto@coompany.xx>" -m seventh &&
+
+       mkdir internal_mailmap &&
+       echo "Committed <committer@example.com>" > internal_mailmap/.mailmap &&
+       echo "<cto@company.xx>                       <cto@coompany.xx>" >> internal_mailmap/.mailmap &&
+       echo "Some Dude <some@dude.xx>         nick1 <bugs@company.xx>" >> internal_mailmap/.mailmap &&
+       echo "Other Author <other@author.xx>   nick2 <bugs@company.xx>" >> internal_mailmap/.mailmap &&
+       echo "Other Author <other@author.xx>         <nick2@company.xx>" >> internal_mailmap/.mailmap &&
+       echo "Santa Claus <santa.claus@northpole.xx> <me@company.xx>" >> internal_mailmap/.mailmap &&
+       echo "Santa Claus <santa.claus@northpole.xx> <me@company.xx>" >> internal_mailmap/.mailmap &&
+
+       git shortlog -e HEAD >actual &&
+       test_cmp expect actual
+
+'
+
+# git log with --pretty format which uses the name and email mailmap placemarkers
+cat >expect <<\EOF
+Author CTO <cto@coompany.xx> maps to CTO <cto@company.xx>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+
+Author claus <me@company.xx> maps to Santa Claus <santa.claus@northpole.xx>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+
+Author santa <me@company.xx> maps to Santa Claus <santa.claus@northpole.xx>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+
+Author nick2 <nick2@company.xx> maps to Other Author <other@author.xx>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+
+Author nick2 <bugs@company.xx> maps to Other Author <other@author.xx>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+
+Author nick1 <bugs@company.xx> maps to Some Dude <some@dude.xx>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+
+Author A U Thor <author@example.com> maps to A U Thor <author@example.com>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+EOF
+
+test_expect_success 'Log output (complex mapping)' '
+       git log --pretty=format:"Author %an <%ae> maps to %aN <%aE>%nCommitter %cn <%ce> maps to %cN <%cE>%n" >actual &&
+       test_cmp expect actual
+'
+
+# git blame
+cat >expect <<\EOF
+^3a2fdcb (A U Thor     2005-04-07 15:13:13 -0700 1) one
+7de6f99b (Some Dude    2005-04-07 15:13:13 -0700 2) two
+5815879d (Other Author 2005-04-07 15:14:13 -0700 3) three
+ff859d96 (Other Author 2005-04-07 15:15:13 -0700 4) four
+5ab6d4fa (Santa Claus  2005-04-07 15:16:13 -0700 5) five
+38a42d8b (Santa Claus  2005-04-07 15:17:13 -0700 6) six
+8ddc0386 (CTO          2005-04-07 15:18:13 -0700 7) seven
+EOF
+
+test_expect_success 'Blame output (complex mapping)' '
+       git blame one >actual &&
+       test_cmp expect actual
+'
+
+test_done
diff --git a/t/t4203-patch-id.sh b/t/t4203-patch-id.sh
new file mode 100755 (executable)
index 0000000..04f7bae
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+test_description='git patch-id'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       test_commit initial foo a &&
+       test_commit first foo b &&
+       git checkout -b same HEAD^ &&
+       test_commit same-msg foo b &&
+       git checkout -b notsame HEAD^ &&
+       test_commit notsame-msg foo c
+'
+
+test_expect_success 'patch-id output is well-formed' '
+       git log -p -1 | git patch-id > output &&
+       grep "^[a-f0-9]\{40\} $(git rev-parse HEAD)$" output
+'
+
+get_patch_id () {
+       git log -p -1 "$1" | git patch-id |
+               sed "s# .*##" > patch-id_"$1"
+}
+
+test_expect_success 'patch-id detects equality' '
+       get_patch_id master &&
+       get_patch_id same &&
+       test_cmp patch-id_master patch-id_same
+'
+
+test_expect_success 'patch-id detects inequality' '
+       get_patch_id master &&
+       get_patch_id notsame &&
+       ! test_cmp patch-id_master patch-id_notsame
+'
+
+test_done
index 3ab9e8e6e3635ce54b19cec7987ab976fd994309..f603c1b1336c4a00889177376d9b51077c9cc2ac 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='git am not losing options'
+test_description='git am with options and not losing them'
 . ./test-lib.sh
 
 tm="$TEST_DIRECTORY/t4252"
@@ -50,4 +50,29 @@ test_expect_success 'interrupted am -C1 -p2' '
        grep "^Three$" file-2
 '
 
+test_expect_success 'interrupted am --directory="frotz nitfol"' '
+       rm -rf .git/rebase-apply &&
+       git reset --hard initial &&
+       test_must_fail git am --directory="frotz nitfol" "$tm"/am-test-5-? &&
+       git am --skip &&
+       grep One "frotz nitfol/file-5"
+'
+
+test_expect_success 'apply to a funny path' '
+       with_sq="with'\''sq"
+       rm -fr .git/rebase-apply &&
+       git reset --hard initial &&
+       git am --directory="$with_sq" "$tm"/am-test-5-2 &&
+       test -f "$with_sq/file-5"
+'
+
+test_expect_success 'am --reject' '
+       rm -rf .git/rebase-apply &&
+       git reset --hard initial &&
+       test_must_fail git am --reject "$tm"/am-test-6-1 &&
+       grep "@@ -1,3 +1,3 @@" file-2.rej &&
+       test_must_fail git diff-files --exit-code --quiet file-2 &&
+       grep "[-]-reject" .git/rebase-apply/apply-opt
+'
+
 test_done
diff --git a/t/t4252/am-test-5-1 b/t/t4252/am-test-5-1
new file mode 100644 (file)
index 0000000..da7bf29
--- /dev/null
@@ -0,0 +1,20 @@
+From: A U Thor <au.thor@example.com>
+Date: Thu Dec 4 16:00:00 2008 -0800
+Subject: Six
+
+Applying this patch with --directory='frotz nitfol' should fail
+
+diff --git i/junk/file-2 w/junk/file-2
+index 06e567b..b6f3a16 100644
+--- i/junk/file-2
++++ w/junk/file-2
+@@ -1,7 +1,7 @@
+ One
+ 2
+-3
++Three
+ 4
+ 5
+-6
++Six
+ 7
diff --git a/t/t4252/am-test-5-2 b/t/t4252/am-test-5-2
new file mode 100644 (file)
index 0000000..373025b
--- /dev/null
@@ -0,0 +1,15 @@
+From: A U Thor <au.thor@example.com>
+Date: Thu Dec 4 16:00:00 2008 -0800
+Subject: Six
+
+Applying this patch with --directory='frotz nitfol' should succeed
+
+diff --git i/file-5 w/file-5
+new file mode 100644
+index 000000..1d6ed9f
+--- /dev/null
++++ w/file-5
+@@ -0,0 +1,3 @@
++One
++two
++three
diff --git a/t/t4252/am-test-6-1 b/t/t4252/am-test-6-1
new file mode 100644 (file)
index 0000000..a8859e9
--- /dev/null
@@ -0,0 +1,21 @@
+From: A U Thor <au.thor@example.com>
+Date: Thu Dec 4 16:00:00 2008 -0800
+Subject: Huh
+
+Should fail and leave rejects
+
+diff --git i/file-2 w/file-2
+index 06e567b..b6f3a16 100644
+--- i/file-2
++++ w/file-2
+@@ -1,3 +1,3 @@
+-0
++One
+ 2
+ 3
+@@ -4,4 +4,4 @@
+ 4
+ 5
+-6
++Six
+ 7
index c942c8be85339157e22f755d8fc94e64efaee4dd..b7e362834bd743b61c27a4fd2118a83b14828489 100755 (executable)
@@ -86,6 +86,10 @@ test_expect_success \
     'git archive vs. the same in a bare repo' \
     'test_cmp b.tar b3.tar'
 
+test_expect_success 'git archive with --output' \
+    'git archive --output=b4.tar HEAD &&
+    test_cmp b.tar b4.tar'
+
 test_expect_success \
     'validate file modification time' \
     'mkdir extract &&
@@ -172,6 +176,10 @@ test_expect_success \
     'git archive --format=zip vs. the same in a bare repo' \
     'test_cmp d.zip d1.zip'
 
+test_expect_success 'git archive --format=zip with --output' \
+    'git archive --format=zip --output=d2.zip HEAD &&
+    test_cmp d.zip d2.zip'
+
 $UNZIP -v >/dev/null 2>&1
 if [ $? -eq 127 ]; then
        echo "Skipping ZIP tests, because unzip was not found"
index fe14589427643b9bb7759c597935da724adf1a64..e70ea94a1368dc045469808d30c717aa2b8bb158 100755 (executable)
@@ -11,7 +11,7 @@ test_expect_success 'split sample box' \
        'git mailsplit -o. "$TEST_DIRECTORY"/t5100/sample.mbox >last &&
        last=`cat last` &&
        echo total is $last &&
-       test `cat last` = 11'
+       test `cat last` = 13'
 
 for mail in `echo 00*`
 do
@@ -26,6 +26,28 @@ do
        '
 done
 
+
+test_expect_success 'split box with rfc2047 samples' \
+       'mkdir rfc2047 &&
+       git mailsplit -orfc2047 "$TEST_DIRECTORY"/t5100/rfc2047-samples.mbox \
+         >rfc2047/last &&
+       last=`cat rfc2047/last` &&
+       echo total is $last &&
+       test `cat rfc2047/last` = 11'
+
+for mail in `echo rfc2047/00*`
+do
+       test_expect_success "mailinfo $mail" '
+               git mailinfo -u $mail-msg $mail-patch <$mail >$mail-info &&
+               echo msg &&
+               test_cmp "$TEST_DIRECTORY"/t5100/empty $mail-msg &&
+               echo patch &&
+               test_cmp "$TEST_DIRECTORY"/t5100/empty $mail-patch &&
+               echo info &&
+               test_cmp "$TEST_DIRECTORY"/t5100/rfc2047-info-$(basename $mail) $mail-info
+       '
+done
+
 test_expect_success 'respect NULs' '
 
        git mailsplit -d3 -o. "$TEST_DIRECTORY"/t5100/nul-plain &&
diff --git a/t/t5100/empty b/t/t5100/empty
new file mode 100644 (file)
index 0000000..e69de29
index 8c052777e0d216e84bb8464b1ceaff1bc7721154..f951538acc0152987d0e296ab0ea73b738275bdb 100644 (file)
@@ -1,4 +1,4 @@
-Author: A U Thor
+Author: A (zzz) U Thor (Comment)
 Email: a.u.thor@example.com
 Subject: a commit.
 Date: Fri, 9 Jun 2006 00:44:16 -0700
diff --git a/t/t5100/info0012 b/t/t5100/info0012
new file mode 100644 (file)
index 0000000..ac1216f
--- /dev/null
@@ -0,0 +1,5 @@
+Author: Dmitriy Blinov
+Email: bda@mnsspb.ru
+Subject: Изменён список пакетов необходимых для сборки
+Date: Wed, 12 Nov 2008 17:54:41 +0300
+
diff --git a/t/t5100/info0013 b/t/t5100/info0013
new file mode 100644 (file)
index 0000000..bbe049e
--- /dev/null
@@ -0,0 +1,5 @@
+Author: A U Thor
+Email: a.u.thor@example.com
+Subject: a patch
+Date: Fri, 9 Jun 2006 00:44:16 -0700
+
diff --git a/t/t5100/msg0012 b/t/t5100/msg0012
new file mode 100644 (file)
index 0000000..1dc2bf7
--- /dev/null
@@ -0,0 +1,7 @@
+textlive-* исправлены на texlive-*
+docutils заменён на python-docutils
+
+Действительно, оказалось, что rest2web вытягивает за собой
+python-docutils. В то время как сам rest2web не нужен.
+
+Signed-off-by: Dmitriy Blinov <bda@mnsspb.ru>
diff --git a/t/t5100/msg0013 b/t/t5100/msg0013
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/t/t5100/patch0012 b/t/t5100/patch0012
new file mode 100644 (file)
index 0000000..36a0b68
--- /dev/null
@@ -0,0 +1,30 @@
+---
+ howto/build_navy.txt |    6 +++---
+ 1 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/howto/build_navy.txt b/howto/build_navy.txt
+index 3fd3afb..0ee807e 100644
+--- a/howto/build_navy.txt
++++ b/howto/build_navy.txt
+@@ -119,8 +119,8 @@
+    - libxv-dev
+    - libusplash-dev
+    - latex-make
+-   - textlive-lang-cyrillic
+-   - textlive-latex-extra
++   - texlive-lang-cyrillic
++   - texlive-latex-extra
+    - dia
+    - python-pyrex
+    - libtool
+@@ -128,7 +128,7 @@
+    - sox
+    - cython
+    - imagemagick
+-   - docutils
++   - python-docutils
+ #. на машине dinar: добавить свой открытый ssh-ключ в authorized_keys2 пользователя ddev
+ #. на своей машине: отредактировать /etc/sudoers (команда ``visudo``) примерно следующим образом::
+-- 
+1.5.6.5
diff --git a/t/t5100/patch0013 b/t/t5100/patch0013
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/t/t5100/rfc2047-info-0001 b/t/t5100/rfc2047-info-0001
new file mode 100644 (file)
index 0000000..0a383b0
--- /dev/null
@@ -0,0 +1,4 @@
+Author: Keith Moore
+Email: moore@cs.utk.edu
+Subject: If you can read this you understand the example.
+
diff --git a/t/t5100/rfc2047-info-0002 b/t/t5100/rfc2047-info-0002
new file mode 100644 (file)
index 0000000..881be75
--- /dev/null
@@ -0,0 +1,4 @@
+Author: Olle Järnefors
+Email: ojarnef@admin.kth.se
+Subject: Time for ISO 10646?
+
diff --git a/t/t5100/rfc2047-info-0003 b/t/t5100/rfc2047-info-0003
new file mode 100644 (file)
index 0000000..d0f7891
--- /dev/null
@@ -0,0 +1,4 @@
+Author: Patrik Fältström
+Email: paf@nada.kth.se
+Subject: RFC-HDR care and feeding
+
diff --git a/t/t5100/rfc2047-info-0004 b/t/t5100/rfc2047-info-0004
new file mode 100644 (file)
index 0000000..f67a90a
--- /dev/null
@@ -0,0 +1,4 @@
+Author: Nathaniel Borenstein (םולש ןב ילטפנ)
+Email: nsb@thumper.bellcore.com
+Subject: Test of new header generator
+
diff --git a/t/t5100/rfc2047-info-0005 b/t/t5100/rfc2047-info-0005
new file mode 100644 (file)
index 0000000..c27be3b
--- /dev/null
@@ -0,0 +1,2 @@
+Subject: (a)
+
diff --git a/t/t5100/rfc2047-info-0006 b/t/t5100/rfc2047-info-0006
new file mode 100644 (file)
index 0000000..9dad474
--- /dev/null
@@ -0,0 +1,2 @@
+Subject: (a b)
+
diff --git a/t/t5100/rfc2047-info-0007 b/t/t5100/rfc2047-info-0007
new file mode 100644 (file)
index 0000000..294f195
--- /dev/null
@@ -0,0 +1,2 @@
+Subject: (ab)
+
diff --git a/t/t5100/rfc2047-info-0008 b/t/t5100/rfc2047-info-0008
new file mode 100644 (file)
index 0000000..294f195
--- /dev/null
@@ -0,0 +1,2 @@
+Subject: (ab)
+
diff --git a/t/t5100/rfc2047-info-0009 b/t/t5100/rfc2047-info-0009
new file mode 100644 (file)
index 0000000..294f195
--- /dev/null
@@ -0,0 +1,2 @@
+Subject: (ab)
+
diff --git a/t/t5100/rfc2047-info-0010 b/t/t5100/rfc2047-info-0010
new file mode 100644 (file)
index 0000000..9dad474
--- /dev/null
@@ -0,0 +1,2 @@
+Subject: (a b)
+
diff --git a/t/t5100/rfc2047-info-0011 b/t/t5100/rfc2047-info-0011
new file mode 100644 (file)
index 0000000..9dad474
--- /dev/null
@@ -0,0 +1,2 @@
+Subject: (a b)
+
diff --git a/t/t5100/rfc2047-samples.mbox b/t/t5100/rfc2047-samples.mbox
new file mode 100644 (file)
index 0000000..3ca2470
--- /dev/null
@@ -0,0 +1,48 @@
+From nobody Mon Sep 17 00:00:00 2001
+From: =?US-ASCII?Q?Keith_Moore?= <moore@cs.utk.edu>
+To: =?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= <keld@dkuug.dk>
+CC: =?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>
+Subject: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=
+ =?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=
+
+From nobody Mon Sep 17 00:00:00 2001
+From: =?ISO-8859-1?Q?Olle_J=E4rnefors?= <ojarnef@admin.kth.se>
+To: ietf-822@dimacs.rutgers.edu, ojarnef@admin.kth.se
+Subject: Time for ISO 10646?
+
+From nobody Mon Sep 17 00:00:00 2001
+To: Dave Crocker <dcrocker@mordor.stanford.edu>
+Cc: ietf-822@dimacs.rutgers.edu, paf@comsol.se
+From: =?ISO-8859-1?Q?Patrik_F=E4ltstr=F6m?= <paf@nada.kth.se>
+Subject: Re: RFC-HDR care and feeding
+
+From nobody Mon Sep 17 00:00:00 2001
+From: Nathaniel Borenstein <nsb@thumper.bellcore.com>
+      (=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?=)
+To: Greg Vaudreuil <gvaudre@NRI.Reston.VA.US>, Ned Freed
+   <ned@innosoft.com>, Keith Moore <moore@cs.utk.edu>
+Subject: Test of new header generator
+MIME-Version: 1.0
+Content-type: text/plain; charset=ISO-8859-1
+
+From nobody Mon Sep 17 00:00:00 2001
+Subject: (=?ISO-8859-1?Q?a?=)
+
+From nobody Mon Sep 17 00:00:00 2001
+Subject: (=?ISO-8859-1?Q?a?= b)
+
+From nobody Mon Sep 17 00:00:00 2001
+Subject: (=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=)
+
+From nobody Mon Sep 17 00:00:00 2001
+Subject: (=?ISO-8859-1?Q?a?=  =?ISO-8859-1?Q?b?=)
+
+From nobody Mon Sep 17 00:00:00 2001
+Subject: (=?ISO-8859-1?Q?a?=
+    =?ISO-8859-1?Q?b?=)
+
+From nobody Mon Sep 17 00:00:00 2001
+Subject: (=?ISO-8859-1?Q?a_b?=)
+
+From nobody Mon Sep 17 00:00:00 2001
+Subject: (=?ISO-8859-1?Q?a?= =?ISO-8859-2?Q?_b?=)
index 4bf7947b418963e9b15e393fc738e515b3d2141d..c5ad206b40e1fcf79019cebdfd848d72c17cefcc 100644 (file)
@@ -2,7 +2,10 @@
        
     
 From nobody Mon Sep 17 00:00:00 2001
-From: A U Thor <a.u.thor@example.com>
+From: A (zzz)
+      U
+      Thor
+      <a.u.thor@example.com> (Comment)
 Date: Fri, 9 Jun 2006 00:44:16 -0700
 Subject: [PATCH] a commit.
 
@@ -501,3 +504,60 @@ index 3e5fe51..aabfe5c 100644
 
 --=-=-=--
 
+From bda@mnsspb.ru Wed Nov 12 17:54:41 2008
+From: Dmitriy Blinov <bda@mnsspb.ru>
+To: navy-patches@dinar.mns.mnsspb.ru
+Date: Wed, 12 Nov 2008 17:54:41 +0300
+Message-Id: <1226501681-24923-1-git-send-email-bda@mnsspb.ru>
+X-Mailer: git-send-email 1.5.6.5
+MIME-Version: 1.0
+Content-Type: text/plain;
+  charset=utf-8
+Content-Transfer-Encoding: 8bit
+Subject: [Navy-patches] [PATCH]
+       =?utf-8?b?0JjQt9C80LXQvdGR0L0g0YHQv9C40YHQvtC6INC/0LA=?=
+       =?utf-8?b?0LrQtdGC0L7QsiDQvdC10L7QsdGF0L7QtNC40LzRi9GFINC00LvRjyA=?=
+       =?utf-8?b?0YHQsdC+0YDQutC4?=
+
+textlive-* исправлены на texlive-*
+docutils заменён на python-docutils
+
+Действительно, оказалось, что rest2web вытягивает за собой
+python-docutils. В то время как сам rest2web не нужен.
+
+Signed-off-by: Dmitriy Blinov <bda@mnsspb.ru>
+---
+ howto/build_navy.txt |    6 +++---
+ 1 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/howto/build_navy.txt b/howto/build_navy.txt
+index 3fd3afb..0ee807e 100644
+--- a/howto/build_navy.txt
++++ b/howto/build_navy.txt
+@@ -119,8 +119,8 @@
+    - libxv-dev
+    - libusplash-dev
+    - latex-make
+-   - textlive-lang-cyrillic
+-   - textlive-latex-extra
++   - texlive-lang-cyrillic
++   - texlive-latex-extra
+    - dia
+    - python-pyrex
+    - libtool
+@@ -128,7 +128,7 @@
+    - sox
+    - cython
+    - imagemagick
+-   - docutils
++   - python-docutils
+ #. на машине dinar: добавить свой открытый ssh-ключ в authorized_keys2 пользователя ddev
+ #. на своей машине: отредактировать /etc/sudoers (команда ``visudo``) примерно следующим образом::
+-- 
+1.5.6.5
+From nobody Mon Sep 17 00:00:00 2001
+From: <a.u.thor@example.com> (A U Thor)
+Date: Fri, 9 Jun 2006 00:44:16 -0700
+Subject: [PATCH] a patch
+
index 04522857abb716b8866e0f5153ec33b3ac780536..ccfc64c6eef7e0aba7bd8a8496427470e9020309 100755 (executable)
@@ -180,6 +180,23 @@ test_expect_success \
 
 unset GIT_OBJECT_DIRECTORY
 
+test_expect_success 'survive missing objects/pack directory' '
+       (
+               rm -fr missing-pack &&
+               mkdir missing-pack &&
+               cd missing-pack &&
+               git init &&
+               GOP=.git/objects/pack
+               rm -fr $GOP &&
+               git index-pack --stdin --keep=test <../test-3-${packname_3}.pack &&
+               test -f $GOP/pack-${packname_3}.pack &&
+               test_cmp $GOP/pack-${packname_3}.pack ../test-3-${packname_3}.pack &&
+               test -f $GOP/pack-${packname_3}.idx &&
+               test_cmp $GOP/pack-${packname_3}.idx ../test-3-${packname_3}.idx &&
+               test -f $GOP/pack-${packname_3}.keep
+       )
+'
+
 test_expect_success \
     'verify pack' \
     'git verify-pack   test-1-${packname_1}.idx \
index 884e24253a0a9d262b39ae96ea5c03ecb7ba4072..e6f70d474f2f855d3b2b40eed526e4873b3288d5 100755 (executable)
@@ -10,6 +10,7 @@ test_expect_success \
     'setup' \
     'rm -rf .git
      git init &&
+     git config pack.threads 1 &&
      i=1 &&
      while test $i -le 100
      do
index 771c0a06a4bd01cf40628ffae379b5d992802ef6..55ed7c7935c007573761842cea5978a784c865b3 100755 (executable)
@@ -112,4 +112,42 @@ test_expect_success 'prune: do not prune heads listed as an argument' '
 
 '
 
+test_expect_success 'gc --no-prune' '
+
+       before=$(git count-objects | sed "s/ .*//") &&
+       BLOB=$(echo aleph_0 | git hash-object -w --stdin) &&
+       BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") &&
+       test $((1 + $before)) = $(git count-objects | sed "s/ .*//") &&
+       test -f $BLOB_FILE &&
+       test-chmtime =-$((86400*5001)) $BLOB_FILE &&
+       git config gc.pruneExpire 2.days.ago &&
+       git gc --no-prune &&
+       test 1 = $(git count-objects | sed "s/ .*//") &&
+       test -f $BLOB_FILE
+
+'
+
+test_expect_success 'gc respects gc.pruneExpire' '
+
+       git config gc.pruneExpire 5002.days.ago &&
+       git gc &&
+       test -f $BLOB_FILE &&
+       git config gc.pruneExpire 5000.days.ago &&
+       git gc &&
+       test ! -f $BLOB_FILE
+
+'
+
+test_expect_success 'gc --prune=<date>' '
+
+       BLOB=$(echo aleph_0 | git hash-object -w --stdin) &&
+       BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") &&
+       test-chmtime =-$((86400*5001)) $BLOB_FILE &&
+       git gc --prune=5002.days.ago &&
+       test -f $BLOB_FILE &&
+       git gc --prune=5000.days.ago &&
+       test ! -f $BLOB_FILE
+
+'
+
 test_done
index b21317d68527988d0c2939e7173098b08bfbb64f..f2d5581b12f7d70c9f346da75dced81e12bd4c7f 100755 (executable)
@@ -32,9 +32,7 @@ test_expect_success setup '
        done &&
        git update-ref HEAD "$commit" &&
        git clone ./. victim &&
-       cd victim &&
-       git log &&
-       cd .. &&
+       ( cd victim && git log ) &&
        git update-ref HEAD "$zero" &&
        parent=$zero &&
        i=0 &&
@@ -59,88 +57,84 @@ test_expect_success 'pack the source repository' '
 '
 
 test_expect_success 'pack the destination repository' '
+    (
        cd victim &&
        git repack -a -d &&
-       git prune &&
-       cd ..
+       git prune
+    )
 '
 
-test_expect_success \
-        'pushing rewound head should not barf but require --force' '
-       # should not fail but refuse to update.
-       if git send-pack ./victim/.git/ master
-       then
-               # now it should fail with Pasky patch
-               echo >&2 Gaah, it should have failed.
-               false
-       else
-               echo >&2 Thanks, it correctly failed.
-               true
-       fi &&
-       if cmp victim/.git/refs/heads/master .git/refs/heads/master
-       then
-               # should have been left as it was!
-               false
-       else
-               true
-       fi &&
+test_expect_success 'refuse pushing rewound head without --force' '
+       pushed_head=$(git rev-parse --verify master) &&
+       victim_orig=$(cd victim && git rev-parse --verify master) &&
+       test_must_fail git send-pack ./victim master &&
+       victim_head=$(cd victim && git rev-parse --verify master) &&
+       test "$victim_head" = "$victim_orig" &&
        # this should update
-       git send-pack --force ./victim/.git/ master &&
-       cmp victim/.git/refs/heads/master .git/refs/heads/master
+       git send-pack --force ./victim master &&
+       victim_head=$(cd victim && git rev-parse --verify master) &&
+       test "$victim_head" = "$pushed_head"
 '
 
 test_expect_success \
         'push can be used to delete a ref' '
-       cd victim &&
-       git branch extra master &&
-       cd .. &&
-       test -f victim/.git/refs/heads/extra &&
-       git send-pack ./victim/.git/ :extra master &&
-       ! test -f victim/.git/refs/heads/extra
+       ( cd victim && git branch extra master ) &&
+       git send-pack ./victim :extra master &&
+       ( cd victim &&
+         test_must_fail git rev-parse --verify extra )
 '
 
-unset GIT_CONFIG
-HOME=`pwd`/no-such-directory
-export HOME ;# this way we force the victim/.git/config to be used.
-
-test_expect_success \
-       'pushing a delete should be denied with denyDeletes' '
-       cd victim &&
-       git config receive.denyDeletes true &&
-       git branch extra master &&
-       cd .. &&
-       test -f victim/.git/refs/heads/extra &&
-       test_must_fail git send-pack ./victim/.git/ :extra master
+test_expect_success 'refuse deleting push with denyDeletes' '
+       (
+           cd victim &&
+           ( git branch -D extra || : ) &&
+           git config receive.denyDeletes true &&
+           git branch extra master
+       ) &&
+       test_must_fail git send-pack ./victim :extra master
 '
-rm -f victim/.git/refs/heads/extra
 
-test_expect_success \
-        'pushing with --force should be denied with denyNonFastforwards' '
-       cd victim &&
-       git config receive.denyNonFastforwards true &&
-       cd .. &&
-       git update-ref refs/heads/master master^ || return 1
-       git send-pack --force ./victim/.git/ master && return 1
-       ! test_cmp .git/refs/heads/master victim/.git/refs/heads/master
+test_expect_success 'denyNonFastforwards trumps --force' '
+       (
+           cd victim &&
+           ( git branch -D extra || : ) &&
+           git config receive.denyNonFastforwards true
+       ) &&
+       victim_orig=$(cd victim && git rev-parse --verify master) &&
+       test_must_fail git send-pack --force ./victim master^:master &&
+       victim_head=$(cd victim && git rev-parse --verify master) &&
+       test "$victim_orig" = "$victim_head"
 '
 
-test_expect_success \
-       'pushing does not include non-head refs' '
-       mkdir parent && cd parent &&
-       git init && touch file && git add file && git commit -m add &&
-       cd .. &&
-       git clone parent child && cd child && git push --all &&
-       cd ../parent &&
-       git branch -a >branches && ! grep origin/master branches
+test_expect_success 'push --all excludes remote tracking hierarchy' '
+       mkdir parent &&
+       (
+           cd parent &&
+           git init && : >file && git add file && git commit -m add
+       ) &&
+       git clone parent child &&
+       (
+           cd child && git push --all
+       ) &&
+       (
+           cd parent &&
+           test -z "$(git for-each-ref refs/remotes/origin)"
+       )
 '
 
 rewound_push_setup() {
        rm -rf parent child &&
-       mkdir parent && cd parent &&
-       git init && echo one >file && git add file && git commit -m one &&
-       echo two >file && git commit -a -m two &&
-       cd .. &&
-       git clone parent child && cd child && git reset --hard HEAD^
+       mkdir parent &&
+       (
+           cd parent &&
+           git init &&
+           echo one >file && git add file && git commit -m one &&
+           echo two >file && git commit -a -m two
+       ) &&
+       git clone parent child &&
+       (
+           cd child && git reset --hard HEAD^
+       )
 }
 
 rewound_push_succeeded() {
@@ -156,30 +150,57 @@ rewound_push_failed() {
        fi
 }
 
-test_expect_success \
-       'pushing explicit refspecs respects forcing' '
+test_expect_success 'pushing explicit refspecs respects forcing' '
        rewound_push_setup &&
-       if git send-pack ../parent/.git refs/heads/master:refs/heads/master
-       then
-               false
-       else
-               true
-       fi && rewound_push_failed &&
-       git send-pack ../parent/.git +refs/heads/master:refs/heads/master &&
-       rewound_push_succeeded
+       parent_orig=$(cd parent && git rev-parse --verify master) &&
+       (
+           cd child &&
+           test_must_fail git send-pack ../parent \
+               refs/heads/master:refs/heads/master
+       ) &&
+       parent_head=$(cd parent && git rev-parse --verify master) &&
+       test "$parent_orig" = "$parent_head" &&
+       (
+           cd child &&
+           git send-pack ../parent \
+               +refs/heads/master:refs/heads/master
+       ) &&
+       parent_head=$(cd parent && git rev-parse --verify master) &&
+       child_head=$(cd parent && git rev-parse --verify master) &&
+       test "$parent_head" = "$child_head"
 '
 
-test_expect_success \
-       'pushing wildcard refspecs respects forcing' '
+test_expect_success 'pushing wildcard refspecs respects forcing' '
        rewound_push_setup &&
-       if git send-pack ../parent/.git refs/heads/*:refs/heads/*
-       then
-               false
-       else
-               true
-       fi && rewound_push_failed &&
-       git send-pack ../parent/.git +refs/heads/*:refs/heads/* &&
-       rewound_push_succeeded
+       parent_orig=$(cd parent && git rev-parse --verify master) &&
+       (
+           cd child &&
+           test_must_fail git send-pack ../parent \
+               "refs/heads/*:refs/heads/*"
+       ) &&
+       parent_head=$(cd parent && git rev-parse --verify master) &&
+       test "$parent_orig" = "$parent_head" &&
+       (
+           cd child &&
+           git send-pack ../parent \
+               "+refs/heads/*:refs/heads/*"
+       ) &&
+       parent_head=$(cd parent && git rev-parse --verify master) &&
+       child_head=$(cd parent && git rev-parse --verify master) &&
+       test "$parent_head" = "$child_head"
+'
+
+test_expect_success 'warn pushing to delete current branch' '
+       rewound_push_setup &&
+       (
+           cd child &&
+           git send-pack ../parent :refs/heads/master 2>errs
+       ) &&
+       grep "warning: to refuse deleting" child/errs &&
+       (
+               cd parent &&
+               test_must_fail git rev-parse --verify master
+       )
 '
 
 test_done
index 9b2e1a94c5fb5aa094853a169cbdf2f7dc56e152..4fdb418550ee0f3c93e80f8c3df41094c0ada00c 100755 (executable)
@@ -71,4 +71,16 @@ test_expect_success 'post-checkout receives the right args when not switching br
         test $old = $new -a $flag = 0
 '
 
+mkdir -p templates/hooks
+cat >templates/hooks/post-checkout <<'EOF'
+#!/bin/sh
+echo $@ > $GIT_DIR/post-checkout.args
+EOF
+chmod +x templates/hooks/post-checkout
+
+test_expect_success 'post-checkout hook is triggered by clone' '
+       git clone --template=templates . clone3 &&
+       test -f clone3/.git/post-checkout.args
+'
+
 test_done
index 1f59960d90c31f02768666c86654a97e1fad9305..eb637184a00007c61e6d92f7b5546eed6ec5a0ae 100755 (executable)
@@ -107,6 +107,32 @@ test_expect_success 'remove remote' '
 )
 '
 
+test_expect_success 'remove remote protects non-remote branches' '
+(
+       cd test &&
+       (cat >expect1 <<EOF
+Note: A non-remote branch was not removed; to delete it, use:
+  git branch -d master
+EOF
+    cat >expect2 <<EOF
+Note: Non-remote branches were not removed; to delete them, use:
+  git branch -d foobranch
+  git branch -d master
+EOF
+) &&
+       git tag footag
+       git config --add remote.oops.fetch "+refs/*:refs/*" &&
+       git remote rm oops 2>actual1 &&
+       git branch foobranch &&
+       git config --add remote.oops.fetch "+refs/*:refs/*" &&
+       git remote rm oops 2>actual2 &&
+       git branch -d foobranch &&
+       git tag -d footag &&
+       test_cmp expect1 actual1 &&
+       test_cmp expect2 actual2
+)
+'
+
 cat > test/expect << EOF
 * remote origin
   URL: $(pwd)/one
@@ -376,4 +402,31 @@ test_expect_success 'migrate a remote from named file in $GIT_DIR/branches' '
         test "$(git config remote.origin.fetch)" = "refs/heads/master:refs/heads/origin")
 '
 
+test_expect_success 'remote prune to cause a dangling symref' '
+       git clone one seven &&
+       (
+               cd one &&
+               git checkout side2 &&
+               git branch -D master
+       ) &&
+       (
+               cd seven &&
+               git remote prune origin
+       ) 2>err &&
+       grep "has become dangling" err &&
+
+       : And the dangling symref will not cause other annoying errors
+       (
+               cd seven &&
+               git branch -a
+       ) 2>err &&
+       ! grep "points nowhere" err
+       (
+               cd seven &&
+               test_must_fail git branch nomore origin
+       ) 2>err &&
+       grep "dangling symref" err
+'
+
 test_done
+
index 4426df9226535c55eacff80217c4ab70f77639b6..89649e7a9b2dc138bb9618d4e5770e76f62dc7e0 100755 (executable)
@@ -492,7 +492,7 @@ test_expect_success 'warn on push to HEAD of non-bare repository' '
                git checkout master &&
                git config receive.denyCurrentBranch warn) &&
        git push testrepo master 2>stderr &&
-       grep "warning.*this may cause confusion" stderr
+       grep "warning: updating the current branch" stderr
 '
 
 test_expect_success 'deny push to HEAD of non-bare repository' '
@@ -510,7 +510,7 @@ test_expect_success 'allow push to HEAD of bare repository (bare)' '
                git config receive.denyCurrentBranch true &&
                git config core.bare true) &&
        git push testrepo master 2>stderr &&
-       ! grep "warning.*this may cause confusion" stderr
+       ! grep "warning: updating the current branch" stderr
 '
 
 test_expect_success 'allow push to HEAD of non-bare repository (config)' '
@@ -520,7 +520,7 @@ test_expect_success 'allow push to HEAD of non-bare repository (config)' '
                git config receive.denyCurrentBranch false
        ) &&
        git push testrepo master 2>stderr &&
-       ! grep "warning.*this may cause confusion" stderr
+       ! grep "warning: updating the current branch" stderr
 '
 
 test_expect_success 'fetch with branches' '
index 6dfc55ad61246e1520a4909e02603b5328fee2a2..96be5236a2faf7178edc3c60094ecc37da741a65 100755 (executable)
@@ -103,4 +103,41 @@ test_expect_success 'bob works and pushes' '
        )
 '
 
+test_expect_success 'alice works and pushes yet again' '
+       (
+               # Alice does not care what Bob does.  She does not
+               # even have to be aware of his existence.  She just
+               # keeps working and pushing
+               cd alice-work &&
+               echo more and more alice >file &&
+               git commit -a -m sixth.1 &&
+               echo more and more alice >>file &&
+               git commit -a -m sixth.2 &&
+               echo more and more alice >>file &&
+               git commit -a -m sixth.3 &&
+               git push ../alice-pub
+       )
+'
+
+test_expect_success 'bob works and pushes again' '
+       (
+               cd alice-pub &&
+               git cat-file commit master >../bob-work/commit
+       )
+       (
+               # This time Bob does not pull from Alice, and
+               # the master branch at her public repository points
+               # at a commit Bob does not fully know about, but
+               # he happens to have the commit object (but not the
+               # necessary tree) in his repository from Alice.
+               # This should not prevent the push by Bob from
+               # succeeding.
+               cd bob-work &&
+               git hash-object -t commit -w commit &&
+               echo even more bob >file &&
+               git commit -a -m seventh &&
+               git push ../bob-pub
+       )
+'
+
 test_done
index c236b5e83beed8997e8f6c3a50f0bb74f73f3f33..10e5fd0d5a5c38eaa102d88e607f2585e1157526 100755 (executable)
@@ -94,6 +94,18 @@ test_expect_success 'MKCOL sends directory names with trailing slashes' '
 
 '
 
+x1="[0-9a-f]"
+x2="$x1$x1"
+x5="$x1$x1$x1$x1$x1"
+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] "
+
+'
+
 stop_httpd
 
 test_done
index 78a3fa639c97b7dce054d89c74c67a855d0d7954..44793f2eee0e0c14802ecedfe0637cc6855f51b6 100755 (executable)
@@ -125,4 +125,38 @@ test_expect_success 'clone to destination with extra trailing /' '
 
 '
 
+test_expect_success 'clone to an existing empty directory' '
+       mkdir target-3 &&
+       git clone src target-3 &&
+       T=$( cd target-3 && git rev-parse HEAD ) &&
+       S=$( cd src && git rev-parse HEAD ) &&
+       test "$T" = "$S"
+'
+
+test_expect_success 'clone to an existing non-empty directory' '
+       mkdir target-4 &&
+       >target-4/Fakefile &&
+       test_must_fail git clone src target-4
+'
+
+test_expect_success 'clone to an existing path' '
+       >target-5 &&
+       test_must_fail git clone src target-5
+'
+
+test_expect_success 'clone a void' '
+       mkdir src-0 &&
+       (
+               cd src-0 && git init
+       ) &&
+       git clone src-0 target-6 &&
+       (
+               cd src-0 && test_commit A
+       ) &&
+       git clone src-0 target-7 &&
+       # There is no reason to insist they are bit-for-bit
+       # identical, but this test should suffice for now.
+       test_cmp target-6/.git/config target-7/.git/config
+'
+
 test_done
index fe0fda282ca4f085f506c743848966fa6bb93591..3559d179647035a6ad8783aabfbecb8edcd4c254 100755 (executable)
@@ -116,4 +116,20 @@ test_expect_success 'bundle clone with nonexistent HEAD' '
        test ! -e .git/refs/heads/master
 '
 
+test_expect_success 'clone empty repository' '
+       cd "$D" &&
+       mkdir empty &&
+       (cd empty && git init) &&
+       git clone empty empty-clone &&
+       test_tick &&
+       (cd empty-clone
+        echo "content" >> foo &&
+        git add foo &&
+        git commit -m "Initial commit" &&
+        git push origin master &&
+        expected=$(git rev-parse master) &&
+        actual=$(git --git-dir=../empty/.git rev-parse master) &&
+        test $actual = $expected)
+'
+
 test_done
diff --git a/t/t5704-bundle.sh b/t/t5704-bundle.sh
new file mode 100755 (executable)
index 0000000..a8f4419
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+test_description='some bundle related tests'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+
+       : > file &&
+       git add file &&
+       test_tick &&
+       git commit -m initial &&
+       test_tick &&
+       git tag -m tag tag &&
+       : > file2 &&
+       git add file2 &&
+       : > file3 &&
+       test_tick &&
+       git commit -m second &&
+       git add file3 &&
+       test_tick &&
+       git commit -m third
+
+'
+
+test_expect_success 'tags can be excluded by rev-list options' '
+
+       git bundle create bundle --all --since=7.Apr.2005.15:16:00.-0700 &&
+       git ls-remote bundle > output &&
+       ! grep tag output
+
+'
+
+test_done
diff --git a/t/t5705-clone-2gb.sh b/t/t5705-clone-2gb.sh
new file mode 100755 (executable)
index 0000000..9f52154
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+test_description='Test cloning a repository larger than 2 gigabyte'
+. ./test-lib.sh
+
+test -z "$GIT_TEST_CLONE_2GB" &&
+say "Skipping expensive 2GB clone test; enable it with GIT_TEST_CLONE_2GB=t" &&
+test_done &&
+exit
+
+test_expect_success 'setup' '
+
+       git config pack.compression 0 &&
+       git config pack.depth 0 &&
+       blobsize=$((20*1024*1024)) &&
+       blobcount=$((2*1024*1024*1024/$blobsize+1)) &&
+       i=1 &&
+       (while test $i -le $blobcount
+        do
+               printf "Generating blob $i/$blobcount\r" >&2 &&
+               printf "blob\nmark :$i\ndata $blobsize\n" &&
+               #test-genrandom $i $blobsize &&
+               printf "%-${blobsize}s" $i &&
+               echo "M 100644 :$i $i" >> commit
+               i=$(($i+1)) ||
+               echo $? > exit-status
+        done &&
+        echo "commit refs/heads/master" &&
+        echo "author A U Thor <author@email.com> 123456789 +0000" &&
+        echo "committer C O Mitter <committer@email.com> 123456789 +0000" &&
+        echo "data 5" &&
+        echo ">2gb" &&
+        cat commit) |
+       git fast-import &&
+       test ! -f exit-status
+
+'
+
+test_expect_success 'clone' '
+
+       git clone --bare --no-hardlinks . clone
+
+'
+
+test_done
index 86bf7e14ba5bb9a76ef00cb5a3564e326f674a18..59d1f6283bec5cf7740d988dfd090c1463389c71 100755 (executable)
@@ -14,7 +14,7 @@ touch foo && git add foo && git commit -m "added foo" &&
 test_format() {
        cat >expect.$1
        test_expect_success "format $1" "
-git rev-list --pretty=format:$2 master >output.$1 &&
+git rev-list --pretty=format:'$2' master >output.$1 &&
 test_cmp expect.$1 output.$1
 "
 }
@@ -101,6 +101,13 @@ commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
 \e[31mfoo\e[32mbar\e[34mbaz\e[mxyzzy
 EOF
 
+test_format advanced-colors '%C(red yellow bold)foo%C(reset)' <<'EOF'
+commit 131a310eb913d107dd3c09a65d1651175898735d
+\e[1;31;43mfoo\e[m
+commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+\e[1;31;43mfoo\e[m
+EOF
+
 cat >commit-msg <<'EOF'
 Test printing of complex bodies
 
index dd7eac84ea191fb797075eca3706498edad68b32..052a6c90f5a184ddc82f2db1a2907a1b1104166c 100755 (executable)
@@ -224,6 +224,31 @@ test_expect_success 'bisect skip: cannot tell between 2 commits' '
        fi
 '
 
+# $HASH1 is good, $HASH4 is both skipped and bad, we skip $HASH3
+# and $HASH2 is good,
+# so we should not be able to tell the first bad commit
+# among $HASH3 and $HASH4
+test_expect_success 'bisect skip: with commit both bad and skipped' '
+       git bisect start &&
+       git bisect skip &&
+       git bisect bad &&
+       git bisect good $HASH1 &&
+       git bisect skip &&
+       if git bisect good > my_bisect_log.txt
+       then
+               echo Oops, should have failed.
+               false
+       else
+               test $? -eq 2 &&
+               grep "first bad commit could be any of" my_bisect_log.txt &&
+               ! grep $HASH1 my_bisect_log.txt &&
+               ! grep $HASH2 my_bisect_log.txt &&
+               grep $HASH3 my_bisect_log.txt &&
+               grep $HASH4 my_bisect_log.txt &&
+               git bisect reset
+       fi
+'
+
 # We want to automatically find the commit that
 # introduced "Another" into hello.
 test_expect_success \
index b0a9d7d536314ec842b141c09ba0d6f8b06b6288..329c851685b1c663ee88d45a5d21d452a293fa8e 100755 (executable)
@@ -39,13 +39,31 @@ test_expect_success 'result is really identical' '
 '
 
 test_expect_success 'rewrite bare repository identically' '
-       (git config core.bare true && cd .git && git filter-branch branch)
+       (git config core.bare true && cd .git &&
+        git filter-branch branch > filter-output 2>&1 &&
+       ! fgrep fatal filter-output)
 '
 git config core.bare false
 test_expect_success 'result is really identical' '
        test $H = $(git rev-parse HEAD)
 '
 
+TRASHDIR=$(pwd)
+test_expect_success 'correct GIT_DIR while using -d' '
+       mkdir drepo &&
+       ( cd drepo &&
+       git init &&
+       test_commit drepo &&
+       git filter-branch -d "$TRASHDIR/dfoo" \
+               --index-filter "cp \"$TRASHDIR\"/dfoo/backup-refs \"$TRASHDIR\"" \
+       ) &&
+       grep drepo "$TRASHDIR/backup-refs"
+'
+
+test_expect_success 'Fail if commit filter fails' '
+       test_must_fail git filter-branch -f --commit-filter "exit 1" HEAD
+'
+
 test_expect_success 'rewrite, renaming a specific file' '
        git filter-branch -f --tree-filter "mv d doh || :" HEAD
 '
@@ -262,4 +280,12 @@ test_expect_success 'Tag name filtering allows slashes in tag names' '
        test_cmp expect actual
 '
 
+test_expect_success 'Prune empty commits' '
+       git rev-list HEAD > expect &&
+       make_commit to_remove &&
+       git filter-branch -f --index-filter "git update-index --remove to_remove" --prune-empty HEAD &&
+       git rev-list HEAD > actual &&
+       test_cmp expect actual
+'
+
 test_done
index f377fea4bb1d32c95096defb9fb08b291b67dbff..69501e2711f26a7c498d545520dcf47c00d9b1e1 100755 (executable)
@@ -1090,6 +1090,121 @@ test_expect_success 'filename for the message is relative to cwd' '
        git cat-file tag tag-from-subdir-2 | grep "in sub directory"
 '
 
+# create a few more commits to test --contains
+
+hash1=$(git rev-parse HEAD)
+
+test_expect_success 'creating second commit and tag' '
+       echo foo-2.0 >foo &&
+       git add foo &&
+       git commit -m second
+       git tag v2.0
+'
+
+hash2=$(git rev-parse HEAD)
+
+test_expect_success 'creating third commit without tag' '
+       echo foo-dev >foo &&
+       git add foo &&
+       git commit -m third
+'
+
+hash3=$(git rev-parse HEAD)
+
+# simple linear checks of --continue
+
+cat > expected <<EOF
+v0.2.1
+v1.0
+v1.0.1
+v1.1.3
+v2.0
+EOF
+
+test_expect_success 'checking that first commit is in all tags (hash)' "
+       git tag -l --contains $hash1 v* >actual
+       test_cmp expected actual
+"
+
+# other ways of specifying the commit
+test_expect_success 'checking that first commit is in all tags (tag)' "
+       git tag -l --contains v1.0 v* >actual
+       test_cmp expected actual
+"
+
+test_expect_success 'checking that first commit is in all tags (relative)' "
+       git tag -l --contains HEAD~2 v* >actual
+       test_cmp expected actual
+"
+
+cat > expected <<EOF
+v2.0
+EOF
+
+test_expect_success 'checking that second commit only has one tag' "
+       git tag -l --contains $hash2 v* >actual
+       test_cmp expected actual
+"
+
+
+cat > expected <<EOF
+EOF
+
+test_expect_success 'checking that third commit has no tags' "
+       git tag -l --contains $hash3 v* >actual
+       test_cmp expected actual
+"
+
+# how about a simple merge?
+
+test_expect_success 'creating simple branch' '
+       git branch stable v2.0 &&
+        git checkout stable &&
+       echo foo-3.0 > foo &&
+       git commit foo -m fourth
+       git tag v3.0
+'
+
+hash4=$(git rev-parse HEAD)
+
+cat > expected <<EOF
+v3.0
+EOF
+
+test_expect_success 'checking that branch head only has one tag' "
+       git tag -l --contains $hash4 v* >actual
+       test_cmp expected actual
+"
+
+test_expect_success 'merging original branch into this branch' '
+       git merge --strategy=ours master &&
+        git tag v4.0
+'
+
+cat > expected <<EOF
+v4.0
+EOF
+
+test_expect_success 'checking that original branch head has one tag now' "
+       git tag -l --contains $hash3 v* >actual
+       test_cmp expected actual
+"
+
+cat > expected <<EOF
+v0.2.1
+v1.0
+v1.0.1
+v1.1.3
+v2.0
+v3.0
+v4.0
+EOF
+
+test_expect_success 'checking that initial commit is in all tags' "
+       git tag -l --contains $hash1 v* >actual
+       test_cmp expected actual
+"
+
 # mixing modes and options:
 
 test_expect_success 'mixing incompatibles modes and options is forbidden' '
index 21c19a28cfdcef1478a97168cae8b6d6cae597a1..af690ec6c1f36871dbd0044d22ab78ab95103541 100755 (executable)
@@ -283,4 +283,17 @@ test_expect_success 'gracefully add submodule with a trailing slash' '
 
 '
 
+test_expect_success 'ls-files gracefully handles trailing slash' '
+
+       test "init" = "$(git ls-files init/)"
+
+'
+
+test_expect_success 'submodule <invalid-path> warns' '
+
+       git submodule no-such-submodule 2> output.err &&
+       grep "^error: .*no-such-submodule" output.err
+
+'
+
 test_done
index 6e18a96319bef5c4ddfbc86ce79b02b364f04387..5998baf27b5ad0fb32bf0fd42023d029a65d0e6b 100755 (executable)
@@ -149,10 +149,7 @@ EOF
 
 test_expect_success '--signoff' '
        echo "yet another content *narf*" >> foo &&
-       echo "zort" | (
-               test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
-               git commit -s -F - foo
-       ) &&
+       echo "zort" | git commit -s -F - foo &&
        git cat-file commit HEAD | sed "1,/^$/d" > output &&
        test_cmp expect output
 '
index 63bfc6d8b3f6917cad1b51a73a84ba61762b220d..b4e2b4db8421c24cb714fcad7cd6ec00ea88c016 100755 (executable)
@@ -127,6 +127,26 @@ test_expect_success \
        "showing committed revisions" \
        "git rev-list HEAD >current"
 
+cat >editor <<\EOF
+#!/bin/sh
+sed -e "s/good/bad/g" < "$1" > "$1-"
+mv "$1-" "$1"
+EOF
+chmod 755 editor
+
+cat >msg <<EOF
+A good commit message.
+EOF
+
+test_expect_success \
+       'editor not invoked if -F is given' '
+        echo "moo" >file &&
+        VISUAL=./editor git commit -a -F msg &&
+        git show -s --pretty=format:"%s" | grep -q good &&
+        echo "quack" >file &&
+        echo "Another good message." | VISUAL=./editor git commit -a -F - &&
+        git show -s --pretty=format:"%s" | grep -q good
+        '
 # We could just check the head sha1, but checking each commit makes it
 # easier to isolate bugs.
 
index 09fa5f115c9fabe1fec60a5597439b2c7f9ded6d..e768c3eb2d48a9af2fc92729a6c20126f67ec866 100755 (executable)
@@ -9,38 +9,81 @@ Testing basic merge tool invocation'
 
 . ./test-lib.sh
 
+# All the mergetool test work by checking out a temporary branch based
+# off 'branch1' and then merging in master and checking the results of
+# running mergetool
+
 test_expect_success 'setup' '
     echo master >file1 &&
-    git add file1 &&
+    mkdir subdir &&
+    echo master sub >subdir/file3 &&
+    git add file1 subdir/file3 &&
     git commit -m "added file1" &&
+
     git checkout -b branch1 master &&
     echo branch1 change >file1 &&
     echo branch1 newfile >file2 &&
-    git add file1 file2 &&
+    echo branch1 sub >subdir/file3 &&
+    git add file1 file2 subdir/file3 &&
     git commit -m "branch1 changes" &&
-    git checkout -b branch2 master &&
-    echo branch2 change >file1 &&
-    echo branch2 newfile >file2 &&
-    git add file1 file2 &&
-    git commit -m "branch2 changes" &&
+
     git checkout master &&
     echo master updated >file1 &&
     echo master new >file2 &&
-    git add file1 file2 &&
-    git commit -m "master updates"
-'
+    echo master new sub >subdir/file3 &&
+    git add file1 file2 subdir/file3 &&
+    git commit -m "master updates" &&
 
-test_expect_success 'custom mergetool' '
     git config merge.tool mytool &&
     git config mergetool.mytool.cmd "cat \"\$REMOTE\" >\"\$MERGED\"" &&
-    git config mergetool.mytool.trustExitCode true &&
-       git checkout branch1 &&
+    git config mergetool.mytool.trustExitCode true
+'
+
+test_expect_success 'custom mergetool' '
+    git checkout -b test1 branch1 &&
     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 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool file2 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) &&
     test "$(cat file1)" = "master updated" &&
     test "$(cat file2)" = "master new" &&
-       git commit -m "branch1 resolved with mergetool"
+    test "$(cat subdir/file3)" = "master new sub" &&
+    git commit -m "branch1 resolved with mergetool"
 '
 
+test_expect_success 'mergetool crlf' '
+    git config core.autocrlf true &&
+    git checkout -b test2 branch1
+    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 subdir/file3 >/dev/null 2>&1 ) &&
+    test "$(printf x | cat file1 -)" = "$(printf "master updated\r\nx")" &&
+    test "$(printf x | cat file2 -)" = "$(printf "master new\r\nx")" &&
+    test "$(printf x | cat subdir/file3 -)" = "$(printf "master new sub\r\nx")" &&
+    git commit -m "branch1 resolved with mergetool - autocrlf" &&
+    git config core.autocrlf false &&
+    git reset --hard
+'
+
+test_expect_success 'mergetool in subdir' '
+    git checkout -b test3 branch1
+    cd subdir && (
+    test_must_fail git merge master >/dev/null 2>&1 &&
+    ( yes "" | git mergetool file3 >/dev/null 2>&1 ) &&
+    test "$(cat file3)" = "master new sub" )
+'
+
+# We can't merge files from parent directories when running mergetool
+# from a subdir. Is this a bug?
+#
+#test_expect_failure 'mergetool in subdir' '
+#    cd subdir && (
+#    ( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) &&
+#    ( yes "" | git mergetool ../file2 >/dev/null 2>&1 ) &&
+#    test "$(cat ../file1)" = "master updated" &&
+#    test "$(cat ../file2)" = "master new" &&
+#    git commit -m "branch1 resolved with mergetool - subdir" )
+#'
+
 test_done
index 3f602ea7de498e9e60bd61c8f58b66947c9923fe..f5682d66db2832311774fb68b7264002dfeb091f 100755 (executable)
@@ -69,5 +69,24 @@ test_expect_success 'packed obs in alt ODB are repacked even when local repo is
        done
 '
 
+test_expect_failure 'packed obs in alt ODB are repacked when local repo has packs' '
+       rm -f .git/objects/pack/* &&
+       echo new_content >> file1 &&
+       git add file1 &&
+       git commit -m more_content &&
+       git repack &&
+       git repack -a -d &&
+       myidx=$(ls -1 .git/objects/pack/*.idx) &&
+       test -f "$myidx" &&
+       for p in alt_objects/pack/*.idx; do
+               git verify-pack -v $p | sed -n -e "/^[0-9a-f]\{40\}/p"
+       done | while read sha1 rest; do
+               if ! ( git verify-pack -v $myidx | grep "^$sha1" ); then
+                       echo "Missing object in local pack: $sha1"
+                       return 1
+               fi
+       done
+'
+
 test_done
 
index 63a8225ae5c7ffc265e28c9bf17bbec87ab339d8..5babdf26e625933268b911cc6e81f6a448f7f78d 100755 (executable)
@@ -50,12 +50,10 @@ test_expect_success '-A with -d option leaves unreachable objects unpacked' '
 
 compare_mtimes ()
 {
-       perl -e 'my $reference = shift;
-                foreach my $file (@ARGV) {
-                       exit(1) unless(-f $file && -M $file == -M $reference);
-                }
-                exit(0);
-               ' -- "$@"
+       read tref rest &&
+       while read t rest; do
+               test "$tref" = "$t" || break
+       done
 }
 
 test_expect_success '-A without -d option leaves unreachable objects packed' '
@@ -87,7 +85,9 @@ test_expect_success 'unpacked objects receive timestamp of pack file' '
        tmppack=".git/objects/pack/tmp_pack" &&
        ln "$packfile" "$tmppack" &&
        git repack -A -l -d &&
-       compare_mtimes "$tmppack" "$fsha1path" "$csha1path" "$tsha1path"
+       test-chmtime -v +0 "$tmppack" "$fsha1path" "$csha1path" "$tsha1path" \
+               > mtimes &&
+       compare_mtimes < mtimes
 '
 
 test_done
index cb3d1837709fbce30fc508e339ce671c55eb9a1b..08d5b91c9176bd9c6a23ffa72b2729bb1d2ba0d8 100755 (executable)
@@ -32,16 +32,59 @@ clean_fake_sendmail() {
 }
 
 test_expect_success 'Extract patches' '
-    patches=`git format-patch -n HEAD^1`
+    patches=`git format-patch -s --cc="One <one@example.com>" --cc=two@example.com -n HEAD^1`
 '
 
+# Test no confirm early to ensure remaining tests will not hang
+test_no_confirm () {
+       rm -f no_confirm_okay
+       echo n | \
+               GIT_SEND_EMAIL_NOTTY=1 \
+               git send-email \
+               --from="Example <from@example.com>" \
+               --to=nobody@example.com \
+               --smtp-server="$(pwd)/fake.sendmail" \
+               $@ \
+               $patches > stdout &&
+               test_must_fail grep "Send this email" stdout &&
+               > no_confirm_okay
+}
+
+# Exit immediately to prevent hang if a no-confirm test fails
+check_no_confirm () {
+       test -f no_confirm_okay || {
+               say 'No confirm test failed; skipping remaining tests to prevent hanging'
+               test_done
+       }
+}
+
+test_expect_success 'No confirm with --suppress-cc' '
+       test_no_confirm --suppress-cc=sob
+'
+check_no_confirm
+
+test_expect_success 'No confirm with --confirm=never' '
+       test_no_confirm --confirm=never
+'
+check_no_confirm
+
+# leave sendemail.confirm set to never after this so that none of the
+# remaining tests prompt unintentionally.
+test_expect_success 'No confirm with sendemail.confirm=never' '
+       git config sendemail.confirm never &&
+       test_no_confirm --compose --subject=foo
+'
+check_no_confirm
+
 test_expect_success 'Send patches' '
-     git send-email --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors
+     git send-email --suppress-cc=sob --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors
 '
 
 cat >expected <<\EOF
 !nobody@example.com!
 !author@example.com!
+!one@example.com!
+!two@example.com!
 EOF
 test_expect_success \
     'Verify commandline' \
@@ -50,13 +93,15 @@ test_expect_success \
 cat >expected-show-all-headers <<\EOF
 0001-Second.patch
 (mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
+(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com'
+(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com'
 Dry-OK. Log says:
 Server: relay.example.com
 MAIL FROM:<from@example.com>
-RCPT TO:<to@example.com>,<cc@example.com>,<author@example.com>,<bcc@example.com>
+RCPT TO:<to@example.com>,<cc@example.com>,<author@example.com>,<one@example.com>,<two@example.com>,<bcc@example.com>
 From: Example <from@example.com>
 To: to@example.com
-Cc: cc@example.com, A <author@example.com>
+Cc: cc@example.com, A <author@example.com>, One <one@example.com>, two@example.com
 Subject: [PATCH 1/1] Second.
 Date: DATE-STRING
 Message-Id: MESSAGE-ID-STRING
@@ -70,6 +115,7 @@ EOF
 test_expect_success 'Show all headers' '
        git send-email \
                --dry-run \
+               --suppress-cc=sob \
                --from="Example <from@example.com>" \
                --to=to@example.com \
                --cc=cc@example.com \
@@ -104,6 +150,28 @@ test_expect_success 'no patch was sent' '
        ! test -e commandline1
 '
 
+test_expect_success 'Author From: in message body' '
+       clean_fake_sendmail &&
+       git send-email \
+               --from="Example <nobody@example.com>" \
+               --to=nobody@example.com \
+               --smtp-server="$(pwd)/fake.sendmail" \
+               $patches &&
+       sed "1,/^$/d" < msgtxt1 > msgbody1
+       grep "From: A <author@example.com>" msgbody1
+'
+
+test_expect_success 'Author From: not in message body' '
+       clean_fake_sendmail &&
+       git send-email \
+               --from="A <author@example.com>" \
+               --to=nobody@example.com \
+               --smtp-server="$(pwd)/fake.sendmail" \
+               $patches &&
+       sed "1,/^$/d" < msgtxt1 > msgbody1
+       ! grep "From: A <author@example.com>" msgbody1
+'
+
 test_expect_success 'allow long lines with --no-validate' '
        git send-email \
                --from="Example <nobody@example.com>" \
@@ -148,15 +216,13 @@ test_set_editor "$(pwd)/fake-editor"
 
 test_expect_success '--compose works' '
        clean_fake_sendmail &&
-       echo y | \
-               GIT_SEND_EMAIL_NOTTY=1 \
-               git send-email \
-               --compose --subject foo \
-               --from="Example <nobody@example.com>" \
-               --to=nobody@example.com \
-               --smtp-server="$(pwd)/fake.sendmail" \
-               $patches \
-               2>errors
+       git send-email \
+       --compose --subject foo \
+       --from="Example <nobody@example.com>" \
+       --to=nobody@example.com \
+       --smtp-server="$(pwd)/fake.sendmail" \
+       $patches \
+       2>errors
 '
 
 test_expect_success 'first message is compose text' '
@@ -167,16 +233,18 @@ test_expect_success 'second message is patch' '
        grep "Subject:.*Second" msgtxt2
 '
 
-cat >expected-show-all-headers <<\EOF
+cat >expected-suppress-sob <<\EOF
 0001-Second.patch
 (mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
+(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com'
+(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com'
 Dry-OK. Log says:
 Server: relay.example.com
 MAIL FROM:<from@example.com>
-RCPT TO:<to@example.com>,<cc@example.com>,<author@example.com>
+RCPT TO:<to@example.com>,<cc@example.com>,<author@example.com>,<one@example.com>,<two@example.com>
 From: Example <from@example.com>
 To: to@example.com
-Cc: cc@example.com, A <author@example.com>
+Cc: cc@example.com, A <author@example.com>, One <one@example.com>, two@example.com
 Subject: [PATCH 1/1] Second.
 Date: DATE-STRING
 Message-Id: MESSAGE-ID-STRING
@@ -185,10 +253,10 @@ X-Mailer: X-MAILER-STRING
 Result: OK
 EOF
 
-test_expect_success 'sendemail.cc set' '
-       git config sendemail.cc cc@example.com &&
+test_suppression () {
        git send-email \
                --dry-run \
+               --suppress-cc=$1 \
                --from="Example <from@example.com>" \
                --to=to@example.com \
                --smtp-server relay.example.com \
@@ -196,20 +264,27 @@ test_expect_success 'sendemail.cc set' '
        sed     -e "s/^\(Date:\).*/\1 DATE-STRING/" \
                -e "s/^\(Message-Id:\).*/\1 MESSAGE-ID-STRING/" \
                -e "s/^\(X-Mailer:\).*/\1 X-MAILER-STRING/" \
-               >actual-show-all-headers &&
-       test_cmp expected-show-all-headers actual-show-all-headers
+               >actual-suppress-$1 &&
+       test_cmp expected-suppress-$1 actual-suppress-$1
+}
+
+test_expect_success 'sendemail.cc set' '
+       git config sendemail.cc cc@example.com &&
+       test_suppression sob
 '
 
-cat >expected-show-all-headers <<\EOF
+cat >expected-suppress-sob <<\EOF
 0001-Second.patch
 (mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
+(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com'
+(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com'
 Dry-OK. Log says:
 Server: relay.example.com
 MAIL FROM:<from@example.com>
-RCPT TO:<to@example.com>,<author@example.com>
+RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com>
 From: Example <from@example.com>
 To: to@example.com
-Cc: A <author@example.com>
+Cc: A <author@example.com>, One <one@example.com>, two@example.com
 Subject: [PATCH 1/1] Second.
 Date: DATE-STRING
 Message-Id: MESSAGE-ID-STRING
@@ -220,17 +295,166 @@ EOF
 
 test_expect_success 'sendemail.cc unset' '
        git config --unset sendemail.cc &&
-       git send-email \
-               --dry-run \
-               --from="Example <from@example.com>" \
-               --to=to@example.com \
-               --smtp-server relay.example.com \
-               $patches |
-       sed     -e "s/^\(Date:\).*/\1 DATE-STRING/" \
-               -e "s/^\(Message-Id:\).*/\1 MESSAGE-ID-STRING/" \
-               -e "s/^\(X-Mailer:\).*/\1 X-MAILER-STRING/" \
-               >actual-show-all-headers &&
-       test_cmp expected-show-all-headers actual-show-all-headers
+       test_suppression sob
+'
+
+cat >expected-suppress-all <<\EOF
+0001-Second.patch
+Dry-OK. Log says:
+Server: relay.example.com
+MAIL FROM:<from@example.com>
+RCPT TO:<to@example.com>
+From: Example <from@example.com>
+To: to@example.com
+Subject: [PATCH 1/1] Second.
+Date: DATE-STRING
+Message-Id: MESSAGE-ID-STRING
+X-Mailer: X-MAILER-STRING
+
+Result: OK
+EOF
+
+test_expect_success '--suppress-cc=all' '
+       test_suppression all
+'
+
+cat >expected-suppress-body <<\EOF
+0001-Second.patch
+(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
+(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com'
+(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com'
+Dry-OK. Log says:
+Server: relay.example.com
+MAIL FROM:<from@example.com>
+RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com>
+From: Example <from@example.com>
+To: to@example.com
+Cc: A <author@example.com>, One <one@example.com>, two@example.com
+Subject: [PATCH 1/1] Second.
+Date: DATE-STRING
+Message-Id: MESSAGE-ID-STRING
+X-Mailer: X-MAILER-STRING
+
+Result: OK
+EOF
+
+test_expect_success '--suppress-cc=body' '
+       test_suppression body
+'
+
+cat >expected-suppress-sob <<\EOF
+0001-Second.patch
+(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
+(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com'
+(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com'
+Dry-OK. Log says:
+Server: relay.example.com
+MAIL FROM:<from@example.com>
+RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com>
+From: Example <from@example.com>
+To: to@example.com
+Cc: A <author@example.com>, One <one@example.com>, two@example.com
+Subject: [PATCH 1/1] Second.
+Date: DATE-STRING
+Message-Id: MESSAGE-ID-STRING
+X-Mailer: X-MAILER-STRING
+
+Result: OK
+EOF
+
+test_expect_success '--suppress-cc=sob' '
+       test_suppression sob
+'
+
+cat >expected-suppress-bodycc <<\EOF
+0001-Second.patch
+(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
+(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com'
+(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com'
+(body) Adding cc: C O Mitter <committer@example.com> from line 'Signed-off-by: C O Mitter <committer@example.com>'
+Dry-OK. Log says:
+Server: relay.example.com
+MAIL FROM:<from@example.com>
+RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com>,<committer@example.com>
+From: Example <from@example.com>
+To: to@example.com
+Cc: A <author@example.com>, One <one@example.com>, two@example.com, C O Mitter <committer@example.com>
+Subject: [PATCH 1/1] Second.
+Date: DATE-STRING
+Message-Id: MESSAGE-ID-STRING
+X-Mailer: X-MAILER-STRING
+
+Result: OK
+EOF
+
+test_expect_success '--suppress-cc=bodycc' '
+       test_suppression bodycc
+'
+
+cat >expected-suppress-cc <<\EOF
+0001-Second.patch
+(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
+(body) Adding cc: C O Mitter <committer@example.com> from line 'Signed-off-by: C O Mitter <committer@example.com>'
+Dry-OK. Log says:
+Server: relay.example.com
+MAIL FROM:<from@example.com>
+RCPT TO:<to@example.com>,<author@example.com>,<committer@example.com>
+From: Example <from@example.com>
+To: to@example.com
+Cc: A <author@example.com>, C O Mitter <committer@example.com>
+Subject: [PATCH 1/1] Second.
+Date: DATE-STRING
+Message-Id: MESSAGE-ID-STRING
+X-Mailer: X-MAILER-STRING
+
+Result: OK
+EOF
+
+test_expect_success '--suppress-cc=cc' '
+       test_suppression cc
+'
+
+test_confirm () {
+       echo y | \
+               GIT_SEND_EMAIL_NOTTY=1 \
+               git send-email \
+               --from="Example <nobody@example.com>" \
+               --to=nobody@example.com \
+               --smtp-server="$(pwd)/fake.sendmail" \
+               $@ \
+               $patches | grep "Send this email"
+}
+
+test_expect_success '--confirm=always' '
+       test_confirm --confirm=always --suppress-cc=all
+'
+
+test_expect_success '--confirm=auto' '
+       test_confirm --confirm=auto
+'
+
+test_expect_success '--confirm=cc' '
+       test_confirm --confirm=cc
+'
+
+test_expect_success '--confirm=compose' '
+       test_confirm --confirm=compose --compose
+'
+
+test_expect_success 'confirm by default (due to cc)' '
+       CONFIRM=$(git config --get sendemail.confirm) &&
+       git config --unset sendemail.confirm &&
+       test_confirm &&
+       git config sendemail.confirm $CONFIRM
+'
+
+test_expect_success 'confirm by default (due to --compose)' '
+       CONFIRM=$(git config --get sendemail.confirm) &&
+       git config --unset sendemail.confirm &&
+       test_confirm --suppress-cc=all --compose
+       ret="$?"
+       git config sendemail.confirm ${CONFIRM:-never}
+       test $ret = "0"
 '
 
 test_expect_success '--compose adds MIME for utf8 body' '
@@ -239,9 +463,7 @@ test_expect_success '--compose adds MIME for utf8 body' '
         echo "echo utf8 body: àéìöú >>\"\$1\""
        ) >fake-editor-utf8 &&
        chmod +x fake-editor-utf8 &&
-       echo y | \
          GIT_EDITOR="\"$(pwd)/fake-editor-utf8\"" \
-         GIT_SEND_EMAIL_NOTTY=1 \
          git send-email \
          --compose --subject foo \
          --from="Example <nobody@example.com>" \
@@ -263,9 +485,7 @@ test_expect_success '--compose respects user mime type' '
         echo " echo utf8 body: àéìöú) >\"\$1\""
        ) >fake-editor-utf8-mime &&
        chmod +x fake-editor-utf8-mime &&
-       echo y | \
          GIT_EDITOR="\"$(pwd)/fake-editor-utf8-mime\"" \
-         GIT_SEND_EMAIL_NOTTY=1 \
          git send-email \
          --compose --subject foo \
          --from="Example <nobody@example.com>" \
@@ -279,9 +499,7 @@ test_expect_success '--compose respects user mime type' '
 
 test_expect_success '--compose adds MIME for utf8 subject' '
        clean_fake_sendmail &&
-       echo y | \
          GIT_EDITOR="\"$(pwd)/fake-editor\"" \
-         GIT_SEND_EMAIL_NOTTY=1 \
          git send-email \
          --compose --subject utf8-sübjëct \
          --from="Example <nobody@example.com>" \
@@ -303,7 +521,7 @@ test_expect_success 'detects ambiguous reference/file conflict' '
 test_expect_success 'feed two files' '
        rm -fr outdir &&
        git format-patch -2 -o outdir &&
-       GIT_SEND_EMAIL_NOTTY=1 git send-email \
+       git send-email \
        --dry-run \
        --from="Example <nobody@example.com>" \
        --to=nobody@example.com \
index 8a9dde44d57b792d7142f082aa18274a13d532f3..9c7b1ad18bca943e6c5b6e50f55d914ccadbfce8 100755 (executable)
@@ -15,8 +15,17 @@ compare_git_head_with () {
 }
 
 compare_svn_head_with () {
-       LC_ALL=en_US.UTF-8 svn log --limit 1 `git svn info --url` | \
-               sed -e 1,3d -e "/^-\{1,\}\$/d" >current &&
+       # extract just the log message and strip out committer info.
+       # don't use --limit here since svn 1.1.x doesn't have it,
+       LC_ALL=en_US.UTF-8 svn log `git svn info --url` | perl -w -e '
+               use bytes;
+               $/ = ("-"x72) . "\n";
+               my @x = <STDIN>;
+               @x = split(/\n/, $x[1]);
+               splice(@x, 0, 2);
+               $x[-1] = "";
+               print join("\n", @x);
+       ' > current &&
        test_cmp current "$1"
 }
 
diff --git a/t/t9130-git-svn-authors-file.sh b/t/t9130-git-svn-authors-file.sh
new file mode 100755 (executable)
index 0000000..b8fb277
--- /dev/null
@@ -0,0 +1,94 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Eric Wong
+#
+
+test_description='git svn authors file tests'
+
+. ./lib-git-svn.sh
+
+cat > svn-authors <<EOF
+aa = AAAAAAA AAAAAAA <aa@example.com>
+bb = BBBBBBB BBBBBBB <bb@example.com>
+EOF
+
+test_expect_success 'setup svnrepo' '
+       for i in aa bb cc dd
+       do
+               svn mkdir -m $i --username $i "$svnrepo"/$i
+       done
+       '
+
+test_expect_success 'start import with incomplete authors file' '
+       ! git svn clone --authors-file=svn-authors "$svnrepo" x
+       '
+
+test_expect_success 'imported 2 revisions successfully' '
+       (
+               cd x
+               test "`git rev-list refs/remotes/git-svn | wc -l`" -eq 2 &&
+               git rev-list -1 --pretty=raw refs/remotes/git-svn | \
+                 grep "^author BBBBBBB BBBBBBB <bb@example\.com> " &&
+               git rev-list -1 --pretty=raw refs/remotes/git-svn~1 | \
+                 grep "^author AAAAAAA AAAAAAA <aa@example\.com> "
+       )
+       '
+
+cat >> svn-authors <<EOF
+cc = CCCCCCC CCCCCCC <cc@example.com>
+dd = DDDDDDD DDDDDDD <dd@example.com>
+EOF
+
+test_expect_success 'continues to import once authors have been added' '
+       (
+               cd x
+               git svn fetch --authors-file=../svn-authors &&
+               test "`git rev-list refs/remotes/git-svn | wc -l`" -eq 4 &&
+               git rev-list -1 --pretty=raw refs/remotes/git-svn | \
+                 grep "^author DDDDDDD DDDDDDD <dd@example\.com> " &&
+               git rev-list -1 --pretty=raw refs/remotes/git-svn~1 | \
+                 grep "^author CCCCCCC CCCCCCC <cc@example\.com> "
+       )
+       '
+
+test_expect_success 'authors-file against globs' '
+       svn mkdir -m globs --username aa \
+         "$svnrepo"/aa/trunk "$svnrepo"/aa/branches "$svnrepo"/aa/tags &&
+       git svn clone --authors-file=svn-authors -s "$svnrepo"/aa aa-work &&
+       for i in bb ee cc
+       do
+               branch="aa/branches/$i"
+               svn mkdir -m "$branch" --username $i "$svnrepo/$branch"
+       done
+       '
+
+test_expect_success 'fetch fails on ee' '
+       ( cd aa-work && ! git svn fetch --authors-file=../svn-authors )
+       '
+
+tmp_config_get () {
+       GIT_CONFIG=.git/svn/.metadata git config --get "$1"
+}
+
+test_expect_success 'failure happened without negative side effects' '
+       (
+               cd aa-work &&
+               test 6 -eq "`tmp_config_get svn-remote.svn.branches-maxRev`" &&
+               test 6 -eq "`tmp_config_get svn-remote.svn.tags-maxRev`"
+       )
+       '
+
+cat >> svn-authors <<EOF
+ee = EEEEEEE EEEEEEE <ee@example.com>
+EOF
+
+test_expect_success 'fetch continues after authors-file is fixed' '
+       (
+               cd aa-work &&
+               git svn fetch --authors-file=../svn-authors &&
+               test 8 -eq "`tmp_config_get svn-remote.svn.branches-maxRev`" &&
+               test 8 -eq "`tmp_config_get svn-remote.svn.tags-maxRev`"
+       )
+       '
+
+test_done
diff --git a/t/t9131-git-svn-empty-symlink.sh b/t/t9131-git-svn-empty-symlink.sh
new file mode 100755 (executable)
index 0000000..8f35e29
--- /dev/null
@@ -0,0 +1,110 @@
+#!/bin/sh
+
+test_description='test that git handles an svn repository with empty symlinks'
+
+. ./lib-git-svn.sh
+test_expect_success 'load svn dumpfile' '
+       svnadmin load "$rawsvnrepo" <<EOF
+SVN-fs-dump-format-version: 2
+
+UUID: 60780f9a-7df5-43b4-83ab-60e2c0673ef7
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-11-26T07:17:27.590577Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 4
+test
+K 10
+svn:author
+V 12
+normalperson
+K 8
+svn:date
+V 27
+2008-11-26T07:18:03.511836Z
+PROPS-END
+
+Node-path: bar
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Content-length: 33
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+
+Revision-number: 2
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 13
+bar => doink
+
+K 10
+svn:author
+V 12
+normalperson
+K 8
+svn:date
+V 27
+2008-11-27T03:55:31.601672Z
+PROPS-END
+
+Node-path: bar
+Node-kind: file
+Node-action: change
+Text-content-length: 10
+Text-content-md5: 92ca4fe7a9721f877f765c252dcd66c9
+Content-length: 10
+
+link doink
+
+EOF
+'
+
+test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" x'
+test_expect_success 'enable broken symlink workaround' \
+  '(cd x && git config svn.brokenSymlinkWorkaround true)'
+test_expect_success '"bar" is an empty file' 'test -f x/bar && ! test -s x/bar'
+test_expect_success 'get "bar" => symlink fix from svn' \
+               '(cd x && git svn rebase)'
+test_expect_success '"bar" becomes a symlink' 'test -L x/bar'
+
+
+test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" y'
+test_expect_success 'disable broken symlink workaround' \
+  '(cd y && git config svn.brokenSymlinkWorkaround false)'
+test_expect_success '"bar" is an empty file' 'test -f y/bar && ! test -s y/bar'
+test_expect_success 'get "bar" => symlink fix from svn' \
+               '(cd y && git svn rebase)'
+test_expect_success '"bar" does not become a symlink' '! test -L y/bar'
+
+# svn.brokenSymlinkWorkaround is unset
+test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" z'
+test_expect_success '"bar" is an empty file' 'test -f z/bar && ! test -s z/bar'
+test_expect_success 'get "bar" => symlink fix from svn' \
+               '(cd z && git svn rebase)'
+test_expect_success '"bar" does not become a symlink' '! test -L z/bar'
+
+
+test_done
diff --git a/t/t9132-git-svn-broken-symlink.sh b/t/t9132-git-svn-broken-symlink.sh
new file mode 100755 (executable)
index 0000000..b8de59e
--- /dev/null
@@ -0,0 +1,102 @@
+#!/bin/sh
+
+test_description='test that git handles an svn repository with empty symlinks'
+
+. ./lib-git-svn.sh
+test_expect_success 'load svn dumpfile' '
+       svnadmin load "$rawsvnrepo" <<EOF
+SVN-fs-dump-format-version: 2
+
+UUID: 60780f9a-7df5-43b4-83ab-60e2c0673ef7
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-11-26T07:17:27.590577Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 4
+test
+K 10
+svn:author
+V 12
+normalperson
+K 8
+svn:date
+V 27
+2008-11-26T07:18:03.511836Z
+PROPS-END
+
+Node-path: bar
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 4
+Text-content-md5: 912ec803b2ce49e4a541068d495ab570
+Content-length: 37
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+asdf
+
+Revision-number: 2
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 13
+bar => doink
+
+K 10
+svn:author
+V 12
+normalperson
+K 8
+svn:date
+V 27
+2008-11-27T03:55:31.601672Z
+PROPS-END
+
+Node-path: bar
+Node-kind: file
+Node-action: change
+Text-content-length: 10
+Text-content-md5: 92ca4fe7a9721f877f765c252dcd66c9
+Content-length: 10
+
+link doink
+
+EOF
+'
+
+test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" x'
+
+test_expect_success '"bar" is a symlink that points to "asdf"' '
+       test -L x/bar &&
+       (cd x && test xasdf = x"`git cat-file blob HEAD:bar`")
+'
+
+test_expect_success 'get "bar" => symlink fix from svn' '
+       (cd x && git svn rebase)
+'
+
+test_expect_success '"bar" remains a proper symlink' '
+       test -L x/bar &&
+       (cd x && test xdoink = x"`git cat-file blob HEAD:bar`")
+'
+
+test_done
diff --git a/t/t9133-git-svn-nested-git-repo.sh b/t/t9133-git-svn-nested-git-repo.sh
new file mode 100755 (executable)
index 0000000..893f57e
--- /dev/null
@@ -0,0 +1,101 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Eric Wong
+#
+
+test_description='git svn property tests'
+. ./lib-git-svn.sh
+
+test_expect_success 'setup repo with a git repo inside it' '
+       svn co "$svnrepo" s &&
+       (
+               cd s &&
+               git init &&
+               test -f .git/HEAD &&
+               > .git/a &&
+               echo a > a &&
+               svn add .git a &&
+               svn commit -m "create a nested git repo" &&
+               svn up &&
+               echo hi >> .git/a &&
+               svn commit -m "modify .git/a" &&
+               svn up
+       )
+'
+
+test_expect_success 'clone an SVN repo containing a git repo' '
+       git svn clone "$svnrepo" g &&
+       echo a > expect &&
+       test_cmp expect g/a
+'
+
+test_expect_success 'SVN-side change outside of .git' '
+       (
+               cd s &&
+               echo b >> a &&
+               svn commit -m "SVN-side change outside of .git" &&
+               svn up &&
+               svn log -v | fgrep "SVN-side change outside of .git"
+       )
+'
+
+test_expect_success 'update git svn-cloned repo' '
+       (
+               cd g &&
+               git svn rebase &&
+               echo a > expect &&
+               echo b >> expect &&
+               test_cmp a expect &&
+               rm expect
+       )
+'
+
+test_expect_success 'SVN-side change inside of .git' '
+       (
+               cd s &&
+               git add a &&
+               git commit -m "add a inside an SVN repo" &&
+               git log &&
+               svn add --force .git &&
+               svn commit -m "SVN-side change inside of .git" &&
+               svn up &&
+               svn log -v | fgrep "SVN-side change inside of .git"
+       )
+'
+
+test_expect_success 'update git svn-cloned repo' '
+       (
+               cd g &&
+               git svn rebase &&
+               echo a > expect &&
+               echo b >> expect &&
+               test_cmp a expect &&
+               rm expect
+       )
+'
+
+test_expect_success 'SVN-side change in and out of .git' '
+       (
+               cd s &&
+               echo c >> a &&
+               git add a &&
+               git commit -m "add a inside an SVN repo" &&
+               svn commit -m "SVN-side change in and out of .git" &&
+               svn up &&
+               svn log -v | fgrep "SVN-side change in and out of .git"
+       )
+'
+
+test_expect_success 'update git svn-cloned repo again' '
+       (
+               cd g &&
+               git svn rebase &&
+               echo a > expect &&
+               echo b >> expect &&
+               echo c >> expect &&
+               test_cmp a expect &&
+               rm expect
+       )
+'
+
+test_done
diff --git a/t/t9134-git-svn-ignore-paths.sh b/t/t9134-git-svn-ignore-paths.sh
new file mode 100755 (executable)
index 0000000..c4b5b8b
--- /dev/null
@@ -0,0 +1,98 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Vitaly Shukela
+# Copyright (c) 2009 Eric Wong
+#
+
+test_description='git svn property tests'
+. ./lib-git-svn.sh
+
+test_expect_success 'setup test repository' '
+       svn co "$svnrepo" s &&
+       (
+               cd s &&
+               mkdir qqq www &&
+               echo test_qqq > qqq/test_qqq.txt &&
+               echo test_www > www/test_www.txt &&
+               svn add qqq &&
+               svn add www &&
+               svn commit -m "create some files" &&
+               svn up &&
+               echo hi >> www/test_www.txt &&
+               svn commit -m "modify www/test_www.txt" &&
+               svn up
+       )
+'
+
+test_expect_success 'clone an SVN repository with ignored www directory' '
+       git svn clone --ignore-paths="^www" "$svnrepo" g &&
+       echo test_qqq > expect &&
+       for i in g/*/*.txt; do cat $i >> expect2; done &&
+       test_cmp expect expect2
+'
+
+test_expect_success 'SVN-side change outside of www' '
+       (
+               cd s &&
+               echo b >> qqq/test_qqq.txt &&
+               svn commit -m "SVN-side change outside of www" &&
+               svn up &&
+               svn log -v | fgrep "SVN-side change outside of www"
+       )
+'
+
+test_expect_success 'update git svn-cloned repo' '
+       (
+               cd g &&
+               git svn rebase --ignore-paths="^www" &&
+               printf "test_qqq\nb\n" > expect &&
+               for i in */*.txt; do cat $i >> expect2; done &&
+               test_cmp expect2 expect &&
+               rm expect expect2
+       )
+'
+
+test_expect_success 'SVN-side change inside of ignored www' '
+       (
+               cd s &&
+               echo zaq >> www/test_www.txt
+               svn commit -m "SVN-side change inside of www/test_www.txt" &&
+               svn up &&
+               svn log -v | fgrep "SVN-side change inside of www/test_www.txt"
+       )
+'
+
+test_expect_success 'update git svn-cloned repo' '
+       (
+               cd g &&
+               git svn rebase --ignore-paths="^www" &&
+               printf "test_qqq\nb\n" > expect &&
+               for i in */*.txt; do cat $i >> expect2; done &&
+               test_cmp expect2 expect &&
+               rm expect expect2
+       )
+'
+
+test_expect_success 'SVN-side change in and out of ignored www' '
+       (
+               cd s &&
+               echo cvf >> www/test_www.txt
+               echo ygg >> qqq/test_qqq.txt
+               svn commit -m "SVN-side change in and out of ignored www" &&
+               svn up &&
+               svn log -v | fgrep "SVN-side change in and out of ignored www"
+       )
+'
+
+test_expect_success 'update git svn-cloned repo again' '
+       (
+               cd g &&
+               git svn rebase --ignore-paths="^www" &&
+               printf "test_qqq\nb\nygg\n" > expect &&
+               for i in */*.txt; do cat $i >> expect2; done &&
+               test_cmp expect2 expect &&
+               rm expect expect2
+       )
+'
+
+test_done
diff --git a/t/t9135-git-svn-moved-branch-empty-file.sh b/t/t9135-git-svn-moved-branch-empty-file.sh
new file mode 100755 (executable)
index 0000000..03705fa
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+test_description='test moved svn branch with missing empty files'
+
+. ./lib-git-svn.sh
+test_expect_success 'load svn dumpfile'  '
+       svnadmin load "$rawsvnrepo" < "${TEST_DIRECTORY}/t9135/svn.dump"
+       '
+
+test_expect_success 'clone using git svn' 'git svn clone -s "$svnrepo" x'
+
+test_expect_success 'test that b1 exists and is empty' '
+       (cd x && test -f b1 && ! test -s b1)
+       '
+
+test_done
diff --git a/t/t9135/svn.dump b/t/t9135/svn.dump
new file mode 100644 (file)
index 0000000..b51c0cc
--- /dev/null
@@ -0,0 +1,192 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 1f80e919-e9e3-4d80-a3ae-d9f21095e27b
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-02-10T19:23:16.424027Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 123
+Content-length: 123
+
+K 7
+svn:log
+V 20
+init standard layout
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-10T19:23:17.195072Z
+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 18
+branch-b off trunk
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-10T19:23:19.160095Z
+PROPS-END
+
+Node-path: branches/branch-b
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Revision-number: 3
+Prop-content-length: 120
+Content-length: 120
+
+K 7
+svn:log
+V 17
+add empty file b1
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-10T19:23:20.194568Z
+PROPS-END
+
+Node-path: branches/branch-b/b1
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 4
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 8
+branch-c
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-10T19:23:21.169100Z
+PROPS-END
+
+Node-path: branches/branch-c
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: trunk
+
+
+Revision-number: 5
+Prop-content-length: 126
+Content-length: 126
+
+K 7
+svn:log
+V 23
+oops, wrong branchpoint
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-10T19:23:21.253557Z
+PROPS-END
+
+Node-path: branches/branch-c
+Node-action: delete
+
+
+Revision-number: 6
+Prop-content-length: 127
+Content-length: 127
+
+K 7
+svn:log
+V 24
+branch-c off of branch-b
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-10T19:23:21.314659Z
+PROPS-END
+
+Node-path: branches/branch-c
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: branches/branch-b
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
diff --git a/t/t9136-git-svn-recreated-branch-empty-file.sh b/t/t9136-git-svn-recreated-branch-empty-file.sh
new file mode 100755 (executable)
index 0000000..733d16e
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+test_description='test recreated svn branch with empty files'
+
+. ./lib-git-svn.sh
+test_expect_success 'load svn dumpfile'  '
+       svnadmin load "$rawsvnrepo" < "${TEST_DIRECTORY}/t9136/svn.dump"
+       '
+
+test_expect_success 'clone using git svn' 'git svn clone -s "$svnrepo" x'
+
+test_done
diff --git a/t/t9136/svn.dump b/t/t9136/svn.dump
new file mode 100644 (file)
index 0000000..6b1ce0b
--- /dev/null
@@ -0,0 +1,192 @@
+SVN-fs-dump-format-version: 2
+
+UUID: eecae021-8f16-48da-969d-79beb8ae6ea5
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-02-22T00:50:56.292890Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 106
+Content-length: 106
+
+K 7
+svn:log
+V 4
+init
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-22T00:50:57.192384Z
+PROPS-END
+
+Node-path: branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: tags
+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
+
+
+Node-path: trunk/file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 2
+Prop-content-length: 105
+Content-length: 105
+
+K 7
+svn:log
+V 3
+1.0
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-22T00:50:58.124724Z
+PROPS-END
+
+Node-path: tags/1.0
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Revision-number: 3
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 9
+1.0.1-bad
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-22T00:50:58.151727Z
+PROPS-END
+
+Node-path: tags/1.0.1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: tags/1.0
+
+
+Revision-number: 4
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 9
+Wrong tag
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-22T00:50:58.167427Z
+PROPS-END
+
+Node-path: tags/1.0.1
+Node-action: delete
+
+
+Revision-number: 5
+Prop-content-length: 113
+Content-length: 113
+
+K 7
+svn:log
+V 10
+1.0-branch
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-22T00:50:58.184498Z
+PROPS-END
+
+Node-path: branches/1.0
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 4
+Node-copyfrom-path: tags/1.0
+
+
+Revision-number: 6
+Prop-content-length: 113
+Content-length: 113
+
+K 7
+svn:log
+V 10
+1.0.1-good
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-22T00:50:58.200695Z
+PROPS-END
+
+Node-path: tags/1.0.1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: branches/1.0
+
+
index 43cd6eecbac70f1ab63f19bf972f17c1f8188d5e..6ed10d0933daa493e7a32296b38e9a32a23a725a 100755 (executable)
@@ -43,9 +43,11 @@ gitweb_run () {
        GATEWAY_INTERFACE="CGI/1.1"
        HTTP_ACCEPT="*/*"
        REQUEST_METHOD="GET"
+       SCRIPT_NAME="$TEST_DIRECTORY/../gitweb/gitweb.perl"
        QUERY_STRING=""$1""
        PATH_INFO=""$2""
-       export GATEWAY_INTERFACE HTTP_ACCEPT REQUEST_METHOD QUERY_STRING PATH_INFO
+       export GATEWAY_INTERFACE HTTP_ACCEPT REQUEST_METHOD \
+               SCRIPT_NAME QUERY_STRING PATH_INFO
 
        GITWEB_CONFIG=$(pwd)/gitweb_config.perl
        export GITWEB_CONFIG
@@ -54,7 +56,7 @@ gitweb_run () {
        # written to web server logs, so we are not interested in that:
        # we are interested only in properly formatted errors/warnings
        rm -f gitweb.log &&
-       perl -- "$TEST_DIRECTORY/../gitweb/gitweb.perl" \
+       perl -- "$SCRIPT_NAME" \
                >/dev/null 2>gitweb.log &&
        if grep "^[[]" gitweb.log >/dev/null 2>&1; then false; else true; fi
 
@@ -659,6 +661,11 @@ cat >>gitweb_config.perl <<EOF
 \$feature{'snapshot'}{'override'} = 1;
 EOF
 
+test_expect_success \
+       'config override: tree view, features not overridden in repo config' \
+       'gitweb_run "p=.git;a=tree"'
+test_debug 'cat gitweb.log'
+
 test_expect_success \
        'config override: tree view, features disabled in repo config' \
        'git config gitweb.blame no &&
@@ -667,12 +674,23 @@ test_expect_success \
 test_debug 'cat gitweb.log'
 
 test_expect_success \
-       'config override: tree view, features enabled in repo config' \
+       'config override: tree view, features enabled in repo config (1)' \
        'git config gitweb.blame yes &&
         git config gitweb.snapshot "zip,tgz, tbz2" &&
         gitweb_run "p=.git;a=tree"'
 test_debug 'cat gitweb.log'
 
+cat >.git/config <<\EOF
+# testing noval and alternate separator
+[gitweb]
+       blame
+       snapshot = zip tgz
+EOF
+test_expect_success \
+       'config override: tree view, features enabled in repo config (2)' \
+       'gitweb_run "p=.git;a=tree"'
+test_debug 'cat gitweb.log'
+
 # ----------------------------------------------------------------------
 # non-ASCII in README.html
 
index fc0e1932e6397403c4efec753e8f4e23fb3cd26a..7a847ecbde018f36911ee6a1074ba169af1f350d 100644 (file)
@@ -3,6 +3,22 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
+# if --tee was passed, write the output not only to the terminal, but
+# additionally to the file test-results/$BASENAME.out, too.
+case "$GIT_TEST_TEE_STARTED, $* " in
+done,*)
+       # do not redirect again
+       ;;
+*' --tee '*|*' --va'*)
+       mkdir -p test-results
+       BASE=test-results/$(basename "$0" .sh)
+       (GIT_TEST_TEE_STARTED=done ${SHELL-sh} "$0" "$@" 2>&1;
+        echo $? > $BASE.exit) | tee $BASE.out
+       test "$(cat $BASE.exit)" = 0
+       exit
+       ;;
+esac
+
 # Keep the original TERM for say_color
 ORIGINAL_TERM=$TERM
 
@@ -94,6 +110,10 @@ do
        --no-python)
                # noop now...
                shift ;;
+       --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind)
+               valgrind=t; verbose=t; shift ;;
+       --tee)
+               shift ;; # was handled already
        *)
                break ;;
        esac
@@ -127,7 +147,7 @@ fi
 
 error () {
        say_color error "error: $*"
-       trap - exit
+       trap - EXIT
        exit 1
 }
 
@@ -163,7 +183,7 @@ die () {
        exit 1
 }
 
-trap 'die' exit
+trap 'die' EXIT
 
 # The semantics of the editor variables are that of invoking
 # sh -c "$EDITOR \"$@\"" files ...
@@ -193,6 +213,31 @@ test_tick () {
        export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
 }
 
+# Call test_commit with the arguments "<message> [<file> [<contents>]]"
+#
+# This will commit a file with the given contents and the given commit
+# message.  It will also add a tag with <message> as name.
+#
+# Both <file> and <contents> default to <message>.
+
+test_commit () {
+       file=${2:-"$1.t"}
+       echo "${3-$1}" > "$file" &&
+       git add "$file" &&
+       test_tick &&
+       git commit -m "$1" &&
+       git tag "$1"
+}
+
+# Call test_merge with the arguments "<message> <commit>", where <commit>
+# can be a tag pointing to the commit-to-merge.
+
+test_merge () {
+       test_tick &&
+       git merge -m "$1" "$2" &&
+       git tag "$1"
+}
+
 # You are not expected to call test_ok_ and test_failure_ directly, use
 # the text_expect_* functions instead.
 
@@ -208,7 +253,7 @@ test_failure_ () {
        say_color error "FAIL $test_count: $1"
        shift
        echo "$@" | sed -e 's/^/        /'
-       test "$immediate" = "" || { trap - exit; exit 1; }
+       test "$immediate" = "" || { trap - EXIT; exit 1; }
 }
 
 test_known_broken_ok_ () {
@@ -409,14 +454,14 @@ test_create_repo () {
        repo="$1"
        mkdir -p "$repo"
        cd "$repo" || error "Cannot setup test environment"
-       "$GIT_EXEC_PATH/git" init "--template=$GIT_EXEC_PATH/templates/blt/" >&3 2>&4 ||
+       "$GIT_EXEC_PATH/git" init "--template=$owd/../templates/blt/" >&3 2>&4 ||
        error "cannot run git init -- have you built things yet?"
        mv .git/hooks .git/hooks-disabled
        cd "$owd"
 }
 
 test_done () {
-       trap - exit
+       trap - EXIT
        test_results_dir="$TEST_DIRECTORY/test-results"
        mkdir -p "$test_results_dir"
        test_results_path="$test_results_dir/${0%-*}-$$"
@@ -467,8 +512,73 @@ test_done () {
 # Test the binaries we have just built.  The tests are kept in
 # t/ subdirectory and are run in 'trash directory' subdirectory.
 TEST_DIRECTORY=$(pwd)
-PATH=$TEST_DIRECTORY/..:$PATH
-GIT_EXEC_PATH=$(pwd)/..
+if test -z "$valgrind"
+then
+       PATH=$TEST_DIRECTORY/..:$PATH
+       GIT_EXEC_PATH=$TEST_DIRECTORY/..
+else
+       make_symlink () {
+               test -h "$2" &&
+               test "$1" = "$(readlink "$2")" || {
+                       # be super paranoid
+                       if mkdir "$2".lock
+                       then
+                               rm -f "$2" &&
+                               ln -s "$1" "$2" &&
+                               rm -r "$2".lock
+                       else
+                               while test -d "$2".lock
+                               do
+                                       say "Waiting for lock on $2."
+                                       sleep 1
+                               done
+                       fi
+               }
+       }
+
+       make_valgrind_symlink () {
+               # handle only executables
+               test -x "$1" || return
+
+               base=$(basename "$1")
+               symlink_target=$TEST_DIRECTORY/../$base
+               # do not override scripts
+               if test -x "$symlink_target" &&
+                   test ! -d "$symlink_target" &&
+                   test "#!" != "$(head -c 2 < "$symlink_target")"
+               then
+                       symlink_target=../valgrind.sh
+               fi
+               case "$base" in
+               *.sh|*.perl)
+                       symlink_target=../unprocessed-script
+               esac
+               # create the link, or replace it if it is out of date
+               make_symlink "$symlink_target" "$GIT_VALGRIND/bin/$base" || exit
+       }
+
+       # override all git executables in TEST_DIRECTORY/..
+       GIT_VALGRIND=$TEST_DIRECTORY/valgrind
+       mkdir -p "$GIT_VALGRIND"/bin
+       for file in $TEST_DIRECTORY/../git* $TEST_DIRECTORY/../test-*
+       do
+               make_valgrind_symlink $file
+       done
+       OLDIFS=$IFS
+       IFS=:
+       for path in $PATH
+       do
+               ls "$path"/git-* 2> /dev/null |
+               while read file
+               do
+                       make_valgrind_symlink "$file"
+               done
+       done
+       IFS=$OLDIFS
+       PATH=$GIT_VALGRIND/bin:$PATH
+       GIT_EXEC_PATH=$GIT_VALGRIND/bin
+       export GIT_VALGRIND
+fi
 GIT_TEMPLATE_DIR=$(pwd)/../templates/blt
 unset GIT_CONFIG
 GIT_CONFIG_NOSYSTEM=1
@@ -493,7 +603,7 @@ fi
 test="trash directory.$(basename "$0" .sh)"
 test ! -z "$debug" || remove_trash="$TEST_DIRECTORY/$test"
 rm -fr "$test" || {
-       trap - exit
+       trap - EXIT
        echo >&5 "FATAL: Cannot prepare test area"
        exit 1
 }
diff --git a/t/valgrind/.gitignore b/t/valgrind/.gitignore
new file mode 100644 (file)
index 0000000..d4ae667
--- /dev/null
@@ -0,0 +1,2 @@
+/bin/
+/templates
diff --git a/t/valgrind/analyze.sh b/t/valgrind/analyze.sh
new file mode 100755 (executable)
index 0000000..d8105d9
--- /dev/null
@@ -0,0 +1,123 @@
+#!/bin/sh
+
+out_prefix=$(dirname "$0")/../test-results/valgrind.out
+output=
+count=0
+total_count=0
+missing_message=
+new_line='
+'
+
+# start outputting the current valgrind error in $out_prefix.++$count,
+# and the test case which failed in the corresponding .message file
+start_output () {
+       test -z "$output" || return
+
+       # progress
+       total_count=$(($total_count+1))
+       test -t 2 && printf "\rFound %d errors" $total_count >&2
+
+       count=$(($count+1))
+       output=$out_prefix.$count
+       : > $output
+
+       echo "*** $1 ***" > $output.message
+}
+
+finish_output () {
+       test ! -z "$output" || return
+       output=
+
+       # if a test case has more than one valgrind error, we need to
+       # copy the last .message file to the previous errors
+       test -z "$missing_message" || {
+               while test $missing_message -lt $count
+               do
+                       cp $out_prefix.$count.message \
+                               $out_prefix.$missing_message.message
+                       missing_message=$(($missing_message+1))
+               done
+               missing_message=
+       }
+}
+
+# group the valgrind errors by backtrace
+output_all () {
+       last_line=
+       j=0
+       i=1
+       while test $i -le $count
+       do
+               # output <number> <backtrace-in-one-line>
+               echo "$i $(tr '\n' ' ' < $out_prefix.$i)"
+               i=$(($i+1))
+       done |
+       sort -t ' ' -k 2 | # order by <backtrace-in-one-line>
+       while read number line
+       do
+               # find duplicates, do not output backtrace twice
+               if test "$line" != "$last_line"
+               then
+                       last_line=$line
+                       j=$(($j+1))
+                       printf "\nValgrind error $j:\n\n"
+                       cat $out_prefix.$number
+                       printf "\nfound in:\n"
+               fi
+               # print the test case where this came from
+               printf "\n"
+               cat $out_prefix.$number.message
+       done
+}
+
+handle_one () {
+       OLDIFS=$IFS
+       IFS="$new_line"
+       while read line
+       do
+               case "$line" in
+               # backtrace, possibly a new one
+               ==[0-9]*)
+
+                       # Does the current valgrind error have a message yet?
+                       case "$output" in
+                       *.message)
+                               test -z "$missing_message" &&
+                               missing_message=$count
+                               output=
+                       esac
+
+                       start_output $(basename $1)
+                       echo "$line" |
+                       sed 's/==[0-9]*==/==valgrind==/' >> $output
+                       ;;
+               # end of backtrace
+               '}')
+                       test -z "$output" || {
+                               echo "$line" >> $output
+                               test $output = ${output%.message} &&
+                               output=$output.message
+                       }
+                       ;;
+               # end of test case
+               '')
+                       finish_output
+                       ;;
+               # normal line; if $output is set, print the line
+               *)
+                       test -z "$output" || echo "$line" >> $output
+                       ;;
+               esac
+       done < $1
+       IFS=$OLDIFS
+
+       # just to be safe
+       finish_output
+}
+
+for test_script in "$(dirname "$0")"/../test-results/*.out
+do
+       handle_one $test_script
+done
+
+output_all
diff --git a/t/valgrind/default.supp b/t/valgrind/default.supp
new file mode 100644 (file)
index 0000000..9e013fa
--- /dev/null
@@ -0,0 +1,45 @@
+{
+       ignore-zlib-errors-cond
+       Memcheck:Cond
+       obj:*libz.so*
+}
+
+{
+       ignore-zlib-errors-value8
+       Memcheck:Value8
+       obj:*libz.so*
+}
+
+{
+       ignore-zlib-errors-value4
+       Memcheck:Value4
+       obj:*libz.so*
+}
+
+{
+       ignore-ldso-cond
+       Memcheck:Cond
+       obj:*ld-*.so
+}
+
+{
+       ignore-ldso-addr8
+       Memcheck:Addr8
+       obj:*ld-*.so
+}
+
+{
+       ignore-ldso-addr4
+       Memcheck:Addr4
+       obj:*ld-*.so
+}
+
+{
+       writing-data-from-zlib-triggers-even-more-errors
+       Memcheck:Param
+       write(buf)
+       obj:/lib/ld-*.so
+       fun:write_in_full
+       fun:write_buffer
+       fun:write_loose_object
+}
diff --git a/t/valgrind/valgrind.sh b/t/valgrind/valgrind.sh
new file mode 100755 (executable)
index 0000000..582b4dc
--- /dev/null
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+base=$(basename "$0")
+
+TRACK_ORIGINS=
+
+VALGRIND_VERSION=$(valgrind --version)
+VALGRIND_MAJOR=$(expr "$VALGRIND_VERSION" : '[^0-9]*\([0-9]*\)')
+VALGRIND_MINOR=$(expr "$VALGRIND_VERSION" : '[^0-9]*[0-9]*\.\([0-9]*\)')
+test 3 -gt "$VALGRIND_MAJOR" ||
+test 3 -eq "$VALGRIND_MAJOR" -a 4 -gt "$VALGRIND_MINOR" ||
+TRACK_ORIGINS=--track-origins=yes
+
+exec valgrind -q --error-exitcode=126 \
+       --leak-check=no \
+       --suppressions="$GIT_VALGRIND/default.supp" \
+       --gen-suppressions=all \
+       $TRACK_ORIGINS \
+       --log-fd=4 \
+       --input-fd=4 \
+       $GIT_VALGRIND_OPTIONS \
+       "$GIT_VALGRIND"/../../"$base" "$@"
index 93c605594fc06683088b934273873165215ccbb5..a3f68ae3b4c2dbac2f5404a73eda3419532110cf 100755 (executable)
@@ -43,10 +43,12 @@ allowdeletetag=$(git config --bool hooks.allowdeletetag)
 
 # check for no description
 projectdesc=$(sed -e '1q' "$GIT_DIR/description")
-if [ -z "$projectdesc" -o "$projectdesc" = "Unnamed repository; edit this file to name it for gitweb." ]; then
+case "$projectdesc" in
+"Unnamed repository"* | "")
        echo "*** Project description file hasn't been set" >&2
        exit 1
-fi
+       ;;
+esac
 
 # --- Check types
 # if $newrev is 0000...0000, it's a commit to delete a ref.
index c6f25e80b8bcf0a21db2bea368b9e444c19bc0bf..498b267a8c7812490d6479839c5577eaaec79d62 100644 (file)
@@ -1 +1 @@
-Unnamed repository; edit this file to name it for gitweb.
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/test-ctype.c b/test-ctype.c
new file mode 100644 (file)
index 0000000..033c749
--- /dev/null
@@ -0,0 +1,78 @@
+#include "cache.h"
+
+
+static int test_isdigit(int c)
+{
+       return isdigit(c);
+}
+
+static int test_isspace(int c)
+{
+       return isspace(c);
+}
+
+static int test_isalpha(int c)
+{
+       return isalpha(c);
+}
+
+static int test_isalnum(int c)
+{
+       return isalnum(c);
+}
+
+static int test_is_glob_special(int c)
+{
+       return is_glob_special(c);
+}
+
+static int test_is_regex_special(int c)
+{
+       return is_regex_special(c);
+}
+
+#define DIGIT "0123456789"
+#define LOWER "abcdefghijklmnopqrstuvwxyz"
+#define UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
+static const struct ctype_class {
+       const char *name;
+       int (*test_fn)(int);
+       const char *members;
+} classes[] = {
+       { "isdigit", test_isdigit, DIGIT },
+       { "isspace", test_isspace, " \n\r\t" },
+       { "isalpha", test_isalpha, LOWER UPPER },
+       { "isalnum", test_isalnum, LOWER UPPER DIGIT },
+       { "is_glob_special", test_is_glob_special, "*?[\\" },
+       { "is_regex_special", test_is_regex_special, "$()*+.?[\\^{|" },
+       { NULL }
+};
+
+static int test_class(const struct ctype_class *test)
+{
+       int i, rc = 0;
+
+       for (i = 0; i < 256; i++) {
+               int expected = i ? !!strchr(test->members, i) : 0;
+               int actual = test->test_fn(i);
+
+               if (actual != expected) {
+                       rc = 1;
+                       printf("%s classifies char %d (0x%02x) wrongly\n",
+                              test->name, i, i);
+               }
+       }
+       return rc;
+}
+
+int main(int argc, char **argv)
+{
+       const struct ctype_class *test;
+       int rc = 0;
+
+       for (test = classes; test->name; test++)
+               rc |= test_class(test);
+
+       return rc;
+}
index 2c0f5a37e8b9051b1db80aa04e9ea763fb8d772b..d261398d6c50aeefa7450b99caae23ee5cdff0d0 100644 (file)
@@ -2,11 +2,13 @@
 
 int main(int argc, char **argv)
 {
-       if (argc == 3 && !strcmp(argv[1], "normalize_absolute_path")) {
+       if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) {
                char *buf = xmalloc(PATH_MAX + 1);
-               int rv = normalize_absolute_path(buf, argv[2]);
-               assert(strlen(buf) == rv);
+               int rv = normalize_path_copy(buf, argv[2]);
+               if (rv)
+                       buf = "++failed++";
                puts(buf);
+               return 0;
        }
 
        if (argc >= 2 && !strcmp(argv[1], "make_absolute_path")) {
@@ -15,12 +17,22 @@ int main(int argc, char **argv)
                        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;
        }
 
-       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)");
+               return 0;
+       }
+
+       fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
+               argv[1] ? argv[1] : "(there was none)");
+       return 1;
 }
diff --git a/test-sigchain.c b/test-sigchain.c
new file mode 100644 (file)
index 0000000..42db234
--- /dev/null
@@ -0,0 +1,22 @@
+#include "sigchain.h"
+#include "cache.h"
+
+#define X(f) \
+static void f(int sig) { \
+       puts(#f); \
+       fflush(stdout); \
+       sigchain_pop(sig); \
+       raise(sig); \
+}
+X(one)
+X(two)
+X(three)
+#undef X
+
+int main(int argc, char **argv) {
+       sigchain_push(SIGTERM, one);
+       sigchain_push(SIGTERM, two);
+       sigchain_push(SIGTERM, three);
+       raise(SIGTERM);
+       return 0;
+}
diff --git a/trace.c b/trace.c
index 4713f9165c54405d51e81c3e90847120ee907e5d..4229ae1231d69aedd9f1aa8350989ddbe2bdb845 100644 (file)
--- a/trace.c
+++ b/trace.c
@@ -50,7 +50,7 @@ static int get_trace_fd(int *need_close)
                return fd;
        }
 
-       fprintf(stderr, "What does '%s' for GIT_TRACE mean?\n", trace);
+       fprintf(stderr, "What does '%s' for GIT_TRACE mean?\n", trace);
        fprintf(stderr, "If you want to trace into a file, "
                "then please set GIT_TRACE to an absolute pathname "
                "(starting with /).\n");
index 56831c57c5cf6500714c46c63581ca98662d58c3..9ad4a16c317b90770974aa5afbcf0986f4a5db91 100644 (file)
@@ -50,9 +50,7 @@ static int read_loose_refs(struct strbuf *path, int name_offset,
        memset (&list, 0, sizeof(list));
 
        while ((de = readdir(dir))) {
-               if (de->d_name[0] == '.' && (de->d_name[1] == '\0' ||
-                               (de->d_name[1] == '.' &&
-                                de->d_name[2] == '\0')))
+               if (is_dot_or_dotdot(de->d_name))
                        continue;
                ALLOC_GROW(list.entries, list.nr + 1, list.alloc);
                list.entries[list.nr++] = xstrdup(de->d_name);
diff --git a/tree.c b/tree.c
index 03e782a9cabc0a12ed5baec0ef59c99f19dbc843..25d2e29fa8b1dfb964b97a10898c77d8fe86ef78 100644 (file)
--- a/tree.c
+++ b/tree.c
@@ -110,7 +110,7 @@ int read_tree_recursive(struct tree *tree,
                case 0:
                        continue;
                case READ_TREE_RECURSIVE:
-                       break;;
+                       break;
                default:
                        return -1;
                }
@@ -131,6 +131,34 @@ int read_tree_recursive(struct tree *tree,
                        if (retval)
                                return -1;
                        continue;
+               } else if (S_ISGITLINK(entry.mode)) {
+                       int retval;
+                       struct strbuf path;
+                       unsigned int entrylen;
+                       struct commit *commit;
+
+                       entrylen = tree_entry_len(entry.path, entry.sha1);
+                       strbuf_init(&path, baselen + entrylen + 1);
+                       strbuf_add(&path, base, baselen);
+                       strbuf_add(&path, entry.path, entrylen);
+                       strbuf_addch(&path, '/');
+
+                       commit = lookup_commit(entry.sha1);
+                       if (!commit)
+                               die("Commit %s in submodule path %s not found",
+                                   sha1_to_hex(entry.sha1), path.buf);
+
+                       if (parse_commit(commit))
+                               die("Invalid commit %s in submodule path %s",
+                                   sha1_to_hex(entry.sha1), path.buf);
+
+                       retval = read_tree_recursive(commit->tree,
+                                                    path.buf, path.len,
+                                                    stage, match, fn, context);
+                       strbuf_release(&path);
+                       if (retval)
+                               return -1;
+                       continue;
                }
        }
        return 0;
index bcdc8bbb3b44a43aa43db6035a31478158e070af..75cd2f1a6adf3ea773a6acc3f1e7f122e30676e5 100644 (file)
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "blob.h"
+#include "exec_cmd.h"
 
 static char *create_temp_file(unsigned char *sha1)
 {
@@ -25,8 +26,10 @@ int main(int argc, char **argv)
 {
        unsigned char sha1[20];
 
+       git_extract_argv0_path(argv[0]);
+
        if (argc != 2)
-               usage("git-unpack-file <sha1>");
+               usage("git unpack-file <sha1>");
        if (get_sha1(argv[1], sha1))
                die("Not a valid object name %s", argv[1]);
 
index 3a4e181af43add517a7a86511c17e4198552289c..e547282ed5c0027b35cbbd8e4f07bf968c6f2171 100644 (file)
@@ -61,7 +61,7 @@ static void unlink_entry(struct cache_entry *ce)
        char *cp, *prev;
        char *name = ce->name;
 
-       if (has_symlink_leading_path(ce_namelen(ce), ce->name))
+       if (has_symlink_or_noent_leading_path(ce_namelen(ce), ce->name))
                return;
        if (unlink(name))
                return;
@@ -583,7 +583,7 @@ static int verify_absent(struct cache_entry *ce, const char *action,
        if (o->index_only || o->reset || !o->update)
                return 0;
 
-       if (has_symlink_leading_path(ce_namelen(ce), ce->name))
+       if (has_symlink_or_noent_leading_path(ce_namelen(ce), ce->name))
                return 0;
 
        if (!lstat(ce->name, &st)) {
index 7e8209ea4b43995737b36bc58db47e7dd6eadb19..7b38fd867bba6f8b8dcf9f2094769314b7f23e02 100644 (file)
@@ -1,4 +1,5 @@
 #include "cache.h"
+#include "exec_cmd.h"
 
 static const char update_server_info_usage[] =
 "git update-server-info [--force]";
@@ -19,6 +20,8 @@ int main(int ac, char **av)
        if (i != ac)
                usage(update_server_info_usage);
 
+       git_extract_argv0_path(av[0]);
+
        setup_git_directory();
 
        return !!update_server_info(force);
index e5adbc011e0ab71eeb06a42c4bd40cbea0bf3fa2..a49d87244706a4faacaadf7916ec86f2e2c0f04e 100644 (file)
@@ -11,7 +11,7 @@
 #include "list-objects.h"
 #include "run-command.h"
 
-static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>";
+static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=nn] <dir>";
 
 /* bits #0..7 in revision.h, #8..10 in commit.c */
 #define THEY_HAVE      (1u << 11)
@@ -397,12 +397,11 @@ static int get_common_commits(void)
        static char line[1000];
        unsigned char sha1[20];
        char hex[41], last_hex[41];
-       int len;
 
        save_commit_buffer = 0;
 
        for(;;) {
-               len = packet_read_line(0, line, sizeof(line));
+               int len = packet_read_line(0, line, sizeof(line));
                reset_timeout();
 
                if (!len) {
@@ -410,7 +409,7 @@ static int get_common_commits(void)
                                packet_write(1, "NAK\n");
                        continue;
                }
-               len = strip(line, len);
+               strip(line, len);
                if (!prefixcmp(line, "have ")) {
                        switch (got_sha1(line+5, sha1)) {
                        case -1: /* they have what we do not */
@@ -616,6 +615,8 @@ int main(int argc, char **argv)
        int i;
        int strict = 0;
 
+       git_extract_argv0_path(argv[0]);
+
        for (i = 1; i < argc; i++) {
                char *arg = argv[i];
 
@@ -643,7 +644,7 @@ int main(int argc, char **argv)
        dir = argv[i];
 
        if (!enter_repo(dir, strict))
-               die("'%s': unable to chdir or not a git archive", dir);
+               die("'%s' does not appear to be a git repository", dir);
        if (is_repository_shallow())
                die("attempt to fetch/clone from a shallow repository");
        if (getenv("GIT_DEBUG_SEND_PACK"))
index 3681062ebfef85af08d71ed6e1ff734804906d6a..d556da975197a718979575864e37c2b9b9f3327a 100644 (file)
@@ -6,14 +6,20 @@ static struct userdiff_driver *drivers;
 static int ndrivers;
 static int drivers_alloc;
 
-#define FUNCNAME(name, pattern) \
-       { name, NULL, -1, { pattern, REG_EXTENDED } }
+#define PATTERNS(name, pattern, word_regex)                    \
+       { name, NULL, -1, { pattern, REG_EXTENDED }, word_regex }
 static struct userdiff_driver builtin_drivers[] = {
-FUNCNAME("html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$"),
-FUNCNAME("java",
+PATTERNS("html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$",
+        "[^<>= \t]+|[^[:space:]]|[\x80-\xff]+"),
+PATTERNS("java",
         "!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n"
-        "^[ \t]*(([ \t]*[A-Za-z_][A-Za-z_0-9]*){2,}[ \t]*\\([^;]*)$"),
-FUNCNAME("objc",
+        "^[ \t]*(([ \t]*[A-Za-z_][A-Za-z_0-9]*){2,}[ \t]*\\([^;]*)$",
+        "[a-zA-Z_][a-zA-Z0-9_]*"
+        "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
+        "|[-+*/<>%&^|=!]="
+        "|--|\\+\\+|<<=?|>>>?=?|&&|\\|\\|"
+        "|[^[:space:]]|[\x80-\xff]+"),
+PATTERNS("objc",
         /* Negate C statements that can look like functions */
         "!^[ \t]*(do|for|if|else|return|switch|while)\n"
         /* Objective-C methods */
@@ -21,20 +27,60 @@ FUNCNAME("objc",
         /* C functions */
         "^[ \t]*(([ \t]*[A-Za-z_][A-Za-z_0-9]*){2,}[ \t]*\\([^;]*)$\n"
         /* Objective-C class/protocol definitions */
-        "^(@(implementation|interface|protocol)[ \t].*)$"),
-FUNCNAME("pascal",
+        "^(@(implementation|interface|protocol)[ \t].*)$",
+        /* -- */
+        "[a-zA-Z_][a-zA-Z0-9_]*"
+        "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
+        "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"
+        "|[^[:space:]]|[\x80-\xff]+"),
+PATTERNS("pascal",
         "^((procedure|function|constructor|destructor|interface|"
                "implementation|initialization|finalization)[ \t]*.*)$"
         "\n"
-        "^(.*=[ \t]*(class|record).*)$"),
-FUNCNAME("php", "^[\t ]*((function|class).*)"),
-FUNCNAME("python", "^[ \t]*((class|def)[ \t].*)$"),
-FUNCNAME("ruby", "^[ \t]*((class|module|def)[ \t].*)$"),
-FUNCNAME("bibtex", "(@[a-zA-Z]{1,}[ \t]*\\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$"),
-FUNCNAME("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$"),
+        "^(.*=[ \t]*(class|record).*)$",
+        /* -- */
+        "[a-zA-Z_][a-zA-Z0-9_]*"
+        "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+"
+        "|<>|<=|>=|:=|\\.\\."
+        "|[^[:space:]]|[\x80-\xff]+"),
+PATTERNS("php", "^[\t ]*((function|class).*)",
+        /* -- */
+        "[a-zA-Z_][a-zA-Z0-9_]*"
+        "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+"
+        "|[-+*/<>%&^|=!.]=|--|\\+\\+|<<=?|>>=?|===|&&|\\|\\||::|->"
+        "|[^[:space:]]|[\x80-\xff]+"),
+PATTERNS("python", "^[ \t]*((class|def)[ \t].*)$",
+        /* -- */
+        "[a-zA-Z_][a-zA-Z0-9_]*"
+        "|[-+0-9.e]+[jJlL]?|0[xX]?[0-9a-fA-F]+[lL]?"
+        "|[-+*/<>%&^|=!]=|//=?|<<=?|>>=?|\\*\\*=?"
+        "|[^[:space:]|[\x80-\xff]+"),
+        /* -- */
+PATTERNS("ruby", "^[ \t]*((class|module|def)[ \t].*)$",
+        /* -- */
+        "(@|@@|\\$)?[a-zA-Z_][a-zA-Z0-9_]*"
+        "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+|\\?(\\\\C-)?(\\\\M-)?."
+        "|//=?|[-+*/<>%&^|=!]=|<<=?|>>=?|===|\\.{1,3}|::|[!=]~"
+        "|[^[:space:]|[\x80-\xff]+"),
+PATTERNS("bibtex", "(@[a-zA-Z]{1,}[ \t]*\\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$",
+        "[={}\"]|[^={}\" \t]+"),
+PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$",
+        "\\\\[a-zA-Z@]+|\\\\.|[a-zA-Z0-9\x80-\xff]+|[^[:space:]]"),
+PATTERNS("cpp",
+        /* Jump targets or access declarations */
+        "!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:.*$\n"
+        /* C/++ functions/methods at top level */
+        "^([A-Za-z_][A-Za-z_0-9]*([ \t]+[A-Za-z_][A-Za-z_0-9]*([ \t]*::[ \t]*[^[:space:]]+)?){1,}[ \t]*\\([^;]*)$\n"
+        /* compound type at top level */
+        "^((struct|class|enum)[^;]*)$",
+        /* -- */
+        "[a-zA-Z_][a-zA-Z0-9_]*"
+        "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
+        "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"
+        "|[^[:space:]]|[\x80-\xff]+"),
 { "default", NULL, -1, { NULL, 0 } },
 };
-#undef FUNCNAME
+#undef PATTERNS
 
 static struct userdiff_driver driver_true = {
        "diff=true",
@@ -134,6 +180,8 @@ int userdiff_config(const char *k, const char *v)
                return parse_string(&drv->external, k, v);
        if ((drv = parse_driver(k, v, "textconv")))
                return parse_string(&drv->textconv, k, v);
+       if ((drv = parse_driver(k, v, "wordregex")))
+               return parse_string(&drv->word_regex, k, v);
 
        return 0;
 }
index ba2945770b379f51aa8da45d112a2ef896ec4c10..c3151594f5c0643fead757accc27bf1093cf4a68 100644 (file)
@@ -11,6 +11,7 @@ struct userdiff_driver {
        const char *external;
        int binary;
        struct userdiff_funcname funcname;
+       const char *word_regex;
        const char *textconv;
 };
 
diff --git a/utf8.c b/utf8.c
index dc3735364f85273c2a119b994ddb405c09dc395c..ddfdc5e2b88d346886f64e39a93ced85cc10a78e 100644 (file)
--- a/utf8.c
+++ b/utf8.c
@@ -246,6 +246,25 @@ int utf8_width(const char **start, size_t *remainder_p)
        return git_wcwidth(ch);
 }
 
+/*
+ * Returns the total number of columns required by a null-terminated
+ * string, assuming that the string is utf8.  Returns strlen() instead
+ * if the string does not look like a valid utf8 string.
+ */
+int utf8_strwidth(const char *string)
+{
+       int width = 0;
+       const char *orig = string;
+
+       while (1) {
+               if (!string)
+                       return strlen(orig);
+               if (!*string)
+                       return width;
+               width += utf8_width(&string, NULL);
+       }
+}
+
 int is_utf8(const char *text)
 {
        while (*text) {
diff --git a/utf8.h b/utf8.h
index 98cce1b038a908bec51ccd2f7e1c1f648cb429a1..2f1b14ff49ef3c73bee6f298ba396b96120c34b7 100644 (file)
--- a/utf8.h
+++ b/utf8.h
@@ -5,6 +5,7 @@ typedef unsigned int ucs_char_t;  /* assuming 32bit int */
 
 ucs_char_t pick_one_utf8_char(const char **start, size_t *remainder_p);
 int utf8_width(const char **start, size_t *remainder_p);
+int utf8_strwidth(const char *string);
 int is_utf8(const char *text);
 int is_encoding_utf8(const char *name);
 
diff --git a/var.c b/var.c
index f1eb314e899c518b8dea54b4ff8f3923da7b12cf..7362ed87354a4bea98c28f9a8c315b7209c67fa2 100644 (file)
--- a/var.c
+++ b/var.c
@@ -4,6 +4,7 @@
  * Copyright (C) Eric Biederman, 2005
  */
 #include "cache.h"
+#include "exec_cmd.h"
 
 static const char var_usage[] = "git var [-l | <variable>]";
 
@@ -56,6 +57,8 @@ int main(int argc, char **argv)
                usage(var_usage);
        }
 
+       git_extract_argv0_path(argv[0]);
+
        setup_git_directory_gently(&nongit);
        val = NULL;
 
index 679adab6a0fccef460acaf90b116b8c6cb9bd460..e57630e983488e5c0222dc47a5e32d6efb184762 100644 (file)
--- a/walker.c
+++ b/walker.c
@@ -18,7 +18,7 @@ void walker_say(struct walker *walker, const char *fmt, const char *hex)
 static void report_missing(const struct object *obj)
 {
        char missing_hex[41];
-       strcpy(missing_hex, sha1_to_hex(obj->sha1));;
+       strcpy(missing_hex, sha1_to_hex(obj->sha1));
        fprintf(stderr, "Cannot obtain needed %s %s\n",
                obj->type ? typename(obj->type): "object", missing_hex);
        if (!is_null_sha1(current_commit_sha1))
index c85ca52ec63a679a2da7bd8980ad4e2df4e38794..d8efb1365a01104db568633fa8f6aef0c67b4cd1 100644 (file)
--- a/wrapper.c
+++ b/wrapper.c
@@ -256,3 +256,36 @@ int git_inflate(z_streamp strm, int flush)
        error("inflate: %s (%s)", err, strm->msg ? strm->msg : "no message");
        return ret;
 }
+
+int odb_mkstemp(char *template, size_t limit, const char *pattern)
+{
+       int fd;
+
+       snprintf(template, limit, "%s/%s",
+                get_object_directory(), pattern);
+       fd = mkstemp(template);
+       if (0 <= fd)
+               return fd;
+
+       /* slow path */
+       /* some mkstemp implementations erase template on failure */
+       snprintf(template, limit, "%s/%s",
+                get_object_directory(), pattern);
+       safe_create_leading_directories(template);
+       return xmkstemp(template);
+}
+
+int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1)
+{
+       int fd;
+
+       snprintf(name, namesz, "%s/pack/pack-%s.keep",
+                get_object_directory(), sha1_to_hex(sha1));
+       fd = open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
+       if (0 <= fd)
+               return fd;
+
+       /* slow path */
+       safe_create_leading_directories(name);
+       return open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
+}
index 96ff2f8f564b907b9ef77bd2ea41b5e854a13085..dd87339ff7b0faeb60091b087e5839c3cce9898c 100644 (file)
@@ -15,11 +15,11 @@ int wt_status_relative_paths = 1;
 int wt_status_use_color = -1;
 int wt_status_submodule_summary;
 static char wt_status_colors[][COLOR_MAXLEN] = {
-       "",         /* WT_STATUS_HEADER: normal */
-       "\033[32m", /* WT_STATUS_UPDATED: green */
-       "\033[31m", /* WT_STATUS_CHANGED: red */
-       "\033[31m", /* WT_STATUS_UNTRACKED: red */
-       "\033[31m", /* WT_STATUS_NOBRANCH: red */
+       GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */
+       GIT_COLOR_GREEN,  /* WT_STATUS_UPDATED */
+       GIT_COLOR_RED,    /* WT_STATUS_CHANGED */
+       GIT_COLOR_RED,    /* WT_STATUS_UNTRACKED */
+       GIT_COLOR_RED,    /* WT_STATUS_NOBRANCH */
 };
 
 enum untracked_status_type show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
index d782f06d9916bdde33dc3f7312f9eac4e14ef3a1..b9b0db8d86615d6ca1046b932772f3d9750a8062 100644 (file)
@@ -15,11 +15,10 @@ static int parse_num(char **cp_p, int *num_p)
 {
        char *cp = *cp_p;
        int num = 0;
-       int read_some;
 
        while ('0' <= *cp && *cp <= '9')
                num = num * 10 + *cp++ - '0';
-       if (!(read_some = cp - *cp_p))
+       if (!(cp - *cp_p))
                return -1;
        *cp_p = cp;
        *num_p = num;
index 84fff583e2a6e3411da4260b9af98043fade1e49..4da052a3fff6768fca05b2f3399c5d87ec1daa2e 100644 (file)
@@ -32,6 +32,7 @@ extern "C" {
 #define XDF_IGNORE_WHITESPACE (1 << 2)
 #define XDF_IGNORE_WHITESPACE_CHANGE (1 << 3)
 #define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 4)
+#define XDF_PATIENCE_DIFF (1 << 5)
 #define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE | XDF_IGNORE_WHITESPACE_AT_EOL)
 
 #define XDL_PATCH_NORMAL '-'
@@ -84,6 +85,7 @@ typedef long (*find_func_t)(const char *line, long line_len, char *buffer, long
 
 typedef struct s_xdemitconf {
        long ctxlen;
+       long interhunkctxlen;
        unsigned long flags;
        find_func_t find_func;
        void *find_func_priv;
index 9d0324a38c2a1974648e67161fa0ed1b0f811233..3e97462bdd2ed72b4ec60a1eb3869e516609867b 100644 (file)
@@ -329,6 +329,9 @@ int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
        xdalgoenv_t xenv;
        diffdata_t dd1, dd2;
 
+       if (xpp->flags & XDF_PATIENCE_DIFF)
+               return xdl_do_patience_diff(mf1, mf2, xpp, xe);
+
        if (xdl_prepare_env(mf1, mf2, xpp, xe) < 0) {
 
                return -1;
index 3e099dc445d6130f6a0ce2c6270a3b06d6ee119f..ad033a8e6a79600b6a3ba0cc16244ede0e9437ea 100644 (file)
@@ -55,5 +55,7 @@ int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr);
 void xdl_free_script(xdchange_t *xscr);
 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);
 
 #endif /* #if !defined(XDIFFI_H) */
index 4625c1b4215231dc343478b2f4f7b4bfccf2c766..05bfa41f102801a5182e7fc642976f91e9ba7db6 100644 (file)
@@ -59,9 +59,10 @@ static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *
  */
 xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg) {
        xdchange_t *xch, *xchp;
+       long max_common = 2 * xecfg->ctxlen + xecfg->interhunkctxlen;
 
        for (xchp = xscr, xch = xscr->next; xch; xchp = xch, xch = xch->next)
-               if (xch->i1 - (xchp->i1 + xchp->chg1) > 2 * xecfg->ctxlen)
+               if (xch->i1 - (xchp->i1 + xchp->chg1) > max_common)
                        break;
 
        return xchp;
diff --git a/xdiff/xpatience.c b/xdiff/xpatience.c
new file mode 100644 (file)
index 0000000..e42c16a
--- /dev/null
@@ -0,0 +1,381 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003-2009 Davide Libenzi, Johannes E. Schindelin
+ *
+ *  This 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.
+ *
+ *  This 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 this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *  Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+#include "xinclude.h"
+#include "xtypes.h"
+#include "xdiff.h"
+
+/*
+ * The basic idea of patience diff is to find lines that are unique in
+ * both files.  These are intuitively the ones that we want to see as
+ * common lines.
+ *
+ * The maximal ordered sequence of such line pairs (where ordered means
+ * that the order in the sequence agrees with the order of the lines in
+ * both files) naturally defines an initial set of common lines.
+ *
+ * Now, the algorithm tries to extend the set of common lines by growing
+ * the line ranges where the files have identical lines.
+ *
+ * Between those common lines, the patience diff algorithm is applied
+ * recursively, until no unique line pairs can be found; these line ranges
+ * are handled by the well-known Myers algorithm.
+ */
+
+#define NON_UNIQUE ULONG_MAX
+
+/*
+ * This is a hash mapping from line hash to line numbers in the first and
+ * second file.
+ */
+struct hashmap {
+       int nr, alloc;
+       struct entry {
+               unsigned long hash;
+               /*
+                * 0 = unused entry, 1 = first line, 2 = second, etc.
+                * line2 is NON_UNIQUE if the line is not unique
+                * in either the first or the second file.
+                */
+               unsigned long line1, line2;
+               /*
+                * "next" & "previous" are used for the longest common
+                * sequence;
+                * initially, "next" reflects only the order in file1.
+                */
+               struct entry *next, *previous;
+       } *entries, *first, *last;
+       /* were common records found? */
+       unsigned long has_matches;
+       mmfile_t *file1, *file2;
+       xdfenv_t *env;
+       xpparam_t const *xpp;
+};
+
+/* The argument "pass" is 1 for the first file, 2 for the second. */
+static void insert_record(int line, struct hashmap *map, int pass)
+{
+       xrecord_t **records = pass == 1 ?
+               map->env->xdf1.recs : map->env->xdf2.recs;
+       xrecord_t *record = records[line - 1], *other;
+       /*
+        * After xdl_prepare_env() (or more precisely, due to
+        * xdl_classify_record()), the "ha" member of the records (AKA lines)
+        * is _not_ the hash anymore, but a linearized version of it.  In
+        * other words, the "ha" member is guaranteed to start with 0 and
+        * the second record's ha can only be 0 or 1, etc.
+        *
+        * So we multiply ha by 2 in the hope that the hashing was
+        * "unique enough".
+        */
+       int index = (int)((record->ha << 1) % map->alloc);
+
+       while (map->entries[index].line1) {
+               other = map->env->xdf1.recs[map->entries[index].line1 - 1];
+               if (map->entries[index].hash != record->ha ||
+                               !xdl_recmatch(record->ptr, record->size,
+                                       other->ptr, other->size,
+                                       map->xpp->flags)) {
+                       if (++index >= map->alloc)
+                               index = 0;
+                       continue;
+               }
+               if (pass == 2)
+                       map->has_matches = 1;
+               if (pass == 1 || map->entries[index].line2)
+                       map->entries[index].line2 = NON_UNIQUE;
+               else
+                       map->entries[index].line2 = line;
+               return;
+       }
+       if (pass == 2)
+               return;
+       map->entries[index].line1 = line;
+       map->entries[index].hash = record->ha;
+       if (!map->first)
+               map->first = map->entries + index;
+       if (map->last) {
+               map->last->next = map->entries + index;
+               map->entries[index].previous = map->last;
+       }
+       map->last = map->entries + index;
+       map->nr++;
+}
+
+/*
+ * This function has to be called for each recursion into the inter-hunk
+ * parts, as previously non-unique lines can become unique when being
+ * restricted to a smaller part of the files.
+ *
+ * It is assumed that env has been prepared using xdl_prepare().
+ */
+static int fill_hashmap(mmfile_t *file1, mmfile_t *file2,
+               xpparam_t const *xpp, xdfenv_t *env,
+               struct hashmap *result,
+               int line1, int count1, int line2, int count2)
+{
+       result->file1 = file1;
+       result->file2 = file2;
+       result->xpp = xpp;
+       result->env = env;
+
+       /* We know exactly how large we want the hash map */
+       result->alloc = count1 * 2;
+       result->entries = (struct entry *)
+               xdl_malloc(result->alloc * sizeof(struct entry));
+       if (!result->entries)
+               return -1;
+       memset(result->entries, 0, result->alloc * sizeof(struct entry));
+
+       /* First, fill with entries from the first file */
+       while (count1--)
+               insert_record(line1++, result, 1);
+
+       /* Then search for matches in the second file */
+       while (count2--)
+               insert_record(line2++, result, 2);
+
+       return 0;
+}
+
+/*
+ * Find the longest sequence with a smaller last element (meaning a smaller
+ * line2, as we construct the sequence with entries ordered by line1).
+ */
+static int binary_search(struct entry **sequence, int longest,
+               struct entry *entry)
+{
+       int left = -1, right = longest;
+
+       while (left + 1 < right) {
+               int middle = (left + right) / 2;
+               /* by construction, no two entries can be equal */
+               if (sequence[middle]->line2 > entry->line2)
+                       right = middle;
+               else
+                       left = middle;
+       }
+       /* return the index in "sequence", _not_ the sequence length */
+       return left;
+}
+
+/*
+ * The idea is to start with the list of common unique lines sorted by
+ * the order in file1.  For each of these pairs, the longest (partial)
+ * sequence whose last element's line2 is smaller is determined.
+ *
+ * For efficiency, the sequences are kept in a list containing exactly one
+ * item per sequence length: the sequence with the smallest last
+ * element (in terms of line2).
+ */
+static struct entry *find_longest_common_sequence(struct hashmap *map)
+{
+       struct entry **sequence = xdl_malloc(map->nr * sizeof(struct entry *));
+       int longest = 0, i;
+       struct entry *entry;
+
+       for (entry = map->first; entry; entry = entry->next) {
+               if (!entry->line2 || entry->line2 == NON_UNIQUE)
+                       continue;
+               i = binary_search(sequence, longest, entry);
+               entry->previous = i < 0 ? NULL : sequence[i];
+               sequence[++i] = entry;
+               if (i == longest)
+                       longest++;
+       }
+
+       /* No common unique lines were found */
+       if (!longest) {
+               xdl_free(sequence);
+               return NULL;
+       }
+
+       /* Iterate starting at the last element, adjusting the "next" members */
+       entry = sequence[longest - 1];
+       entry->next = NULL;
+       while (entry->previous) {
+               entry->previous->next = entry;
+               entry = entry->previous;
+       }
+       xdl_free(sequence);
+       return entry;
+}
+
+static int match(struct hashmap *map, int line1, int line2)
+{
+       xrecord_t *record1 = map->env->xdf1.recs[line1 - 1];
+       xrecord_t *record2 = map->env->xdf2.recs[line2 - 1];
+       return xdl_recmatch(record1->ptr, record1->size,
+               record2->ptr, record2->size, map->xpp->flags);
+}
+
+static int patience_diff(mmfile_t *file1, mmfile_t *file2,
+               xpparam_t const *xpp, xdfenv_t *env,
+               int line1, int count1, int line2, int count2);
+
+static int walk_common_sequence(struct hashmap *map, struct entry *first,
+               int line1, int count1, int line2, int count2)
+{
+       int end1 = line1 + count1, end2 = line2 + count2;
+       int next1, next2;
+
+       for (;;) {
+               /* Try to grow the line ranges of common lines */
+               if (first) {
+                       next1 = first->line1;
+                       next2 = first->line2;
+                       while (next1 > line1 && next2 > line2 &&
+                                       match(map, next1 - 1, next2 - 1)) {
+                               next1--;
+                               next2--;
+                       }
+               } else {
+                       next1 = end1;
+                       next2 = end2;
+               }
+               while (line1 < next1 && line2 < next2 &&
+                               match(map, line1, line2)) {
+                       line1++;
+                       line2++;
+               }
+
+               /* Recurse */
+               if (next1 > line1 || next2 > line2) {
+                       struct hashmap submap;
+
+                       memset(&submap, 0, sizeof(submap));
+                       if (patience_diff(map->file1, map->file2,
+                                       map->xpp, map->env,
+                                       line1, next1 - line1,
+                                       line2, next2 - line2))
+                               return -1;
+               }
+
+               if (!first)
+                       return 0;
+
+               while (first->next &&
+                               first->next->line1 == first->line1 + 1 &&
+                               first->next->line2 == first->line2 + 1)
+                       first = first->next;
+
+               line1 = first->line1 + 1;
+               line2 = first->line2 + 1;
+
+               first = first->next;
+       }
+}
+
+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;
+}
+
+/*
+ * Recursively find the longest common sequence of unique lines,
+ * and if none was found, ask xdl_do_diff() to do the job.
+ *
+ * This function assumes that env was prepared with xdl_prepare_env().
+ */
+static int patience_diff(mmfile_t *file1, mmfile_t *file2,
+               xpparam_t const *xpp, xdfenv_t *env,
+               int line1, int count1, int line2, int count2)
+{
+       struct hashmap map;
+       struct entry *first;
+       int result = 0;
+
+       /* trivial case: one side is empty */
+       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(&map, 0, sizeof(map));
+       if (fill_hashmap(file1, file2, xpp, env, &map,
+                       line1, count1, line2, count2))
+               return -1;
+
+       /* are there any matching lines at all? */
+       if (!map.has_matches) {
+               while(count1--)
+                       env->xdf1.rchg[line1++ - 1] = 1;
+               while(count2--)
+                       env->xdf2.rchg[line2++ - 1] = 1;
+               xdl_free(map.entries);
+               return 0;
+       }
+
+       first = find_longest_common_sequence(&map);
+       if (first)
+               result = walk_common_sequence(&map, first,
+                       line1, count1, line2, count2);
+       else
+               result = fall_back_to_classic_diff(&map,
+                       line1, count1, line2, count2);
+
+       xdl_free(map.entries);
+       return result;
+}
+
+int xdl_do_patience_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;
+
+       /* environment is cleaned up in xdl_diff() */
+       return patience_diff(file1, file2, xpp, env,
+                       1, env->xdf1.nrec, 1, env->xdf2.nrec);
+}
index a43aa72cd088af07b13a6bc8de304ecc178047e5..16890852350cb62bb9f9aec5e52eea8ba46f1192 100644 (file)
@@ -290,7 +290,8 @@ int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
 
        xdl_free_classifier(&cf);
 
-       if (xdl_optimize_ctxs(&xe->xdf1, &xe->xdf2) < 0) {
+       if (!(xpp->flags & XDF_PATIENCE_DIFF) &&
+                       xdl_optimize_ctxs(&xe->xdf1, &xe->xdf2) < 0) {
 
                xdl_free_ctx(&xe->xdf2);
                xdl_free_ctx(&xe->xdf1);