Code

Merge branch 'maint-1.6.1' into maint
authorJunio C Hamano <gitster@pobox.com>
Sun, 8 Mar 2009 05:00:27 +0000 (21:00 -0800)
committerJunio C Hamano <gitster@pobox.com>
Sun, 8 Mar 2009 05:00:27 +0000 (21:00 -0800)
* maint-1.6.1:
  builtin-revert.c: release index lock when cherry-picking an empty commit

321 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/SubmittingPatches
Documentation/blame-options.txt
Documentation/cat-texi.perl
Documentation/config.txt
Documentation/diff-options.txt
Documentation/git-am.txt
Documentation/git-annotate.txt
Documentation/git-apply.txt
Documentation/git-archive.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-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-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-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-blame.c
builtin-branch.c
builtin-cat-file.c
builtin-checkout.c
builtin-clone.c
builtin-commit.c
builtin-count-objects.c
builtin-fetch--tool.c
builtin-fetch.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.c
builtin-receive-pack.c
builtin-remote.c
builtin-rerere.c
builtin-reset.c
builtin-revert.c
builtin-shortlog.c
builtin-symbolic-ref.c
builtin-tag.c
builtin-update-index.c
builtin-verify-pack.c
bundle.c
cache.h
color.c
color.h
commit.c
commit.h
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
dir.c
dir.h
entry.c
exec_cmd.c
exec_cmd.h
fast-import.c
git-add--interactive.perl
git-am.sh
git-bisect.sh
git-compat-util.h
git-cvsserver.perl
git-filter-branch.sh
git-mergetool.sh
git-pull.sh
git-quiltimport.sh
git-rebase--interactive.sh
git-rebase.sh
git-sh-setup.sh
git-submodule.sh
git-svn.perl
git-web--browse.sh
git.c
gitk-git/gitk
gitweb/README
gitweb/gitweb.perl
grep.c
grep.h
hash-object.c
http-push.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
revision.c
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/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/t3400-rebase.sh
t/t3404-rebase-interactive.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/t4011-diff-symlink.sh
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.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/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/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/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/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/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/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
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
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.
index ba07c8c57107a8438d5b3ce1a49761dba7182d25..9b559adefce5c9a89e58d30ae79820a9ef2e9072 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:
index 7f28432254a81e4c39ce8ff204b4d6618050c5c6..df2a7c164130743b28f110bda3d323e978897bce 100644 (file)
@@ -74,7 +74,7 @@ of lines before or after the line given by <start>.
        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 +90,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)
@@ -18,8 +18,12 @@ close TMP;
 
 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..f5152c5038b49ab5ebe3804e8e9b314d4a752690 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
@@ -725,6 +731,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 +1000,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 +1019,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 +1071,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.
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 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..5b3eb12c8a1e8ac50f09745efd091882b248f1bc 100644 (file)
@@ -88,6 +88,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 +122,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 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..3bccffae628e065a6cd92155a9283d5710991fd7 100644 (file)
@@ -133,6 +133,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
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..6ab2af4b61e2188297e15f2e3c1d9c5f05f680b8 100644 (file)
@@ -130,6 +130,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
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 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..1685f04efea3d162377325406e08a762dad94d40 100644 (file)
@@ -98,6 +98,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..da3c38cd60dc0d143fe91417b742335cfff27809 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
@@ -241,9 +243,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 +258,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 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..159390c35a9413590b83f512c3f9f59e6a275e77 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
index 6d66c74cc11e6622892061f8328d04dfe38f87bf..5f21efe40745a98eafff7df67a916b1f1193fae4 100644 (file)
@@ -10,7 +10,7 @@ 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).
 +
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 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..0675c43e73813800d616c3971a450157da707269 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
 
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..cc85675c6212690d6aa344beceb11ad19cb19487 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.2.1.txt
\ No newline at end of file
index 9ac455d889b72deba8c949da1d9efe2be3a50244..e6de0397cc82ae97018cbf4fc82d5697ec4d915d 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,
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 aae14ef8bb63abc598e2e3ce99bf64d5c07e0067..114a214ed3fef40ae5cc13737d037f29d6f8acfd 100644 (file)
@@ -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";
 
@@ -1263,11 +1264,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 +1280,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 +1307,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 +1319,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 +1344,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 +1365,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 +1377,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;
@@ -1618,13 +1631,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 +1769,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;
                }
@@ -2394,7 +2408,7 @@ parse_done:
                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..504a981ad56f73a547c11bf3e18185f67111d000 100644 (file)
@@ -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);
@@ -193,21 +200,6 @@ struct ref_list {
        int kinds;
 };
 
-static int has_commit(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;
-}
-
 static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
 {
        struct ref_list *ref_list = (struct ref_list*)(cb_data);
@@ -231,7 +223,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 */
@@ -401,7 +393,8 @@ 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.kind = REF_LOCAL_BRANCH;
@@ -466,22 +459,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 +494,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..20b34ce6e10d9b863226b501cf5a35178b898995 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;
@@ -361,8 +351,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 +669,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..c338910b1c76f3c994e4a22c9c0a1da38843e050 100644 (file)
@@ -19,6 +19,7 @@
 #include "strbuf.h"
 #include "dir.h"
 #include "pack-refs.h"
+#include "sigchain.h"
 
 /*
  * Overall FIXMEs:
@@ -192,15 +193,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 +225,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 +289,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,6 +350,19 @@ 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;
@@ -357,6 +371,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        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;
@@ -406,8 +421,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 +448,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 +536,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 +583,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. */
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 ab35b65b073e9bc089fcc96f295ca3bc457c992a..62fd1f0961a29cc81032d20b3fe37fcc91293407 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;
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 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 5c4c77adaaba36693e304c27d1f171f8e7e9f151..64dffa542170fcceedc766ae6551134f61779893 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;
@@ -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..2ae39afccdc1978141fe4cc4453befa632ecaadf 100644 (file)
@@ -16,6 +16,7 @@
 #include "patch-ids.h"
 #include "run-command.h"
 #include "shortlog.h"
+#include "remote.h"
 
 /* Set a default date-time format for git log ("log.date" config variable) */
 static const char *default_date_mode = NULL;
@@ -249,22 +250,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,
@@ -846,7 +838,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")) {
@@ -1099,13 +1091,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 +1121,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..bcefa52c69481ceb301a96af6402ca2a029144fb 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;
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..849f1fe6f9c703bd7717e54548300dfe6e495061 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;
@@ -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..ac69d37c8af415a64cba88a888e9b80db95ef97a 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));
@@ -542,8 +552,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 +596,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 +756,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 +789,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..bd8fc77a7a65a21a401f917b75d36883cda634ed 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"
@@ -59,17 +60,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_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);
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 09d08fa3e3dd45b3bbb1ded4776da6bfb546a43d..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();
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 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..189151de25ffd1a6671b7a70f359fa9b94b82173 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;
 
@@ -671,6 +669,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 +696,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 +725,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;
@@ -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..db4dccfb77d80c9bd8981719fe2d0dc17c6772b6 100644 (file)
--- a/color.c
+++ b/color.c
@@ -40,30 +40,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")) {
+       if (!strncasecmp(value, "reset", len)) {
                strcpy(dst, "\033[m");
                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 +86,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 +126,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)
@@ -191,3 +202,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(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..5019df82f79f1888b3aa57b9752a6a55b13f475a 100644 (file)
--- a/color.h
+++ b/color.h
@@ -16,8 +16,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 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 4f275cb8e6a67515292a9dfc60bd1343065067a9..a25589880130f2232aaf626cddcd739ac80dd378 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
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..2f23ab3b87e500137fe0af957901c30e61434564 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;
        }
 
index e00454983ee16624910e40fec9ddb12849110b1b..0a3092f646872867b0060d79c0fd1c85a40e24f4 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,9 +57,11 @@ 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 [ -z "${1-}" ]; then
                if [ -n "$__git_dir" ]; then
                        echo "$__git_dir"
                elif [ -d .git ]; then
@@ -67,6 +76,8 @@ __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)"
@@ -111,14 +122,31 @@ __git_ps1 ()
                        fi
                fi
 
-               if [ -n "$1" ]; then
-                       printf "$1" "${b##refs/heads/}$r"
+               local w
+               local i
+
+               if test -n "${GIT_PS1_SHOWDIRTYSTATE-}"; then
+                       if test "$(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
+
+               if [ -n "${1-}" ]; then
+                       printf "$1" "${b##refs/heads/}$w$i$r"
                else
-                       printf " (%s)" "${b##refs/heads/}$r"
+                       printf " (%s)" "${b##refs/heads/}$w$i$r"
                fi
        fi
 }
 
+# __gitcomp_1 requires 2 arguments
 __gitcomp_1 ()
 {
        local c IFS=' '$'\t'$'\n'
@@ -131,6 +159,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 +173,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 +199,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 +218,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 +251,7 @@ __git_refs ()
        done
 }
 
+# __git_refs2 requires 1 argument (to pass to __git_refs)
 __git_refs2 ()
 {
        local i
@@ -226,6 +260,7 @@ __git_refs2 ()
        done
 }
 
+# __git_refs_remotes requires 1 argument (to pass to ls-remote)
 __git_refs_remotes ()
 {
        local cmd i is_hash=y
@@ -470,6 +505,7 @@ __git_aliases ()
        done
 }
 
+# __git_aliased_command requires 1 argument
 __git_aliased_command ()
 {
        local word cmdline=$(git --git-dir="$(__gitdir)" \
@@ -482,6 +518,7 @@ __git_aliased_command ()
        done
 }
 
+# __git_find_subcommand requires 1 argument
 __git_find_subcommand ()
 {
        local word subcommand c=1
@@ -563,7 +600,7 @@ _git_add ()
        --*)
                __gitcomp "
                        --interactive --refresh --patch --update --dry-run
-                       --ignore-errors
+                       --ignore-errors --intent-to-add
                        "
                return
        esac
@@ -628,7 +665,6 @@ _git_branch ()
        done
 
        case "${COMP_WORDS[COMP_CWORD]}" in
-       --*=*)  COMPREPLY=() ;;
        --*)
                __gitcomp "
                        --color --no-color --verbose --abbrev= --no-abbrev
@@ -759,23 +795,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
                ;;
@@ -823,6 +866,8 @@ _git_format_patch ()
                        --not --all
                        --cover-letter
                        --no-prefix --src-prefix= --dst-prefix=
+                       --inline --suffix= --ignore-if-in-upstream
+                       --subject-prefix=
                        "
                return
                ;;
@@ -930,15 +975,42 @@ _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
                ;;
@@ -950,23 +1022,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=
+                       --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
                ;;
@@ -990,6 +1061,7 @@ _git_merge ()
        --*)
                __gitcomp "
                        --no-commit --no-stat --log --no-log --squash --strategy
+                       --commit --stat --no-squash --ff --no-ff
                        "
                return
        esac
@@ -1149,10 +1221,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 +1443,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 +1451,7 @@ _git_remote ()
        fi
 
        case "$subcommand" in
-       rm|show|prune)
+       rename|rm|show|prune)
                __gitcomp "$(__git_remotes)"
                ;;
        update)
@@ -1403,7 +1479,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 +1520,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 +1537,14 @@ _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
                ;;
        --*)
-               __gitcomp "--pretty="
+               __gitcomp "--pretty=
+                       $__git_diff_common_options
+                       "
                return
                ;;
        esac
@@ -1547,7 +1620,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 +1632,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 +1660,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 +1675,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 +1690,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 +1764,6 @@ _git ()
 
        if [ -z "$command" ]; then
                case "${COMP_WORDS[COMP_CWORD]}" in
-               --*=*) COMPREPLY=() ;;
                --*)   __gitcomp "
                        --paginate
                        --no-pager
@@ -1736,6 +1827,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 ;;
@@ -1756,20 +1848,27 @@ _gitk ()
        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_rev = "";
 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 @@ unless(-d $git_dir) {
        -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 @@ EOM
                                "$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 @@ sub get_file($$$) {
        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 @@ sub get_ignore($$$$$) {
                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 @@ sub copy_path($$$$$$$$) {
        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 @@ while ($to_rev < $opt_l) {
        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 @@ if($orig_branch) {
        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 @@ if($orig_branch) {
        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..3832f602253fbe793ddf81c61b61e5a2757ce89d 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):
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..d93cf960f9eaf05eec11b67746142e6e94d719cb 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -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..d75dff42405404ec86820a534620c1ba68cd9116 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;
@@ -615,6 +620,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..0a14268ba952da5ff66be753cc0cc147ba64ee2b 100644 (file)
@@ -40,7 +40,7 @@ static int get_mode(const char *path, int *mode)
                *mode = 0;
        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..006aa017e28dd217d07bb2c48d932e026175f98d 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,6 +24,7 @@ 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;
@@ -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;
 
-       if (eol) {
-               if (suppress_newline)
-                       buffer->suppressed_newline = 1;
-               else
-                       putc('\n', file);
-       }
+       /* find the end of the word */
+       *end = *begin + 1;
+       while (*end < buffer->size && !isspace(buffer->ptr[*end]))
+               (*end)++;
+
+       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;
        }
@@ -1325,6 +1413,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 +1565,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 +1579,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);
@@ -1858,10 +1968,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 +1981,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 +2027,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 +2035,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 +2053,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 +2199,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 +2326,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) {
@@ -2487,6 +2572,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                options->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE;
        else if (!strcmp(arg, "--ignore-space-at-eol"))
                options->xdl_opts |= XDF_IGNORE_WHITESPACE_AT_EOL;
+       else if (!strcmp(arg, "--patience"))
+               options->xdl_opts |= XDF_PATIENCE_DIFF;
 
        /* flags options */
        else if (!strcmp(arg, "--binary")) {
@@ -2509,6 +2596,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                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 (!prefixcmp(arg, "--color-words=")) {
+               options->flags |= DIFF_OPT_COLOR_DIFF | DIFF_OPT_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 +2645,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 +3554,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 +3571,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..6703a4fb4f0302f4adf1065e91cd1bb27e5c973a 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -78,6 +78,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 +98,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 +265,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 */
diff --git a/dir.c b/dir.c
index 0131983dfbc143ce5dae77e067663bb2e7d5f126..cfd1ea587d9cce825e238ca81ea99b752543dada 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;
@@ -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..3748ddf48d9bdeea890af805016b69e76493a79d 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));
@@ -2407,6 +2404,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)
index b0223c3419301132032fb67519a275e57707df22..5f129a42030917bc5dcab67aeaf67a5f00c17677 100755 (executable)
@@ -12,6 +12,13 @@ my ($prompt_color, $header_color, $help_color) =
                $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) =
@@ -33,6 +40,17 @@ my ($diff_new_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("", @_);
@@ -325,6 +343,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 +442,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++) {
@@ -758,11 +780,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 +843,8 @@ y - stage this hunk
 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 +881,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 +936,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 +973,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 +1002,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 +1026,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 +1060,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..10ad340920efb7177df53cb3a209d1a3edd5a039 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 () {
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 @@ my $methods = {
     '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..9a09ba138244b1665de78e94ce12fb8fdb878758 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,8 +220,14 @@ 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
@@ -207,15 +240,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 +252,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 +314,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 +339,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 +408,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 +471,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 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..25adddfddfc0a7e2cc33ea0dc1e1d7a101514972 100755 (executable)
@@ -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
 
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..368c0ef4342df990a5617fd6fbe2cd973b1b208b 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano.
 #
 
-USAGE='[--interactive | -i] [-v] [--onto <newbase>] <upstream> [<branch>]'
+USAGE='[--interactive | -i] [-v] [--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>
@@ -47,6 +47,7 @@ dotest="$GIT_DIR"/rebase-merge
 prec=4
 verbose=
 git_am_opt=
+rebase_root=
 
 continue_merge () {
        test -n "$prev_head" || die "prev_head must be defined"
@@ -297,6 +298,9 @@ do
        -C*)
                git_am_opt="$git_am_opt $1"
                ;;
+       --root)
+               rebase_root=t
+               ;;
        -*)
                usage
                ;;
@@ -306,6 +310,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 +349,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 +379,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 +410,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.
@@ -429,10 +447,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 +480,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 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 2f47e065fe8b7ca856f4527d6a507a28f1b2a06b..204aab671ef78edc24acff4019a2f40a71a59020 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 '
 }
 
 #
@@ -300,6 +301,10 @@ cmd_update()
                        shift
                        cmd_init "$@" || return
                        ;;
+               -N|--no-fetch)
+                       shift
+                       nofetch=1
+                       ;;
                --)
                        shift
                        break
@@ -345,8 +350,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..959eb52f3fbe01c5f13eeb169549da98b0b9d64f 100755 (executable)
@@ -70,7 +70,8 @@ my ($_stdin, $_help, $_edit,
 $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 @@ my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent,
                   \$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 @@ use constant rev_map_fmt => 'NH40';
 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};
@@ -2386,22 +2404,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 +2431,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 +2531,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 +3270,18 @@ use warnings;
 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 +3291,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 +3378,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 +3406,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 +3457,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 +3471,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 +3479,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 +3487,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 +3503,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 +3550,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 +3565,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 +4077,8 @@ my ($ra_invalid, $can_do_switch, %ignored_err, $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 +4214,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 +4393,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 +4630,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 +4737,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 @@ our $version = "++GIT_VERSION++";
 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 @@ our %feature = (
        # $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 @@ our %feature = (
        # $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 @@ our %feature = (
        # $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 @@ our %feature = (
        '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 @@ our %actions = (
        "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 @@ sub href (%) {
        }
 
        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 @@ sub href (%) {
                $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 @@ HTML
                        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 @@ HTML
        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 @@ XML
                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 @@ XML
                } 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 @@ XML
                }
 
                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..37e66779ab9e14dbe5b5416d0e19b04d7f122d52 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,6 +82,8 @@ int main(int argc, const char **argv)
 
        type = blob_type;
 
+       git_extract_argv0_path(argv[0]);
+
        git_config(git_default_config, NULL);
 
        argc = parse_options(argc, argv, hash_object_options, hash_object_usage, 0);
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;
index 3703dbd1af65c30806a98f0d2d3dc14b8b0e9798..f91293c23f2bcb6d673e0316cd3736fdddd0fbe4 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;
 }
@@ -482,7 +483,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;
 }
@@ -1389,6 +1390,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();
 
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..84a74e544b7bcc20c887f321e389ecf3cfb560d6 100644 (file)
@@ -48,7 +48,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));
        }
 }
 
@@ -280,7 +280,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 +348,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..4c5d09dd25aede8ea7e14886f5c17ed847a8f0d9 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
@@ -506,6 +507,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..912290549bcbb03b7e82750a40297fb1a70b8206 100644 (file)
@@ -151,6 +151,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..e9540e46da7af16e3aa765d79a5b03a4847975df 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -6,6 +6,7 @@
 #include "string-list.h"
 #include "mailmap.h"
 #include "log-tree.h"
+#include "color.h"
 
 static char *user_format;
 
@@ -74,8 +75,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 +209,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 +302,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 +320,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 +336,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,6 +555,17 @@ 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");
                        return 4;
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 @@ fallback:
 }
 
 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..3518207c178904b91ce28f8d3bf2ba0ee560d0e7 100644 (file)
--- a/rerere.c
+++ b/rerere.c
@@ -290,7 +290,7 @@ 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;;
+                               continue;
                        handle_file(path, NULL, rr_path(hex, "preimage"));
                        fprintf(stderr, "Recorded preimage for '%s'\n", path);
                }
index c0df714f6cb0825196d1e79cbc53a042529e2d26..286e416b757fa8df731330992fca96773082f75d 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)
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..032300c4c6434701e802df729cd74245e543de20 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,
@@ -2138,8 +2141,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;
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 8f12d48fe8b4ffe4a4b37dcd16ce58e50837433f..f208cf1db972d580d977c356bb589cf6147247a7 100644 (file)
--- a/t/README
+++ b/t/README
@@ -212,6 +212,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..af6e5e1d6ae503dfadc94feef4d9cb4b623163ec 100644 (file)
@@ -5,6 +5,12 @@ LogFormat "%h %l %u %t \"%r\" %>s %b" common
 CustomLog access.log common
 ErrorLog error.log
 
+<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..4597af0
--- /dev/null
@@ -0,0 +1,31 @@
+#!/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
+       )
+'
+
+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 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 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
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 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
 
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 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..7b976ee36db140550dab33ea990ada8e52dfb13e 100755 (executable)
@@ -16,27 +16,31 @@ 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
 
 '
 
 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 +64,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 +112,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
 '
 
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
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 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 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
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 2ec7ac6a510c5b83bc1ee6ce428379beb7a8b5ef..b8cb2df6670a18ebf54fecbb97a48d0c07a06e2b 100755 (executable)
@@ -234,4 +234,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 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..0c455929e4e52795e616c3e2635f5899dff665c2 100644 (file)
@@ -127,7 +127,7 @@ fi
 
 error () {
        say_color error "error: $*"
-       trap - exit
+       trap - EXIT
        exit 1
 }
 
@@ -163,7 +163,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 +193,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 +233,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_ () {
@@ -416,7 +441,7 @@ test_create_repo () {
 }
 
 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%-*}-$$"
@@ -493,7 +518,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/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..19c24db643c0bceb6e1b960d411d657d2feb0f32 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)
@@ -616,6 +616,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];
 
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 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);